当前位置: 首页 > news >正文

【排序算法】

排序算法

  • 1.冒泡排序
  • 2.插入排序
  • 3.希尔排序
  • 4.选择排序
  • 5.快排
  • 6.归并排序
  • 7、计数排序
  • 8、各个排序算法比较情况

1.冒泡排序

Alt 冒泡排序:两两之间互换比较交换,比较一趟能确定一个数的位置。假设有n个数,则需要比较n-1趟,就能确定每一个数的位置,第一趟下标范围为[0,n-2],第二趟下标范围为[0,n-3],…则可以写成代码。

void BubbleSort(int* a,int n)
{for (int i = n-1; i > 0; i--){int flag = 1;for (int j = 0; j < i; j++){if (a[j] > a[j + 1]){flag = 0;int tmp = a[j];a[j] = a[j + 1];a[j + 1] = tmp;}}if (flag)break;}
}

alt

2.插入排序

Alt

 先写排好一个数字,假设[0,end]是一个有序数组,则下一个数用tmp保存,tmp =a[end+1],如果tmp<a[end],a[end]的数移到a[end+1],如果tmp>a[end],把tmp放到下标为end的位置。
 由此看来,end是有序数组最后一个数的下标,tmp保存的是下一个要放的数,一个无序数组,则end先指向第一个数,tmp保存第二个数,根据上述获得这两个数的正确排序,则end = n-2,tmp = a[end-1]时,进行最后一次排序,整个数组就排好序了。

//O(N) = O(N^2)
void InsertSort(int* a,int n)
{////[0,end] end+1for (int i = 0; i < n-1; i++){int end = i;int tmp = a[end + 1];while (end >= 0){if (a[end] > tmp){a[end + 1] = a[end];end--;}else{break;}}a[end + 1] = tmp;}
}

直接插入排序的特性总结:
1. 元素集合越接近有序,直接插入排序算法的时间效率越高
2. 时间复杂度:O(N^2)
3. 空间复杂度:O(1),它是一种稳定的排序算法
4. 稳定性:稳定

3.希尔排序

下面分的间隔gap=3,分成3组。
alt

 希尔排序在插入排序的基础上升级了一点,把一个无序数据按gap(间隔)分成gap个数组,对这gap个数组分别进行插入排序,先把gap的值取大一点,把一个组小的数排到前面,大的数排到后面,gap进行不断变化取值,当gap = 1时,就是插入排序,排序完成。

 先进行排第一个组排序如下。。

//[0,end]有序  tmp保存下一个数int gap = 3;  //把一个数组分成3个数组for(int i = 0;i<n-gap;i+=gap){int end= i;int tmp = a[end+gap];while(end>=0){if(tmp<a[end]){a[end+gap] = a[end]; end -= gap;}else{break;}}a[end+gap]= tmp;}

 对gap组数组进行排序,并且gap取值变化,当gap = 1时,完成排序。

