簡介:總的來說,排序算法共有八大類,即冒泡排序、選擇排序、快速排序、插入排序、希爾排序、歸並排序、基數排序以及堆排序等,本文另外也介紹了桶排序。編程語言使用了C/C++(其實主要用的C),3個經常出現的函數形參,arr - 待排序數組名(首元素地址)、bgn - 待排序數組起始排序元素位置(有時我們僅需要對數組中某一段元素進行排序,但通常bgn = 0,即arr首元素位置)、end - 待排序數組截止排序尾元素的下一個位置(即該位置無效,不可引用)。文中均已升序為例,降序原理相同。
時間復雜度:描述該算法在處理大量數據時,總的來說其時間效率的參考; 穩定性:描述算法對原始序列處理前后,該序列相等大小的元素前后位置是否發生改變
兩個常用的函數:1、獲取數組最大元素值; 2、交換兩個整形元素。代碼如下:
//獲取整形數組的最大值 //NOTE: 默認arr非空 int getMaxValue(const vector<int> &arr) { int max = INT_MIN; for (auto val : arr) { if (val > max) max = val; } return max; } /*交換兩個整形值*/ void mySwap(int *pa, int *pb) { int tmp = *pa; *pa = *pb; *pb = tmp; }
1、冒泡排序 - 依次比較相鄰兩元素,若前一元素大於后一元素則交換之,直至最后一個元素即為最大;然后重新從首元素開始重復同樣的操作,直至倒數第二個元素即為次大元素;依次類推。如同水中的氣泡,依次將最大或最小元素氣泡浮出水面。
時間復雜度:O(N2) 穩定性:穩定

/*冒泡排序*/ void bubbleSort(vector<int> &arr, int bgn, int end) { /*isLoop用於指示依次遍歷中是否發生元素交換,若沒有,則已是有序數列,退出即可*/ bool isLoop = true; for (int i = end; true == isLoop && i > bgn; --i) { isLoop = false; for (int j = bgn + 1; j < i; ++j) { if (arr[j] < arr[j - 1]) { mySwap(&arr[j], &arr[j - 1]); isLoop = true; } } } }
2、選擇排序 - 首先初始化最小元素索引值為首元素,依次遍歷待排序數列,若遇到小於該最小索引位置處的元素則刷新最小索引為該較小元素的位置,直至遇到尾元素,結束一次遍歷,並將最小索引處元素與首元素交換;然后,初始化最小索引值為第二個待排序數列元素位置,同樣的操作,可得到數列第二個元素即為次小元素;以此類推。
時間復雜度:O(N2) 穩定性:不穩定

/*選擇排序*/ void selectSort(vector<int> &arr, int bgn, int end) { for (int i = bgn; i < end; ++i) { int minIndex = i; for (int j = i + 1; j < end; ++j) { if (arr[j] < arr[minIndex]) minIndex = j; } if (minIndex != i) mySwap(&arr[i], &arr[minIndex]); } }
3、快速排序 - (類似於選擇排序的定位思想)選一基准元素,依次將剩余元素中小於該基准元素的值放置其左側,大於等於該基准元素的值放置其右側;然后,取基准元素的前半部分和后半部分分別進行同樣的處理;以此類推,直至各子序列剩余一個元素時,即排序完成(類比二叉樹的思想,from up to down)
時間復雜度:O(NlogN) 穩定性:不穩定

/*快排*/ void quickSort(vector<int> &arr, int bgn, int end) //arr must be the reference of real param { //數組arr空or僅有一個元素則退出 if (bgn >= end - 1) return; int lindex = bgn; int rindex = end - 1; int std = arr[lindex]; while (lindex < rindex) { while (lindex < rindex) { if (arr[rindex] < std) { arr[lindex++] = arr[rindex]; break; } --rindex; } while (lindex < rindex) { if (arr[lindex] >= std) { arr[rindex--] = arr[lindex]; break; } ++lindex; } } arr[lindex] = std; quickSort(arr, bgn, lindex); quickSort(arr, rindex + 1, end); }
4、插入排序 - 數列前面部分看為有序,依次將后面的無序數列元素插入到前面的有序數列中,初始狀態有序數列僅有一個元素,即首元素。在將無序數列元素插入有序數列的過程中,采用了逆序遍歷有序數列,相較於順序遍歷會稍顯繁瑣,但當數列本身已近排序狀態效率會更高。
時間復雜度:O(N2) 穩定性:穩定

