排序(详解)
创始人
2024-01-28 14:56:29

排序

  • 排序的概念
  • 常见的排序算法
    • 直接插入排序
    • 希尔排序(缩小增量排序)
    • 选择排序
    • 堆排序
    • 冒泡排序
    • 快排(递归)
      • 霍尔版本
      • 挖坑法
      • 前后指针法
    • 快排(非递归)
    • 归并排序(递归)
    • 归并排序(非递归)
    • 计数排序

排序的概念

排序:将杂乱无章的数据元素,通过一定的方法按关键字顺序排列的过程叫做排序

内部排序:数据全部放在内存中的排序
外部排序:由于数据太多不能全部放在内存中,需要借助外存的排序

常见的排序算法

比较排序
插入排序:直接插入排序,希尔排序
选择排序:直接选择排序,堆排序
交换排序:冒泡排序,快速排序
归并排序:归并排序

非比较排序

计数排序

直接插入排序

思路:
直接插入排序是指在待排序的元素中,假设前面n-1(其中n>=2)个数已经是排好顺序的,现将第n个数插到前面已经排好的序列中,然后找到合适自己的位置,使得插入第n个数的这个序列也是排好顺序的

算法实现

void Insertsort(int* a, int n)
{int i = 0;for (i = 0; i < n - 1; i++){//[0,end],在end+1位置上插入一个数,使[0,end+1]有序int end = i;int tmp = a[end + 1];while (end >= 0){if (tmp < a[end]){a[end + 1] = a[end];end--;}else{break;}}//跳出循环有两种可能//1.end已经越界,此时end==-1//2.找到比tmp小的数据a[end + 1] = tmp;}
}

图形展示
在这里插入图片描述

运行结果

在这里插入图片描述
直接插入排序的特点

  1. 元素集合越接近有序,插入排序的时间效率便越高
  2. 时间复杂度:O(N^2)
  3. 空间复杂度:O(1)
  4. 稳定性:稳定

希尔排序(缩小增量排序)

思路:
先选定一个整数,把待排序的数据按一定距离分成若干小组,对每一组内的数据进行排序;然后逐渐减小距离,重复上面的操作,直到距离为1所有数据被分到一组,结束排序

图形展示

在这里插入图片描述

算法实现

void Shellsort(int* a, int n)
{int gap = n;while (gap > 0){gap /= 2;int i = 0;//gap组数据依次多组并排for (i = 0; i < n - gap; i++){int end = i;//[0,end],在end+gap位置上插入一个数据使[0,end+gap]有序int tmp = a[end + gap];while (end >= 0){if (a[end] > tmp){a[end + gap] = a[end];end -= gap;}else{break;}}a[end + gap] = tmp;}}
}

运行结果

在这里插入图片描述

希尔排序的特点

  1. 希尔排序是对插入排序的优化
  2. 当gap>1是称为预排序,目的便是使数组接近有序;gap越大,大的数据可以越快跳到后面,小的数据可以越快跳到前面;gap越小,数据跳的越慢,但也越接近有序;当gap==1时,数组已经接近有序,排序就更加的快
  3. 由于gap的取值没有统一的规定,便导致时间复杂度不易计算
  4. 稳定性:不稳定

选择排序

思路:
每次从待排序的数据中选出最小的和最大的数据,若这两个数据不是这组数据的第一个,最后一个数据,则将这两个数据与第一个,最后一个数据进行交换;然后再从剩余的数据中重复此操作,直到排完所有数据

在这里插入图片描述

算法实现

void Swap(int* p1, int* p2)
{int tmp = *p1;*p1 = *p2;*p2 = tmp;
}void Selectsort(int* a, int n)
{int left = 0;int right = n - 1;while (right > left){int min = left;int max = left;int i = 1;for (i = left+1; i <= right; i++){if (a[i] > a[max]){max = i;}if (a[i] < a[min]){min = i;}}Swap(&a[left], &a[min]);//如果最大的数据在第一个位置,需要进行调整if (max == left){max = min;}Swap(&a[right],&a[max]);left++;right--;}
}

在这里插入图片描述
选择排序的特点

  1. 效率不高
  2. 时间复杂度:O(N^2)
  3. 空间复杂度:O(1)
  4. 稳定性:不稳定

堆排序

在上一篇博客中有详细介绍堆排序,这里就不再赘述需要注意的是
升序:建大堆
降序:建小堆

算法实现

void Adjustdown(int* a, int n, int i)
{int minchild = 2 * i + 1;while (minchild < n){if (minchild + 1 < n && a[minchild + 1] > a[minchild]){minchild++;}if (a[minchild] > a[i]){Swap(&a[minchild], &a[i]);i = minchild;minchild = 2 * i + 1;}else{break;}}
}void Heapsort(int* a, int n)
{int i = 0;for (i = (n - 1 - 1) / 2; i >= 0; i--){//向下调整Adjustdown(a, n, i);}i = 1;while (i < n){Swap(&a[0], &a[n - i]);Adjustdown(a, n - i, 0);i++;}
}

堆排序的特点

  1. 效率很高
  2. 时间复杂度:O(N*logN)
  3. 空间复杂度:O(1)
  4. 稳定性:不稳定

冒泡排序

思路:
它重复地走访过要排序的元素列,依次比较两个相邻的元素,如果顺序(后一个数大于前一个数)错误就把他们交换过来。走访元素的工作是重复地进行,直到没有相邻元素需要交换,也就是说该元素列已经排序完成

就类似气泡一样,越往后越大

图形展示
在这里插入图片描述

算法实现

void Bubblesort(int* a, int n)
{int j = 0;for (j = 1; j <= n; j++){int i = 0;int exchange = 1;for (i = 0; i < n - j; i++){if (a[i] > a[i+1]){Swap(&a[i], &a[i + 1]);}else{exchange = 0;}}if (exchange == 1){break;}}
}

冒泡排序的特点

  1. 非常容易理解
  2. 时间复杂度:O(N^2)
  3. 空间复杂度:O(1)
  4. 稳定性:稳定

快排(递归)

思路:
任取待排序元素序列中的某个元素作为基准值,按照该基准值,将待排序的元素分为两个子序列,左子序列的元素全部小于基准值,右子序列的元素全部大于基准值;然后左右子序列重复此操作,直到所有将所有元素排序完 右边找小,左边找大

将整个序列按照基准值划分为左右两部分的常见方式有三种:霍尔版本挖坑法前后指针法

单趟排序

  1. 选择基准值key(一般默认选择第一个数)
  2. 单趟排序的结果,小的数在key左边,大的数在key右边

霍尔版本

在这里插入图片描述

当第一个数为key时,为保证相遇位置的值一定比key小,right先走

  1. R找到比key小的数停下,L撞到R相遇
  2. L找到比key大的数与R找到比key小的数进行交换,然后停下,R撞到L相遇

每次基准值确定之后,就代表这个基准值的位置在整个排序中位置就确定好了,不需要再进行变动;接下来只需要在基准值的两边重复此操作,直到两边都只剩余一个元素,便说明排序完成

在这里插入图片描述

int Partsort1(int* a, int left, int right)
{int keyi = left;while (right > left){while (right>left && a[right] >= a[keyi]){right--;}while (right>left && a[left] <= a[keyi]){left++;}Swap(&a[right], &a[left]);}int meet = right;Swap(&a[meet], &a[keyi]);return meet;
}void Quicksort(int* a, int left, int right)
{if (right <= left){return;}//[left,mid-1] mid [mid+1,right]int mid = Partsort1(a, left, right);Quicksort(a, left, mid - 1);Quicksort(a, mid + 1, right);
}

完成第一趟排序之后,在进行右子序列排序时,发现第一个数(基准值)相较于其他的数还是偏大的,一般情况下基准值都是选择中间值,所以需要对于选基准值进行优化

三数取中

//三数取中
int Getmidindex(int* a, int left, int right)
{int mid = left + (right - left) / 2;if (a[right] > a[mid]){if (a[mid] > a[left]){return mid;}//   a[mid]<=a[left]else if (a[right] > a[left]){return left;}//    a[right]<=a[left]else{return right;}}else{if (a[right] > a[left]){return right;}//    a[right]<=a[left]else if (a[mid] > a[left]){return left;}//    a[mid]<=a[left]else{return mid;}}
}

优化后的结果

在这里插入图片描述

挖坑法

将第一个数据令为基准值,并将存放在临时变量中,形成一个坑位

第一次单趟排序
在这里插入图片描述

剩余的排序

在这里插入图片描述

算法实现

int Partsort2(int* a, int left, int right)
{//三数取中int key = Getmidindex(a, left, right);Swap(&a[left], &a[key]);int hole = left;while (right > left){//右边找小   填到左边的坑while (right > left && a[right] >= key){right--;}a[hole] = a[right];hole = right;//左边找大   填到右边的坑while (right > left && a[left] <= key){left++;}a[hole] = a[left];hole = left;}a[hole] = key;return hole;
}void Quicksort(int* a, int left, int right)
{if (right <= left){return;}int mid = Partsort2(a, left, right);Quicksort(a, left, mid - 1);Quicksort(a, mid + 1, right);
}

前后指针法

初始化时,prev指针指向序列的开头,cur指针指向prev指针的后一个位置

cur指针找小于key的数;prev指针要么紧跟着cur指针要么停留在比key大的数前面
cur指针遇到比key小的值时,便会停下来,++prev,交换prev和cur位置的值

在这里插入图片描述
剩余的排序
在这里插入图片描述

算法实现

int Partsort3(int* a, int left, int right)
{int key = Getmidindex(a, left, right);Swap(&a[left], &a[key]);int prev = left;int cur = left + 1;while (cur <= right){if (a[cur] < a[key] && ++prev != cur){Swap(&a[cur], &a[prev]);}++cur;}Swap(&a[prev], &a[key]);return prev;
}void Quicksort(int* a, int left, int right)
{if (right <= left){return;}int mid = Partsort3(a, left, right);Quicksort(a, left, mid - 1);Quicksort(a, mid + 1, right);
}

快排(非递归)

在这里插入图片描述

在这里插入图片描述

算法实现

void Quicksortnon(int* a, int begin, int end)
{SK sk;SKinit(&sk);SKpush(&sk, begin);SKpush(&sk, end);while (!SKempty(&sk)){int right = SKtop(&sk);SKpop(&sk);int left = SKtop(&sk);SKpop(&sk);int key = Partsort3(a, left, right);if (key + 1 < right){//左边先入栈SKpush(&sk, key + 1);SKpush(&sk, right);}if (left < key - 1){//右边后入栈SKpush(&sk, left);SKpush(&sk, key - 1);}}
}

快排的特点

  1. 综合性能较好,适应场景较多
  2. 时间复杂度:O(N*logN)
  3. 空间复杂度:O(logN)
  4. 稳定性:不稳定

归并排序(递归)

思路:将已有序的子序列合并,得到完全有序的序列;若子序列未有序,先使子序列有序,再进行合并(符合分治法的思想)

在这里插入图片描述

算法实现

void mergesort(int* a, int begin, int end, int* tmp)
{//递归结束条件if (begin >= end){return;}//将数组平分int mid = begin + (end - begin) / 2;//进行递归mergesort(a, begin, mid, tmp);mergesort(a, mid+1, end, tmp);//归并  取小的尾插int begin1 = begin;int end1 = mid;int begin2 = mid + 1;int end2 = end;int i = begin;while (begin1 <= end1 && begin2 <= end2){if (a[begin1] <= a[begin2]){tmp[i++] = a[begin1++];}else{tmp[i++] = a[begin2++];}}//由于两部分不可能同时结束,所以还需要对剩余的数据进行插入while (begin1 <= end1){tmp[i++] = a[begin1++];}while (begin2 <= end2){tmp[i++] = a[begin2++];}//按照相应位置进行拷贝回原数组memcpy(a+begin, tmp+begin, sizeof(int) * (end - begin + 1));
}void Mergesort(int* a, int n)
{int* tmp = (int*)malloc(n * sizeof(int));if (tmp == NULL){perror("malloc fail");return;}int begin = 0;int end = n - 1;//如果直接在归并函数中递归,每次都会开辟空间,导致程序崩溃//所以创建子函数进行归并mergesort(a, begin, end, tmp);free(tmp);tmp = NULL;
}

归并排序(非递归)

在这里插入图片描述

算法实现

void Mergesortnon(int* a, int n)
{//开辟空间,用于排序int* tmp = (int*)malloc(n * sizeof(int));if (tmp == NULL){perror("malloc fail");return;}int gap = 1;while (gap < n){int j = 0;//每组gap个数据  for (j = 0; j < n; j += 2 * gap){//取小尾插,进行归并int begin1 = j;int end1 = j + gap - 1;int begin2 = j + gap;int end2 = j + 2 * gap - 1;int i = j;while (begin1 <= end1 && begin2 <= end2){if (a[begin1] <= a[begin2]){tmp[i++] = a[begin1++];}else{tmp[i++] = a[begin2++];}}//由于两部分不可能同时结束,所以还需要对剩余的数据进行插入while (begin1 <= end1){tmp[i++] = a[begin1++];}while (begin2 <= end2){tmp[i++] = a[begin2++];}//排序之后,按照位置进行拷贝回原数组memcpy(a+j, tmp+j, (end2 - j + 1) * sizeof(int));}gap *= 2;}free(tmp);tmp = NULL;
}

图示分析似乎没有什么问题,但如果再向数组中加一个数,结果会怎么样呢?

在这里插入图片描述

如果再向数组中加入一个数,结果又是怎么样的呢?

在这里插入图片描述

目前出现的这两种情景,上面都没有考虑到,那又该如何解决呢?
先将每次分组的数据下标打印出来,再进行分析

在这里插入图片描述

总结下来越界有三种可能

  1. 前一组的end1越界
  2. 后一组全部越界
  3. 后一组end2越界

解决办法

  1. 直接跳出循环,剩余的数据不再临时开辟的空间中进行排序
  2. 直接跳出循环,剩余的数据不再临时开辟的空间中进行排序
  3. 修改end2的数值,end2=n-1 再进行排序

优化后

void Mergesortnon(int* a, int n)
{//开辟空间,用于排序int* tmp = (int*)malloc(n * sizeof(int));if (tmp == NULL){perror("malloc fail");return;}int gap = 1;while (gap < n){int j = 0;//每组gap个数据  for (j = 0; j < n; j += 2 * gap){//取小尾插,进行归并int begin1 = j;int end1 = j + gap - 1;int begin2 = j + gap;int end2 = j + 2 * gap - 1;int i = j;//第一组end1越界if (end1 >= n){printf("[%d,%d]",begin1,end1);break;}//第二组全部越界if (begin2 >= n){printf("[%d,%d]",begin1,end1);break;}//第二组部分越界if (end2 >= n){//修改end2,继续归并end2 = n - 1;}printf("[%d,%d][%d,%d]  ", begin1, end1, begin2, end2);while (begin1 <= end1 && begin2 <= end2){if (a[begin1] <= a[begin2]){tmp[i++] = a[begin1++];}else{tmp[i++] = a[begin2++];}}//由于两部分不可能同时结束,所以还需要对剩余的数据进行插入while (begin1 <= end1){tmp[i++] = a[begin1++];}while (begin2 <= end2){tmp[i++] = a[begin2++];}//排序之后,按照位置进行拷贝回原数组memcpy(a+j, tmp+j, (end2 - j + 1) * sizeof(int));}gap *= 2;printf("\n");}free(tmp);tmp = NULL;
}

在这里插入图片描述

归并排序的特点

  1. 时间复杂度:O(N*logN)
  2. 空间复杂度:O(N)
  3. 稳定性:稳定

计数排序

思路:
先统计相同元素出现的次数,然后开辟相对大小的空间(最大值-最小值+1),将相同元素按照对应的下标中赋以其出现的次数

用于计数的数组的下标时其对于的值(相对)的个数,某个值出现几次,便会被计数几次
在这里插入图片描述

算法实现

void Countsort(int* a, int n)
{int max = a[0];int min = a[0];int i = 0;for (i = 0; i < n; i++){if (a[i] > max){max = a[i];}if (a[i] < min){min = a[i];}}int range = max - min + 1;//统计次数int* tmp = (int*)malloc(sizeof(int) * range);memset(tmp, 0, sizeof(int) * range);for (i = 0; i < n; i++){tmp[a[i] - min]++;}//排序int j = 0;for (i = 0; i < range; i++){while (tmp[i]--){a[j] = i + min;j++;}}free(tmp);tmp = NULL;
}

计数排序的特点

  1. 当数据范围比较集中时,效率很高
  2. 时间复杂度:O(N,range)
  3. 空间复杂度:O(range)
  4. 稳定性:稳定

相关内容

热门资讯

埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
不用电脑怎么设置苹果铃声 ip... 需要一个app,加上苹果自带的app即可1、手机下载爱思极速版。因为之前换手机搬移数据时店员帮忙整理...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...
应用未安装解决办法 平板应用未... ---IT小技术,每天Get一个小技能!一、前言描述苹果IPad2居然不能安装怎么办?与此IPad不...
长白山自助游攻略 吉林长白山游... 昨天介绍了西坡的景点详细请看链接:一个人的旅行,据说能看到长白山天池全凭运气,您的运气如何?今日介绍...
脚上的穴位图 脚面经络图对应的... 人体穴位作用图解大全更清晰直观的标注了各个人体穴位的作用,包括头部穴位图、胸部穴位图、背部穴位图、胳...
世界上最漂亮的人 世界上最漂亮... 此前在某网上,选出了全球265万颜值姣好的女性。从这些数量庞大的女性群体中,人们投票选出了心目中最美...
猫咪吃了塑料袋怎么办 猫咪误食... 你知道吗?塑料袋放久了会长猫哦!要说猫咪对塑料袋的喜爱程度完完全全可以媲美纸箱家里只要一有塑料袋的响...
埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
才高八斗的主人公 三顾茅庐的主... 古代君王在建立自己的政治体系之时,大多会采取一些礼贤下士的手段,对于一些有卓越才能的人放低姿态。因为...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...