void ShellSort(int*a ,int n)
{int gap = n;while(gap>0){//[0,end]有序  tmp保存下一个数gap /= 2;  //把一个数组分成3个数组for(int j = 0;j<gap;j++){for(int i = j;i<n-gap;i+=gap){int end= i;int tmp = a[end+gap];while(end>=0){if(tmp<a[end]){a[end+gap] = a[end]; end -= gap;}else{break;}}a[end+gap]= tmp;}}}
}

 时间复杂度:O(N) = N^(1.3)
 大致分析如下,不会算!
Alt
 按照gap = n,gap = gap/3来大致计算时间复杂度。
 第一次分组,gap = n/3,则把n个数据排成n/3个数组,每个数组的间隔为gap,每个数组的大小为3个数据,每个数组排序最坏情况移动3次,则移动次数为:3*(n/3) = n次。

… … … … …

最后一次,gap = 1,数组经过多轮预排之后,已经非常接近有序,大致移动n次就可以使数组有序。

4.选择排序

 先确定区间[begin,end],然后再这个区间找最大值,最小值,进行交换,begin++,end- -,缩小区间。

void swap(int*px,int*py)
{int tmp =*px;*px = *py;*py = tmp;
}
void SelectSort(int*a,int n)
{int begin = 0, end = n - 1;while (begin < end){int mini = begin, maxi = begin;for (int i = begin + 1; i <= end; i++){if (a[i] < a[mini]){mini = i;}if (a[i] > a[maxi]){maxi = i;}}swap(&a[begin], &a[mini]);if (maxi == begin){maxi = mini;}swap(&a[end], &a[maxi]);begin++;end--;}}

5.快排

 hoare版本。
 思路:快速排序是Hoare于1962年提出的一种二叉树结构的交换排序方法,其基本思想为:任取待排序元素序列中的某元素作为基准值,按照该排序码将待排序集合分割成两子序列,左子序列中所有元素均小于基准值,右子序列中所有元素均大于基准值,然后最左右子序列重复该过程,直到所有元素都排列在相应位置上为止。

Alt

 取key永远为子序列最左边的一个数,定义left指向子序列最左边,right指向子序列最右边,先走最右边,找比key小的值,在走左边,找比keyi大的值,找到之后,两者交换,交换之后,比key小的在左边,比key大的在右边。left与right相遇则结束,然后key与相遇位置的数交换,key已经放到它应有的位置,分割区间[begin,keyi-1][keyi][keyi+1,end],进行递归遍历,放好每一个数。

 具体过程如下:
Alt

 通过写这个算法,明白还是提高代码组织能力,一个算法有了思路,怎么用代码实现的问题。

void QuickSort(int* a, int left,int right)
{if (left >= right)return;int begin = left;int end = right;int keyi = left;while (left < right){//先走右边,右边找比a[key]小的while (left < right && a[keyi] >= a[right]){right--;}//在走左边,左边找比a[key]大的while (left < right && a[keyi] <= a[left]){left++;}swap(&a[left], &a[right]);}swap(&a[keyi], &a[left]);keyi = left;//[begin,keyi-1]keyi[keyi+1,end]QuickSort(a, begin,keyi-1);QuickSort(a, keyi+1,end);
}

 理解为啥相遇位置的值比key小?
alt
 时间复杂度分析:
Alt

 时间复杂度一般情况下O(N) = N* log ⁡ 2 ( N ) \log_{2}(N) log2(N)
 最坏情况下:为有序,O(N) = N^2.


 快排优化,快排时间复杂度高的原因是数据有序,第一个元素是最小的。
第一种方式随机数选keyi,使得第一个数不是最小的(升序)。具体如下。

 int randi = rand()%(right-left+1)+left;
 swap(&a[left],&a[randi]);
 其实上面这种优化,我觉得还是不靠谱,你怎么能保证你随机选的keyi就不是最小值呢?

void QuickSort(int*a ,int left,int right)
{if(left>=right)return;int begin = left,end = right;//随机数选keyiint randi = rand()%(right-left+1)+left;Swap(&a[left],&a[randi]);int keyi = left;while(left<right){//先走右边,找比a[keyi]小的值while(left<right && a[keyi]<=a[right]){right--;}//在走左边,找比a[keyi]大的值while(left<right && a[keyi]>=a[left]){left++;}Swap(&a[left],&a[right]);}Swap(&a[keyi],&a[left]);keyi = left;//[begin,keyi-1] keyi [keyi+1,end]QuickSort(a,begin,keyi-1);QuickSort(a,keyi+1,end);
}

 第二种优化方式三数取中放keyi位置,这个方法比上面好。定义int GetMid(int *a,int left,int right)取每个区间的最左边值和最右边值,中间值,算出大小在中间的值做keyi。

//三数取中,left,mid,right
//大小居中的值,也就是不是最大也不是最小的
int  GetMidi(int* a, int left, int right)
{int midi = (left + right) / 2;if (a[left] < a[midi]){if (a[midi] < a[right]){return midi;}else if (a[left] > a[right]) //a[midi]>a[right]{return left;}else{return right;}}else  //a[left]>a[midi]{if (a[midi]>a[right]){return midi;}else if (a[left] < a[right])  //a[midi]<a[right]{return left;}else{return right;}}
}void QuickSort(int*a ,int left,int right)
{if(left>=right)return;int begin = left,end = right;//随机数选keyi
//	int randi = rand()%(right-left+1)+left;
//	Swap(&a[left],&a[randi]);int midi = GetMidi(a,left,right);Swap(&a[left],&a[midi]);int keyi = left;while(left<right){//先走右边,找比a[keyi]小的值while(left<right && a[keyi]<=a[right]){right--;}//在走左边,找比a[keyi]大的值while(left<right && a[keyi]>=a[left]){left++;}Swap(&a[left],&a[right]);}Swap(&a[keyi],&a[left]);keyi = left;//[begin,keyi-1] keyi [keyi+1,end]QuickSort(a,begin,keyi-1);QuickSort(a,keyi+1,end);
}

 第三种小区间优化,剩余10个数直接进行插入排序。这种写法递归下去之后在<10个数的区间,直接进行插入排序,不需要返回条件,程序执行完自动返回。

void QuickSort(int*a ,int left,int right)
{if(right-left+1<10){InsertSort(a+left,right-left+1);}else{int begin = left,end = right;int keyi = left;while(left<right){//先走右边,找比a[keyi]小的值while(left<right && a[keyi]<=a[right]){right--;}//在走左边,找比a[keyi]大的值while(left<right && a[keyi]>=a[left]){left++;}Swap(&a[left],&a[right]);}Swap(&a[keyi],&a[left]);keyi = left;//[begin,keyi-1] keyi [keyi+1,end]QuickSort(a,begin,keyi-1);QuickSort(a,keyi+1,end);}
}

 第二种写快排的方法:前后指针法。
在这里插入图片描述
alt
 定义两个指针,prev指向第一个数,cur指向第二个数,如果a[cur]<key,则prev++,然后prev与cur交换,在cur++,如果a[cur]>=key,则cur++,最后cur++出界,循环结束,prev的位置就是key值的位置,至于为什么这么操作,就把key值位置找到了,我也不知道。

void QuickSort(int*a,int left,int right)
{if(left>=right)return;int prev = left;int cur = left+1;int keyi =left;while(cur<=right){if(a[cur]<a[keyi]){++prev;Swap(&a[cur],&a[prev]);++cur;}else{++cur;}}Swap(&a[keyi],&a[prev]);keyi = prev;//[left,keyi-1]keyi[keyi+1,righgt]QuickSort(a,left,keyi-1);QuickSort(a,keyi+1,right);
}

 模拟递归过程,使用栈来实现快排。
Alt

 模拟递归过程,写快排的关键是搞区间,区间确定了,搞一个单趟排就可以了,先把第一个区间[lefe,right]入栈,再走一个单趟排,则一个数字位置已确定,并且分割出两个区间[begin,keyi-1]keyi[keyi+1,end],两个区间在走个单趟,依次循环。

void QuickSortNonR(int*a,int left,int right)
{ST st;STInit(&st);STPush(&st,right);STPush(&st,left);while(!STEmpty(&st)){int begin = STTop(&st);STPop(&st);int end = STTop(&st);STPop(&st);//先把一趟排好int prev = begin;int cur = begin+1;int keyi =begin;while(cur<=right){if(a[cur]<a[keyi]){++prev;Swap(&a[cur],&a[prev]);++cur;}else{++cur;}}Swap(&a[keyi],&a[prev]);keyi = prev;//[begin,keyi-1] keyi [keyi+1,end]if(keyi+1<end){STPush(&st,end);STPush(&st,keyi+1);}if(begin<keyi-1){STPush(&st,keyi-1);STPush(&st,begin);}}STDestory(&st);
}

6.归并排序

 基本思想:归并排序(MERGE-SORT)是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide andConquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。 归并排序核心步骤:
alt
 思路:归并排序是走的二叉树的后序,而快排是走的二叉树的前序,归并排序是先把左子树、右子树、遍历到底部,只剩下一个数,返回时,进行比较,进而进行归并。
Alt

void _MergeSort(int* a, int begin, int end, int* tmp)
{if (begin == end)return;//后序来搞,分区间int mid = (begin + end) / 2;//[begin,mid][mid+1,end]printf("[%d,%d]-[%d,%d]\n", begin, mid, mid + 1, end);_MergeSort(a, begin, mid, tmp);_MergeSort(a, mid + 1, end, tmp);//大小处理,两个区间处理排序int begin1 = begin, end1 = mid;int begin2 = mid + 1, end2 = end;int i = begin1;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));PrintArray(a + begin, end - begin + 1);
}
void MergeSort(int* a, int n)
{int* tmp = (int*)malloc(sizeof(int) * n);if (tmp == NULL){perror("malloc fail");return;}_MergeSort(a, 0, n - 1, tmp);
}

 时间复杂度:O(N) = N* log ⁡ 2 ( N ) \log_{2}(N) log2(N)
归并排序循环实现
 一点点写这个算法,慢慢来感受怎么组织程序。
alt

 怎么弄呢?递归实现归并排序,用的是后序,那么通过循环实现归并,则要从正面出发来搞这个排序。
 首先把数组中的每一个数据看作是一个数组,也就是分割,gap = 1来分割,分割之后,两个数组之间进行排序,每两个一组排好序,gap = 2,再次排序。直到完成这个排序。
 先写第一次分割的两个数组的排序

int begin1 = 0, end1 = begin1 + gap - 1;
int begin2 = end1 + 1, end2 = begin2 + gap - 1;
int i = begin1;
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, tmp , sizeof(int) * (end2 + 1));

 在写一次全部分割的排序

for (int j = 0; j < n; j += gap * 2)
{int begin1 = j, end1 = begin1 + gap - 1;int begin2 = end1 + 1, end2 = begin2 + gap - 1;int i = begin1;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, sizeof(int) * (end2 - j + 1));
}

 整体来写

void MergeSortNonR(int* a, int n)
{int* tmp = (int*)malloc(sizeof(int) * n);if (tmp == NULL){perror("malloc fail");return;}int gap = 1;while (gap < n){for (int j = 0; j < n; j += gap * 2){int begin1 = j, end1 = begin1 + gap - 1;int begin2 = end1 + 1, end2 = begin2 + gap - 1;int i = begin1;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, sizeof(int) * (end2 - j + 1));}gap *= 2;}free(tmp);tmp = NULL;}

 边界处理
//特殊情况矫正
//1.如果begin1越界,不用管,前面已经配对成对。
//2.如果end1越界,[begin1,end1),后面没有配对的另一个数组,不用归并
if(end1>=n)
break;
//3.如果begin2越界,[begin1,end1]已经排好序,不用归并,end1后面就没有归并的数组
if(begin1>=n)
break;
//4.如果end2越界,[begin2,end2)还有数据,一定要归并,只不过弄好数据范围
if(end2>=n)
end2 = n-1;

void MergeSortNonR(int*a,int n)
{int* tmp = (int*)malloc(sizeof(int)*n);if(tmp == NULL){perror("malloc fail");return ;}int gap = 1;while(gap<n){for(int j = 0;j<n;j += gap*2){int begin1 = j,end1 =begin1+gap-1;int begin2 =end1+1,end2 = begin2+gap-1;int i = begin1;//特殊情况矫正//1.如果begin1越界,不用管,前面已经配对成对。//2.如果end1越界,[begin1,end1),后面没有配对的另一个数组,不用归并if(end1>=n)break;//3.如果begin2越界,[begin1,end1]已经排好序,不用归并,end1后面就没有归并的数组if(begin1>=n)break;//4.如果end2越界,[begin2,end2)还有数据,一定要归并,只不过弄好数据范围if(end2>=n)end2 = n-1;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,sizeof(int)*(end2-j+1));}gap *= 2;}free(tmp);tmp  = NULL;}

7、计数排序

算法思想:用相对映射来搞
alt

//时间复杂度:O(N+range)
//空间复杂度:O(range)
void CountSort(int* a, int n)
{int max = a[0], min = a[0];for (int 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* count = (int*)malloc(sizeof(int) * (range));if (count == NULL){perror("malloc fail");return;}memset(count, 0, sizeof(int) * (range));//统计次数,相对映射for (int i = 0; i < n; i++){count[a[i] - min]++;}//排序int j = 0;for (int i = 0; i <(range); i++){while(count[i] > 0){a[j++] = min + i;count[i]--;}}free(count);count = NULL;
}

8、各个排序算法比较情况

Alt


 完结!




http://www.dtcms.com/a/266215.html

相关文章:

  • Vue3封装动态Form表单
  • 第二章-AIGC入门-开启AIGC音频探索之旅:从入门到实践(6/36)
  • 【学术写作+AI实战】株洲高校科研写作研修班全纪实:核心期刊编辑与AI专家的联合授课笔记
  • Web前端数据可视化:ECharts高效数据展示完全指南
  • 【JavaEE】计算机工作原理
  • JavaEE初阶第七期:解锁多线程,从 “单车道” 到 “高速公路” 的编程升级(五)
  • 运维打铁:企业云服务解决方案
  • openEuler 24.03 全流程实战:用 Ansible 5 分钟部署分布式 MinIO 高可用集群
  • Django+DRF 实战:从异常捕获到自定义错误信息
  • 深度分析:Microsoft .NET Framework System.Random 的 C++ 复刻实现
  • 切出idea窗口自动编译,关闭idea自动编译
  • WPF+HelixToolkit打造炫酷自定义3D贴图立方体盒子模型
  • 机器学习在智能供应链中的应用:需求预测与物流优化
  • Java技术深潜:从并发陷阱到云原生突围
  • web网页,在线%电商,茶叶,商城,网上商城系统%分析系统demo,于vscode,vue,java,jdk,springboot,mysql数据库
  • 警惕 Rust 字符串的性能陷阱:`chars().nth()` 的深坑与高效之道
  • 「AI产业」| 《中国信通院华为:智能体技术和应用研究报告》
  • P1202 [USACO1.1] 黑色星期五Friday the Thirteenth
  • Ubuntu Linux Cursor 安装与使用一
  • 成功解决运行:Django框架提示:no such table: django_session
  • 基于探索C++特殊容器类型:容器适配器+底层实现原理
  • 如何通过注解(@Component 等)声明一个 Bean?Spring 是如何找到这些注解的?
  • java微服务(Springboot篇)——————IDEA搭建第一个Springboot入门项目
  • 【基础算法】贪心 (二) :推公式
  • 封装一个png的编码解码操作
  • 译码器Multisim电路仿真汇总——硬件工程师笔记
  • 嵌入式系统中实现串口重定向
  • 【模糊集合】示例
  • 【MySQL\Oracle\PostgreSQL】迁移到openGauss数据出现的问题解决方案
  • Qt Creator自定义控件开发流程