排序在日常生活中应用很广,帮助我们进行筛选,我们处在一个筛选盛行的世界,中考,高考,考研,考编,不都在筛选嘛。

我们以升序为例
1.插入排序
1.1 直接插入排序
从a[1]到a[n]依次插入到前面已经有序的序列中
时间复杂度最好是O(N),初始即有序;最坏是O(N^2),初始逆序
c
void InsertSort(int* a,int n){
int tmp,end;
for (int i = 1; i < n; i++) {//将a[i]插入到前面有序序列中
tmp = a[i];
end = i - 1;
while (end >= 0) {//将tmp插入到a[end]及之前有序的序列中
if (tmp < a[end]) {//如果tmp<a[end],就让a[end]向后挪一个单位
a[end + 1] = a[end];
end--;
}
else
break;
}
a[end + 1] = tmp;//要么是tmp>=a[end],要么是end=-1(说明此时tmp小于序列中所有的数),此时将tmp插入
}
}
1.2 希尔排序
预排序,分组预排,这样大的数据就能减少移动到最后的次数,大的数更快的跳到后面,小的数更快的跳到前面。
时间复杂度是O(NlogN)
我们举个例子,刚开始gap≈n/3,一共有n/3组,每组大约有3个元素,最多挪动1+2=3次,3*(n/3)=n,时间复杂度是O(N);当gap很小,gap=1,是直接插入排序,但是这时候不考虑最坏情况下时间复杂度,考虑最好情况下时间复杂度,因为经过前面的多轮预排序,此时接近有序。
下图出自严蔚敏老师的《数据结构(C语言版)》

下图出自殷人昆老师的《数据结构精讲与习题详解(C语言版)(第2版)》

c
void ShellSort(int* a, int n) {
int tmp,end,gap=n,i,j;
while (gap > 1) {
gap /= 2;
for (j = 0; j < gap; j++) {
for (i = gap + j; i < n; i++) {
end = i - gap;
tmp = a[i];
while (end >= 0) {
if (tmp < a[end]) {
a[end + 1] = a[end];
end--;
}
else
break;
}
a[end + 1] = tmp;
}
}
}
}
也可以像下面这样写,但效率并没有提高,看时间复杂度一味地看循环不是很准确,还是要看逻辑
c
void ShellSort(int* a, int n) {
int tmp, end, gap = n, i;
while (gap > 1) {
gap /= 2;//也可改为gap=gap/3+1;
for (i = gap; i < n; i++) {//i=gap,gap+1.gap+2,...进行的是每组第一次间隔为gap的希尔排序,i=2gap,2gap+1.2gap+2,进行的是每组第二次的希尔排序,以此类推
end = i - gap;
tmp = a[i];
while (end >= 0) {
if (tmp < a[end]) {
a[end + gap] = a[end];
end-=gap;
}
else
break;
}
a[end + gap] = tmp;
}
}
}
2.选择排序
c
//下面两个函数要用的交换函数
void Swap(int* p1, int* p2) {
int tmp = *p1;
*p1 = *p2;
*p2 = tmp;
}
2.1 简单选择排序

朴实无华,蛮干派,又名"躺平排序",因为无论原始数据是否有序,也即最好情况、最坏情况时间复杂度都是O(N^2),因为要依次比较n-1, n-2, n-3, 2个元素并从中选出最大值,可是效率太低啊,我们要一些技巧和方法的
c
void SelectSort(int* a, int n) {
int left=0, right=n-1,i,maxi,mini;
while (left < right) {
maxi = mini = left;
for (i = left + 1; i <= right; i++) {
if (a[i] < a[mini])
mini = i;
else if (a[i] > a[maxi])
maxi = i;
}
Swap(&a[mini], &a[left]);
if (left == maxi)//没有必要管mini是否等于maxi,一旦mini和maxi相等,说明整个left到right都是同一个值,无所谓交换
maxi = mini;
Swap(&a[maxi], &a[right]);
left++;
right--;
}
}
2.2 堆排序
时间复杂度是O(NlogN)
堆排是简单选择排序的优化,将待排序的数组逻辑结构看作完全二叉树,先向下调整建堆,之后交换堆顶元素和末尾元素,再处理剩下的n-1个元素,堆顶元素向下调整为堆,以此类推,直到处理完第二个元素
注:向下调整用于建堆、Top K、堆排比向上调整效率更高,但是插入元素后只能用向上调整建堆,因为如果想用向下调整,要挪动本来的n个元素,时间复杂度就已经是O(N),接着,原本堆中节点的父子关系乱了,没有原来节点间的大小关系,没法运用堆的性质,更麻烦
c
void AdjustDown(int* a, int n, int parent) {
int child = parent * 2 + 1;//左孩子
while (child<n) {
if (child + 1 < n && a[child + 1] > a[child])//找到孩子节点值较大的
child++;
if(a[child]>a[parent])
Swap(&a[parent], &a[child]);//交换父子节点的值
parent = child;
child = parent * 2 + 1;
}
}
void HeapSort(int* a, int n) {
//先建大根堆,向下调整建堆
int child = (n-2) / 2;//最后一个分支节点的下标
while (child >= 0) {//向下调整,从最后一个分支节点调到根节点
AdjustDown(a, n,child);
child--;
}
for (int i = n - 1; i > 0; i--) {//外层循环是N,内层循环是logN
Swap(&a[0], &a[i]);
AdjustDown(a, i, 0);
}
}
3.交换排序
3.1 冒泡排序
实用价值不大,有教学意义
现在各种语言排序底层基本都是快排,基础学科、基础知识的突破是技术革命的关键!!!
从a[0]开始往后冒,如果a[i]>a[i+1],交换两个元素的值,将最大的元素冒到末尾,接着将次大元素冒到倒数第二个元素,以此类推
加入flag优化,如果一趟下来没有任何交换,说明已经有序,此时最好时间复杂度是O(N).
刚开始逆序,时间复杂度是O(N^2).
c
void BubbleSort(int* a, int n) {
int flag = 0, j;
for (int i = 0; i <n-1; i++) {//0到n-2往后冒
for (j = 0; j < n - i-1;j++ ) {
if (a[j] > a[j +1 ]) {
Swap(&a[j], &a[j + 1]);
flag = 1;
}
}
if (flag == 0)//说明此次未发生交换,已有序,跳出循环
break;
}
}
void BubbleSort2(int* a, int n) {
int flag=0,j;
for (int i = n - 1; i > 0; i--) {
j = i;
while (j > 0) {
if (a[j] < a[j - 1]) {
Swap(&a[j], &a[j - 1]);
flag = 1;
}
j--;
}
if (flag==0)//说明此次未发生交换,已有序,跳出循环
break;
}
}
快排和归并内容较多,分别放一篇文章~
