本文總結了經常使用的九種內部排序算法,因做者水平有限,多有謬誤,歡迎批評指正。c++
注:文中數組皆從1開始,沒有用0。默認升序排列。算法
另:轉載請註明出處。ubuntu
普通算法 \(O(n^2)\)數組
先進算法 \(O(n\log n)\)數據結構
特例優化
設待排序序列爲\(T\),排序序列爲\(S\)。ui
從\(T\)中依次選取,加入\(S\)中。spa
設選取元素爲\(T_i\in T\),將\(x\)與\(S\)中的元素\(S_j\)依次比較。debug
若是\(T_i\)大於\(S_j\),則\(j=j+1\);不然將\(S_j\)以及日後的元素後移一位,\(T_i\)做爲新的\(S_j\)。code
//優化寫法 void StraightInsertionSort() { for (int i = 2; i <= n; i++) { for (int j = i - 1; j >= 1 && a[j] > a[j + 1]; j--) { swap(a[j], a[j + 1]); } } }
在直接插入排序中,\(S_j\)是依次選取的,而\(S\)原本已是有序序列,那麼就能夠用二分查找的方式,肯定\(T_i\)應該插入到的位置。
void BinaryInsertionSort() { for (int i = 2; i <= n; i++) { int l = 1, r = i, m; while (l <= r) { m = (l + r) / 2; if (a[i] > a[m]) { l = m + 1; } else { r = m - 1; } } for (int j = i - 1; j > r; j--) { swap(a[j], a[j + 1]); } } }
希爾排序做爲直接插入排序的改進,其思想是,將序列\(T\)進行分組,或者說,按必定的增量選取元素,而不是依次選取。將分組的元素進行排序,不斷細分分組再排序,最終獲得總體有序序列。
因爲分組方法/增量的選取不一樣,希爾排序的算法時間複雜度也會相應改變。一般選取的爲希爾增量,即開始選取序列長度的一半,每次排序後都折半。
可見希爾增量爲1時的特殊狀況,就是直接插入排序。
//加增量後,分組直接插入排序。 void ShellSort() { printf("Mark: Here we use the Shell Increment.\n"); for (int d = n / 2; d >= 1; d /= 2) { /* * for (int i = 1; i <= d; i++) * { * for (int j = i + d; j <= n; j += d) * { * for (int k = j - d; k >= 1 && a[k] > a[k + d]; k -= d) * { * swap(a[k], a[k + d]); * } * } * } */ //寫法優化,少一個循環。 for (int i = d + 1; i <= n; i++) { for (int j = i - d; j >= d && a[j] > a[j + d]; j -= d) { swap(a[j], a[j + d]); } } } }
將無序序列\(T\)兩兩進行交換,進行屢次(最多\(n-1\)次)便可獲得有序序列\(S\)。
加個標誌符判斷已經有序,就不用再排了。
//簡簡單單冒泡排序的優化版本。 void BubbleSort() { for (int i = 1; i <= n - 1; i++) { bool flg = 0; for (int j = 1; j <= n - 1; j++) { if (a[j] > a[j + 1]) { swap(a[j], a[j + 1]); flg = 1; } else { continue; } } if (!flg) { break; } else { continue; } } }
快速排序是對冒泡排序的一種改進,一般選取序列的第一個數做爲一個標準值,大於它的放到右邊,小於它的放到左邊。這時候標準值在序列中的位置就是有序排列後的位置,再分別將左右子列進行一樣操做便可。
代碼運用遞推式寫法。
不具備算法穩定性的緣由是,標準值的選取是不必定的,這裏只是方便起見選取了第一個。
void QuickSort(int l, int r) { if (l < r) { int tmp = a[l]; int il = l, ir = r; while (l < r) { while (l < r && a[r] >= tmp) { r--; } if (a[r] < tmp) { swap(a[l], a[r]); } while (l < r && a[l] <= tmp) { l++; } if (a[l] > tmp) { swap(a[l], a[r]); } } QuickSort(il, l - 1); QuickSort(l + 1, ir); } else { return; } }
遍歷\(T\),每次選取\(T_{i+1}\)到\(T_n\)之間最小的,將其與\(T_i\)交換。
//很簡單。 void SelectionSort() { for (int i = 1; i <= n - 1; i++) { int p = i; for (int j = i + 1; j <= n; j++) { if (a[j] < a[p]) { p = j; } } swap(a[p], a[i]); } }
先將無序序列構成一顆二叉樹,選取非葉子節點,將其調整成一個大根堆,此時將根節點與最後一個元素互換,再進行調整堆的結構,如此反覆。
整個排序是利用了大根堆的性質,即根節點必定是整個堆中最大的元素。
void my_Heap(int A[], int len) { for (int i = len % 2 == 0?len / 2:len / 2 + 1; i >= 1; i--) { ajust_My_Heap(A, i, len); } for (int i = len; i > 1; i--) { swap(a[1], a[i]); ajust_My_Heap(A, 1, i - 1); //debug用 /*for(int j= 1;j<=len;j++) * { * j==len?printf("%d\n",a[j]):printf("%d ",a[j]); * } */ } } void ajust_My_Heap(int A[], int i, int len) { int tmp = A[i]; for (int k = i * 2; k <= len; k = k * 2) { if (k + 1 <= len && A[k] < A[k + 1]) { k++; } if (A[k] > tmp) { A[i] = A[k]; i = k; } else { break; } } A[i] = tmp; } void HeapSort() { //STL寫法 //make_heap(&a[1], &a[n+1]); //sort_heap(&a[1], &a[n+1]); //或用手寫堆來進行操做 my_Heap(a, n); }
兩路歸併排序是運用了分治思想,是將無序序列先進行分割,而後局部調整,而後合併。
合併的時候,若有\(S^1\)和\(S^2\)爲兩個有序序列,要將其合併成有序序列\(S\)。\(S_1=\min\{S^1_1,S^2_1\}\),若是\(S_1\)選取了\(S^1_1\),那麼\(S_2=\min\{S^1_2,S^2_1\}\)。如此進行下去,便可獲得\(S\)。運用了\(S^1\)和\(S^2\)的有序性質。
void Two_MergeSort(int l, int r) { if (l == r) { return; } int m = (l + r) / 2; Two_MergeSort(l, m); Two_MergeSort(m + 1, r); if ((r - l) == 1 && a[l] > a[r]) { swap(a[l], a[r]); } else { int i = l, j = m + 1, p = l; int b[n]; memset(b, 0, sizeof(b)); //核心代碼要好好理解寫法,即使知道了具體的算法思想 while (i <= m && j <= r) { b[p++] = a[i] <= a[j] ? a[i++] : a[j++]; } while (i <= m) { b[p++] = a[i++]; } while (j <= r) { b[p++] = a[j++]; } for (int k = l; k <= r; k++) { a[k] = b[k]; } } }
對於不少個位數相同的數進行排序時,效率很高。
LSD基數排序是按照最低位優先法,將序列從低位開始,安排到十個隊列當中,十個隊列分別記錄他們的這一位數字。利用隊列的FIFO性質,進行收集。再將收集的序列,按更高一位,重複上述方法,一直進行到最高位,收集出來即是有序序列。
注意,必定是隊列。若是從最高位開始,應用隊列的性質是得不到逆序的。即MSD基數排序,應該換爲用棧存儲。
void RadixSort() { //找出最大數位 int maxData = a[1]; for (int i = 2; i <= n; i++) { if (a[i] > maxData) { maxData = a[i]; } } int l = 1; int tmp = 10; while (maxData >= tmp) { tmp *= 10; l++; } int cnt[10]; int b[n]; int r = 1; int k; for (int i = 1; i <= l; i++, r *= 10) { memset(cnt, 0, sizeof(cnt)); for (int j = 1; j <= n; j++) { k = (a[j] / r) % 10; cnt[k]++; } //前綴和來肯定b[j]的位置,很是巧妙 for (int j = 1; j < 10; j++) { cnt[j] = cnt[j - 1] + cnt[j]; } for (int j = n; j >= 1; j--) { k = (a[j] / r) % 10; b[cnt[k]] = a[j]; cnt[k]--; } for (int j = 1; j <= n; j++) { a[j] = b[j]; } } return; }
完整代碼請戳此處。
點個關注不迷路,後續會有更多內容,不限於數據結構,更有PDE、Complex Analysis等重磅內容。
謝謝朋友們!