本文总结了经常使用的九种内部排序算法,因做者水平有限,多有谬误,欢迎批评指正。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等重磅内容。
谢谢朋友们!