/*插入排序*/ void insertSort(vector<int> &arr, int bgn, int end) { for (int i = bgn + 1; i < end; ++i) { /* * 分為1,2兩部分處理,可以囊括j = beg - 1時的情況 * 即需要將arr[i]插入到首元素前的位置,若使用一個for * 包括這兩部分,則會在發生這種情況時退出 */ /*1*/ int j = i - 1; for ( ; j >= bgn; --j) if (arr[j] <= arr[i]) break; /*2*/ if (j != i - 1) { int temp = arr[i]; for (int k = i; k > j + 1; --k) { arr[k] = arr[k - 1]; } arr[j + 1] = temp; } } }
5、希爾排序 - 插入排序的改進版。為了減少數據的移動次數,在初始序列較大時取較大的步長,通常取序列長度的一半,此時只有兩個元素比較,交換一次;之后步長依次減半直至步長為1,即為插入排序,由於此時序列已接近有序,故插入元素時數據移動的次數會相對較少,效率得到了提高。
時間復雜度:通常認為是O(N3/2) ,未驗證 穩定性:不穩定

1 /*希爾排序*/ 2 void shellSort(vector<int> &arr, int bgn, int end) 3 { 4 for (int step = (end - bgn) / 2; step > 0; step /= 2) 5 { 6 for (int i = bgn; i < bgn + step; ++i) 7 { 8 /* 9 * 以下,insertSort的變異 10 */ 11 for (int j = i + step; j < end; j += step) 12 { 13 int k = j - step; 14 for ( ; k >= i; k -= step) 15 if (arr[k] <= arr[j]) 16 break; 17 if (k != j - step) 18 { 19 int tmp = arr[j]; 20 for (int m = j; m > k + step; m -= step) 21 arr[m] = arr[m - step]; 22 arr[k + step] = tmp; 23 } 24 } 25 } 26 } 27 }
6、桶排序 - 實現線性排序,但當元素間值得大小有較大差距時會帶來內存空間的較大浪費。首先,找出待排序列中得最大元素max,申請內存大小為max + 1的桶(數組)並初始化為0;然后,遍歷排序數列,並依次將每個元素作為下標的桶元素值自增1;最后,遍歷桶元素,並依次將值非0的元素下標值載入排序數列(桶元素>1表明有值大小相等的元素,此時依次將他們載入排序數列),遍歷完成,排序數列便為有序數列。
時間復雜度:O(x*N) 穩定性:穩定

/*桶排序*/ void bucketSort(vector<int> &arr) { int max = getMaxValue(arr); int *pBuf = new int[max + 1]; memset(pBuf, 0, (max + 1)*sizeof(int)); for (auto const i : arr) ++pBuf[i]; for (int i = 0, j = 0; i <= max; ++i) { while (pBuf[i]--) arr[j++] = i; } delete []pBuf; }
7、基數排序 - 桶排序的改進版,桶的大小固定為10,減少了內存空間的開銷。首先,找出待排序列中得最大元素max,並依次按max的低位到高位對所有元素排序;桶元素10個元素的大小即為待排序數列元素對應數值為相等元素的個數,即每次遍歷待排序數列,桶將其按對應數值位大小分為了10個層級,桶內元素值得和為待排序數列元素個數。
時間復雜度:O(x*N) 穩定性:穩定

