一些排序算法的总结
1.选择排序 SelectionSort
时间复杂度: O(n^2)
C++代码实现:
template <typename T> void selectionSort(T arr[], int n) { for (int i=0;i<n;i++){ // 寻找[i,n)区间最小值 int minIndex = i; for (int j=i+1;j<n;j++) { if (arr[j] < arr[minIndex]) { minIndex = j; } } swap(arr[i], arr[minIndex]); } }
2.插入排序 InsertionSort
时间复杂度: O(n^2)
空间复杂度: O(1)
插入排序不是通过交换位置,而是通过选择合适的位置进行插入元素.
插入排序对近乎有序的效率极高!
C++代码实现:
template<typename T> void insertionSort(T arr[], int n) { // i从1开始.假设第一个元素位置正确. for (int i = 1; i < n; i++) { // 选择元素arr[i]合适的插入位置 /*for (int j = i; j > 0; j--) { // 作交换 if (arr[j] < arr[j - 1]) { swap(arr[j], arr[j - 1]); } else { break; } }*/ // 改进版本 T e = arr[i]; int j; // j保存元素e应该插入的位置 for (j = i; j > 0&& arr[j-1] >e ; j--) { arr[j] = arr[j - 1]; } arr[j] = e; } }
3.希尔排序 ShellSort
希尔排序也称作 缩小增量排序
该方法的主要思想:
先将待排序的元素划分成若干个子序列. 由相隔某个增量的元素组成.
分别进行插入排序.然后再依次减小增量.再插入排序…
C++代码实现:
template<typename T> void shellSort(T arr[], int n) { int j, gap; T tmp; for (gap = n / 2; gap > 0; gap /= 2) { for (int i = gap; i < n; i++) { tmp = arr[i]; for (j = i; j >= gap&&arr[j-gap]>tmp; j -= gap) { arr[j] = arr[j-gap]; } } arr[j] = tmp; } }
4.归并排序 MergeSort
时间复杂度: O(NlogN)
采用分治算法
C++代码实现:
template<typename T> void mergeSort(T arr[], int n) { __mergeSort(arr,0,n-1); } // 递归使用归并排序,对arr[l...r]的范围进行排序 template<typename T> void __mergeSort(T arr[], int l, int r) { if (l >= r) { return; } int mid = (l + r) / 2; __mergeSort(arr, l, mid); __mergeSort(arr, mid+1, r); if(arr[mid]>arr[mid+1]) __merge(arr, l, mid, r); } template<typename T> // 将arr[l...mid]和arr[mid+1...r]两部分进行归并 void __merge(T arr[], int l,int mid, int r) { T* aux = new T[r - l + 1]; // 将要合并的空间拷贝到一个临时数组中 for (int i=l;i<=r;i++) { aux[i - l] = arr[i]; } int i = l, j = mid + 1; for (int k=l;k<=r;k++){ if (i > mid) { arr[k] = aux[j - l]; j++; } else if (j>r) { arr[k] = aux[i - l]; i++; } else if (aux[i - l] < aux[j - l]) { arr[k] = aux[i - l]; i++; } else { arr[k] = aux[j - l]; j++; } } }
归并算法的优化:
- 对于数量级小的区间.可采用 插入排序
if(r-l<=16){ selectionSort(arr,l,r); }
合并的过程.. 通过比较进行优化
if(arr[mid]>arr[mid+1]) __merge(arr, l, mid, r);
自底向上的归并排序
// 自顶向上的归并排序 template<typename T> void mergeSortBu(T arr[], int n) { for (int sz=1;sz<=n;sz+=sz){ for (int i=0;i+sz<n;i+=sz+sz) { __merge(arr, i, i + sz - 1, min(i+sz+sz-1,n-1)); } } }
5.快速排序 QuickSort
时间复杂度: O(NlogN)
主要的算法思想: 将数组划分成
<v
和>v
的两个区间. 再分别进行快排. 重点在于划分 partition
C++代码实现:
template <typename T> void quickSort(T arr[], int n){ __quickSort(arr, 0, n-1); } // 对arr[l...r]部分进行快速排序 template <typename T> void __quickSort(T arr[], int l, int r){ if( l >= r ) return; int p = __partition(arr, l, r); __quickSort(arr, l, p-1 ); __quickSort(arr, p+1, r); } // 对arr[l...r]部分进行partition操作 // 返回p,使得arr[l...p-1] < arr[p] ; arr[p+1...r] > arr[p] template <typename T> int __partition(T arr[], int l, int r){ T v = arr[l]; int j = l; // arr[l+1...j] < v ; arr[j+1...i) > v for( int i = l + 1 ; i <= r ; i ++ ) if( arr[i] < v ){ j ++; swap( arr[j] , arr[i] ); } swap( arr[l] , arr[j]); return j; }
快排优化一
快排不稳定. 可能退化到 O(n^2)
原因: 是由于 标记值
v
解决: 通过随机选择 标记值
v
可以大概率保证不出现最坏情况swap( arr[l] , arr[rand()%(r-l+1)+l] ); T v = arr[l];
快排优化二
通过增加下标加速划分区间
template <typename T> int _partition2(T arr[], int l, int r){ swap( arr[l] , arr[rand()%(r-l+1)+l] ); T v = arr[l]; // arr[l+1...i) <= v; arr(j...r] >= v int i = l+1, j = r; while( true ){ while( i <= r && arr[i] < v ) i ++; while( j >= l+1 && arr[j] > v ) j --; if( i > j ) break; swap( arr[i] , arr[j] ); i ++; j --; } swap( arr[l] , arr[j]); return j; }
三路快排
针对 数组中可能出现大量重复的值.
将将数组划分成为
<v
=v
>v
三个区间.再对
<v
和>v
各自进行快排.C++代码实现:
template <typename T> void quickSort3Ways(T arr[], int n){ srand(time(NULL)); __quickSort3Ways( arr, 0, n-1); } template <typename T> void __quickSort3Ways(T arr[], int l, int r){ if( r - l <= 15 ){ insertionSort(arr,l,r); return; } swap( arr[l], arr[rand()%(r-l+1)+l ] ); T v = arr[l]; int lt = l; // arr[l+1...lt] < v int gt = r + 1; // arr[gt...r] > v int i = l+1; // arr[lt+1...i) == v while( i < gt ){ if( arr[i] < v ){ swap( arr[i], arr[lt+1]); i ++; lt ++; } else if( arr[i] > v ){ swap( arr[i], arr[gt-1]); gt --; } else{ // arr[i] == v i ++; } } swap( arr[l] , arr[lt] ); __quickSort3Ways(arr, l, lt-1); __quickSort3Ways(arr, gt, r); }
6.堆排序
堆排序的关键在于
堆
-
- 堆首先是一棵完全二叉树
- 二叉堆的性质
实现堆排的方法:
- 方法一:使用额外空间. 将数据逐一插入到一个堆中,再逐一取出.
template<typename T> void heapSort1(T arr[], int n){ MaxHeap<T> maxheap = MaxHeap<T>(n); for( int i = 0 ; i < n ; i ++ ) maxheap.insert(arr[i]); for( int i = n-1 ; i >= 0 ; i-- ) arr[i] = maxheap.extractMax(); }
- 方法二:直接在原数组空间上作堆化. 主要是将叶子节点看做堆.
template<typename T> void heapSort2(T arr[], int n){ MaxHeap<T> maxheap = MaxHeap<T>(arr,n); for( int i = n-1 ; i >= 0 ; i-- ) arr[i] = maxheap.extractMax(); } // 堆化逻辑 MaxHeap(Item arr[], int n){ data = new Item[n+1]; capacity = n; for( int i = 0 ; i < n ; i ++ ) data[i+1] = arr[i]; count = n; for( int i = count/2 ; i >= 1 ; i -- ) shiftDown(i); }