数据结构:排序算法
数据结构:排序算法
前言
本文总结了常用的九种内部排序算法,因作者水平有限,多有谬误,欢迎批评指正。
注:文中数组皆从1开始,没有用0。默认升序排列。
另:转载请注明出处。
分类
按时间复杂度
-
普通算法 \(O(n^2)\)
- 直接插入排序
- 二分插入排序
- 冒泡排序
- 直接选择排序
-
先进算法 \(O(n\log n)\)
- 快速排序
- 堆排序
- 选择排序
-
特例
- 希尔排序,\(O(n^{1.3 \sim 2})\)
- 基数排序,\(O(dn)\),其中\(d\)为元素的最大位数
按排序种类
- 插入排序
- 直接插入排序
- 二分插入排序
- 希尔排序
- 交换排序
- 冒泡排序
- 快速排序
- 选择排序
- 直接选择排序
- 堆排序
- 归并排序
- 基数排序
按算法稳定性
- 稳定算法
- 直接插入排序
- 二分插入排序
- 冒泡排序
- 归并排序
- 基数排序
- 不稳定算法
- 希尔排序
- 快速排序
- 直接选择排序
- 堆排序
直接插入排序
算法思想
设待排序序列为\(T\),排序序列为\(S\)。
从\(T\)中依次选取,加入\(S\)中。
设选取元素为\(T_i\in T\),将\(x\)与\(S\)中的元素\(S_j\)依次比较。
如果\(T_i\)大于\(S_j\),则\(j=j+1\);否则将\(S_j\)以及往后的元素后移一位,\(T_i\)作为新的\(S_j\)。
代码
//优化写法
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等重磅内容。
谢谢朋友们!

浙公网安备 33010602011771号