/*基數排序*/ //1. 計數排序,按整形數值單位進行排序 void countSort(vector<int> &arr, int exp) { int bucket[10] = { 0 }; int arrSize = arr.size(); int *pTemp = new int[arrSize]; memset(pTemp, 0, arrSize * sizeof(int)); //統計單位exp各數值計數值 for (auto const val : arr) ++bucket[(val / exp) % 10]; //計數分層 for (int i = 1; i < 10; ++i) bucket[i] += bucket[i - 1]; //按exp位大小用數組arr元素填充pTemp for (int i = arrSize - 1; i >= 0; --i) pTemp[ --bucket[(arr[i] / exp) % 10] ] = arr[i]; /*bugs*/ #if 0 //bug1: bucket各層次的計數值沒遍歷一次相應自減1 for (auto const val : arr) pTemp[bucket[(val / exp) % 10] - 1] = val; //bug2: arr數組元素每次排序時,下標應從大到小遍歷,否則無法實現排序 for (auto const val : arr) pTemp[ --bucket[(val / exp) % 10] ] = val; #endif //pTemp -> arr for (int i = 0; i < arrSize; ++i) arr[i] = pTemp[i]; delete []pTemp; } //2. 合並各單位計數排序結果 void radixSort(vector<int> &arr) { int max = getMaxValue(arr); for (int exp = 1; max / exp != 0; exp *= 10) countSort(arr, exp); }
8、歸並排序 - 采用了分治和遞歸的思想,遞歸&分治-排序整個數列如同排序兩個有序數列,依次執行這個過程直至排序末端的兩個元素,再依次向上層輸送排序好的兩個子列進行排序直至整個數列有序(類比二叉樹的思想,from down to up)。
時間復雜度:O(NlogN) 穩定性:穩定

/*歸並排序*/ //排序兩個有序數列 void mergeSortInOrder(vector<int> &arr, int bgn, int mid, int end) { int *pBuf = new int[end - bgn]; int *pTemp = pBuf; int lindex = bgn; int rindex = mid; while ((lindex < mid) && (rindex < end)) *(pTemp++) = (arr[lindex] < arr[rindex]) ? arr[lindex++] : arr[rindex++]; while (lindex < mid) *pTemp++ = arr[lindex++]; while (rindex < end) *pTemp++ = arr[rindex++]; //pTemp -> arr pTemp = pBuf; for (int i = bgn; i < end; i++) arr[i] = *pTemp++; delete []pBuf; } //UpToDown To DownToUp void mergeSort(vector<int> &arr, int bgn, int end) { //數組arr空or僅有一個元素則退出 if (bgn >= end - 1) return; int mid = (bgn + end) / 2; mergeSort(arr, bgn, mid); mergeSort(arr, mid, end); mergeSortInOrder(arr, bgn, mid, end); }
9、堆排序 - 堆排序的思想借助於二叉堆中的最大堆得以實現。首先,將待排序數列抽象為二叉樹,並構造出最大堆;然后,依次將最大元素(即根節點元素)與待排序數列的最后一個元素交換(即二叉樹最深層最右邊的葉子結點元素);每次遍歷,刷新最后一個元素的位置(自減1),直至其與首元素相交,即完成排序。
時間復雜度:O(NlogN) 穩定性:不穩定

/*堆排序*/ //根節點元素自頂向下移動到合適的位置以構成最大堆 void downToMaxHeap(vector<int> &arr, int bgn, int end) { int child; int parent = bgn; /*假根節點向下移動至合適的位置 --整個堆排序的核心代碼塊*/ while ((child = parent * 2 + 1) < end) { if ((child < end - 1) && (arr[child] < arr[child + 1])) ++child; //右孩子節點 if (arr[child] > arr[parent]) mySwap(&arr[child], &arr[parent]); else break; parent = child; } } //將數組構造為最大堆 void buildMaxHeap(vector<int> &arr, int bgn, int end) { if (bgn >= end - 1) return; int parent = end / 2 - 1; while (parent >= 0) { downToMaxHeap(arr, parent, end); --parent; } } //堆排序 void heapSort(vector<int> &arr, int bgn, int end) { //構造最大堆 buildMaxHeap(arr, bgn, end); while (end > 1) { mySwap(&arr[0], &arr[--end]); downToMaxHeap(arr, 0, end); } }