目录
排序算法
⚠️注意:以下都是按照升序进行排列
一、插入排序
1.1 直接插入排序
核心思想:把一个数插入到有序区间,保持这个区间有序,当前第n+1个数插入到前面
前面的arr[0]到arr[n-1]已经排好序,此时用arr[n]与前面的arr[n-1], arr[n-2]...的值
进行比较找到合适的位置将arr[n]进行插入,原来位置上的元素顺序后移实现了插入
代码实现
//插入排序
void InsertSort(int* a, int n){
//控制起始条件
for(int i=0; i<n-1;i++){ //注意控制好终止条件,这里的end的位置是在倒数第二个位置,所以要-1
//单趟插入
int end = i;
int temp = a[end + 1]; //有序区间的后面
while(end>=0){ //end到-1就终止了
if(a[end]>temp){
a[end+1] = a[end];
--end;
}else{
break;
}
}
//两种情况:第一种在最右边,第二种在最左边,end为-1了,始终放在end后面
a[end+1] = temp;
}
}
1.2 希尔排序
核心思想:插入排序的进阶版,首先进行预排序,让数组接近升序然后直接排序,这里的预排序
是按分组,选取一个gap值,对这gap组数据插入排序,预拍下gap为n时,有n组需要排序
gap越小,越接近有序,gap越大,越不接近有序
但是gap越小挪动越慢,gap越大挪动越快
时间复杂度:O(N^1.3),一般gap建议为3
void ShellSort(int* a, int n){
int gap = n;
while(gap>1){
// gap = gap/2;
gap = gap/3 + 1; //保证gap一定可以为1 , 如gap是2, 2/3 = 0,那么gap等于0就不满足了,因此要加1
for(int i=0; i< n - gap; ++i){ //并排走
int end = i;
int tmp = a[end+gap]; //存储最后一个数
while(end>=0){
if(tmp < a[end]){
a[end+gap] = a[end];
end = end-gap;
}else{
break;
}
}
a[end+gap] = tmp;
}
}
}
二、选择排序
2.1 选择排序
核心思想:选择最大的,和最小的这里要注意,当maxi最大正好在开始的地方,需要把maxi移到正确的位置
时间复杂度:O(N^2),最坏的排序
void SelectSort(int* a, int n){
int begin=0, end=n-1;
while(begin < end){
//选出当前区间[begin, end]中最大和最小值
int maxi = begin, mini = begin;
for(int i=begin; i<=end; ++i){
if(a[i]>a[maxi]){
maxi = i;
}
if(a[i]<a[mini]){
mini = i;
}
}
Swap(&a[mini], &a[begin]);
//如果换完之后,begin正好是max
if(begin == maxi){
maxi = mini;
}
Swap(&a[maxi], &a[end]);
begin++;
end--;
}
}
2.2 堆排序
我们在写堆排序前需要了解下列知识点
堆定义:用数组存储表示的完全二叉树
小堆:树中所有的父节点都小于等于孩子
大堆:树中所有的父节点都大于等于孩子
父子关系公式:
leftChild = parent * 2 + 1
rightChild = parent * 2 + 2
parent = (child - 1) / 2
在堆排序前要知道堆向下调整算法,当满足下面图的情况我们就可以进一步排序
条件:根左右子树都是小堆,就可以向下做调整
1. 从根开始,不断往下调整
2. 选出左右孩子中小的,跟父亲比较,如果选出的小的孩子比父亲还要小,跟父亲交换,以小的孩子位置继续往下调整,最坏调到叶子终止
3. 如果比父亲大则停止
但是在上述排序动作前,需要先建堆,排升序建大堆,选出最大的数将其放到最后面,然后满足大小堆后即可做向下调整动作即可
核心思想:第一步建堆搞成(大/小堆)
向下调整算法:满足 根 下面的左右子树都是堆
//堆排序
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[child], &a[parent]);
parent = child;
child = parent*2+1;
}else{
break;
}
}
}
void HeapSort(int* a, int n){
//排升序建大堆 O(N)
for(int i=(n-1-1)/2; i>=0; i--){
AdjustDown(a, n, i);
}
//O(N*logN)
int end = n - 1;
while(end > 0){
Swap(&a[0], &a[end]);
AdjustDown(a, end, 0);
end--;
}
}
三、交换排序
3.1 冒泡排序
核心思想:先看单趟,两两比较交换,依次循环往复
如果发现flag一趟排下来没有进行交换动作就直接结束排序
void BubbleSort(int* a, int n){
for(int j=0; j<n; j++){
int flag = 0;
for(int i=1; i<n-j; ++i){
if(a[i] < a[i-1] ){
Swap(&a[i], &a[i-1]);
flag = 1;
}
}
if(flag == 0){
break;
}
}
}
3.2 快速排序
3.2.1 Hoare
上图说明:选一个关键key,一般都是选择头,或者尾
单趟:key放在他正确的位置上,key的左边值比key小,key右边值比key大(这是key一趟下来排完后最终要放的位置)
单趟拍完,再想办法让左边区间有序,key的右边区间有序
时间复杂度为N*log(N), 高度是logN像二叉树,每一次单趟排都是N
如果是有序的,最坏是O(N^2)
那么还有优化解决方案:
第一种是取随机值做下标
第二种是获取这三个数中不是最大,也不是最小的那个值的下标,这种情况下不会有最坏情况,因为有三数组取中
//三数取中
int midIndex(int* a, int left, int right){
int mid = left + (right - left)/2;
if(a[left]<a[mid]){
if(a[mid]<a[right]){
return mid;
}else if(a[left]<a[right]){
return right;
}else{
return left;
}
}else{ // a[left] > a[mid]
if(a[mid]>a[right]){
return mid;
}else if(a[left] < a[right]){
return left;
}else{
return right;
}
}
}
int PartSort(int* a, int left, int right){
int midI = midIndex(a, left, right);
Swap(&a[left], &a[midI]);
int keyi = left; //左边的做key,为下标,最右边的先出发
//没有相遇就继续
while(left<right){
//如果左边做key,右边先走找比key小的
while(left<right && a[right] >= a[keyi]){
--right;
}
//左边再走,找大
while(left < right && a[left] <= a[keyi]){
++left;
}
//交换,把大的还换右边,小的换左边
Swap(&a[left], &a[right]);
}
Swap(&a[left], &a[keyi]); //这里的left和rightn用哪个都行
return left; //返回key的位置
}
3.2.2 前后指针法
pre和key在同一位置,cur在后一个位置
核心思想:
1. cur往前走,找到比key小的数据
2. 找到后停下来,++pre
3. 交换pre和cur指向位置的值
4. 知道cur走到数组的结尾
5. 最后把pre和key指向的值做交换,使得key的做小比key小,key右边比key大
int PartSort2(int* a, int left, int right){
int key = left;
int pre = left;
int cur = pre + 1;
while(cur<=right){
if(a[cur] <= a[key] ){
++pre;
Swap(&a[pre], &a[cur]);
}
//无论哪种情况,cur都要继续向后走
++cur;
}
Swap(&a[pre], &a[key]);
return pre;
}
void QuickSort(int* a, int left, int right){
if(left < right){
int keyPos = PartSort3(a, left, right);
// printArr(a+left, right-left+1);
QuickSort(a, left, keyPos-1);
QuickSort(a, keyPos+1, right);
}
}
3.2.3 挖坑法
int PartSort3(int* a, int left, int right){
int key = a[left]; //坑位的值进行保存
int hole = left;
while(left < right){
//右边找小,填到左边的坑
while(left < right && a[right] >=key ){
--right;
}
//把右边找的小的,填到左边的坑,自己形成新的坑
a[hole] = a[right];
hole = right;
//左边去找到,填到右边的坑里
while(left < right && a[left] <= key ){
++left;
}
//左边找的大的,填到右边的坑,自己形成新的坑
a[hole] = a[left];
hole = left;
}
a[hole] = key;
return hole;
}
四、归并排序
4.1递归版
核心思想:通过递归的方式把一个大数组进行拆分,然后依次进行归并。
此思想类似于后序遍历,先整体左边有序和右边整体有序后,再进行最后一次合并即可得到升序
void MergeSort(int* a, int left, int right, int* newArr){
if(left>=right){
return;
}
int mid = left + (right-left)/2;
MergeSort(a, left, mid, newArr);
MergeSort(a, mid+1, right, newArr);
int index = left;
int begin1 = left, end1 = mid;
int begin2 = mid+1, end2 = right;
while(begin1 <= end1 && begin2 <= end2){
if(a[begin1] < a[begin2]){
newArr[index++] = a[begin1++];
}else{
newArr[index++] = a[begin2++];
}
}
while(begin1 <=end1){
newArr[index++] = a[begin1++];
}
while(begin2 <=end2){
newArr[index++] = a[begin2++];
}
for(int i=left; i<=right; ++i){
a[i] = newArr[i];
}
}
void Merge(int* a, int left, int right){
int* newArr = (int*)malloc(sizeof(int)*right+1);
MergeSort(a, left, right, newArr);
}
五、非递归排序
5.1 QuickSort非递归版
void MergeSortNoRecursion(int* a, int n ){
int* tmp = (int*)malloc(sizeof(int) * n);
int groupNum = 1;
while(groupNum<n){
//控制小组
for(int i=0; i < n; i+=2*groupNum){
int begin1 = i, end1 = i+groupNum-1; // 减去1代表的是下标
int begin2 = i+groupNum, end2 = i+groupNum*2-1;
int index = begin1;
if(begin2>=n){
begin2 = n+1;
end2 = n;
}
if(end1 >=n){
end1 = n-1;
}
if(end2>=n){
end2 = n-1;
}
while(begin1 <= end1 && begin2 <= end2){
if(begin1 < begin2){
tmp[index++] = a[begin1++];
}else{
tmp[index++] = a[begin2++];
}
}
while(begin1 <= end1){
tmp[index++] = a[begin1++];
}
while(begin2 <= end2){
tmp[index++] = a[begin2++];
}
}
//拷贝会原数组
for(int i=0; i<n; ++i){
a[i] = tmp[i];
}
groupNum = groupNum*2;
}
free(tmp);
}
5.2 MergeSort非递归版
递归程序的问题:
1. 递归的深度太深,那么导致栈溢出
2. 性能问题
因此任何一个递归程序,我们要掌握把他改成非递归
1. 循环
2. 栈+循环
void QuickSortNoRecusive(int* a, int left, int right){
ST st;
StackInit(&st);
StackPush(&st, right);
StackPush(&st, left);
while(!StackEmpty(&st)){
int start = StackTop(&st);
StackPop(&st);
int end = StackTop(&st);
StackPop(&st);
int keyi = PartSort3(a, start, end);
if(keyi+1 < right){
StackPush(&st, end);
StackPush(&st, keyi+1);
}
if(start < keyi){
StackPush(&st, keyi-1);
StackPush(&st, start);
}
}
StackDestory(&st);
}
六、计数排序
时间复杂度:O(max(N, Range))
空间复杂度:O(Range)
优点:此排序适合数据大小比较集中的数据
缺点: 只适合整数排序,浮点数/字符串就不行了
void CountSort(int* a, int n){
int min = a[0], max = a[0];
for(int i=1; i <n; i++){
if(a[i]<min){
min = a[i];
}
if(a[i]>max){
max = a[i];
}
}
int range = max - min + 1;
int* count = (int*)calloc(range, sizeof(int));
//统计次数
for(int i=0; i<n; ++i){
count[a[i] - min]++;
}
for(int i=0; i<n; ++i){
printf("%d ", count[i]);
}
//根据count数组排序
int index=0;
for(int j=0; j<range; ++j){
while(count[j]--){
a[index++] = j + min;
}
}
free(count);
}
对各种排序进行性能测试评估
// 测试排序的性能对比
void TestOP()
{
srand(time(0));
const int N = 10000;
int* a1 = (int*)malloc(sizeof(int)*N);
int* a2 = (int*)malloc(sizeof(int)*N);
int* a3 = (int*)malloc(sizeof(int)*N);
int* a4 = (int*)malloc(sizeof(int)*N);
int* a5 = (int*)malloc(sizeof(int)*N);
int* a6 = (int*)malloc(sizeof(int)*N);
int* a7 = (int*)malloc(sizeof(int)*N);
for (int i = 0; i < N; ++i)
{
a1[i] = rand();
a2[i] = a1[i];
a3[i] = a1[i];
a4[i] = a1[i];
a5[i] = a1[i];
a6[i] = a1[i];
a7[i] = a1[i];
}
int begin1 = clock();
InsertSort(a1, N);
int end1 = clock();
int begin2 = clock();
ShellSort(a2, N);
int end2 = clock();
int begin3 = clock();
SelectSort(a3, N);
int end3 = clock();
int begin4 = clock();
HeapSort(a4, N);
int end4 = clock();
int begin5 = clock();
QuickSort(a4, 0, N-1);
int end5 = clock();
int begin6 = clock();
MergeSort(a6, N);
int end6 = clock();
printf("InsertSort:%d\n", end1 - begin1);
printf("ShellSort:%d\n", end2 - begin2);
printf("SelectSort:%d\n", end3 - begin3);
printf("HeapSort:%d\n", end4 - begin4);
printf("QuickSort:%d\n", end5 - begin5);
printf("MergeSort:%d\n", end6 - begin6);
free(a1);
free(a2);
free(a3);
free(a4);
free(a5);
free(a6);
free(a7);
}
最后附上各种排序的时间与空间复杂度图一览
七、完整代码
Gitee链接🔗 🔗 🔗
👉 👉 👉 Click HERE to check some sort of different sorting 👈 👈 👈
创作不易,如果文章对你帮助的话,点赞三连哦:)