排序大乱炖

目录

一:插入排序

1.1直接插入排序

1.2希尔排序

二:选择排序

2.1选择排序

2.2堆排序

三:交换排序

3.1冒泡排序

3.2快速排序 

3.2.1Hoare版本

3.2.2双指针法 

3.2.3非递归 


一:插入排序

1.1直接插入排序

直接插入排序,可以有很多种写法,写法也比较简单,在这里,我主要介绍一种和希尔排序十分相似的思想,方便后续讲解希尔排序

在这里我们定义一个变量end,它用来记录下标,在这里我们认为[0,end]范围内的数组是有序的,然后将下标end+1所在的数组,与[0,end]范围内的数组比大小(所有排序讲解的均为升序),放在合适的位置 。

如果,a[end] > a[end+1],我们即将a[end]的数字,放在end+1处,这是又会产生一个问题,我们是每次比较都要两个数字交换,还是让a[end]直接将a[end+1]覆盖呢?

直接覆盖肯定是首先就排除的,因为覆盖会使数据缺失,那就选每次比价,每次交换吗?

但是,如果数组长度很长,可能依次交换,并不能让a[end+1]放在合适的位置,这样看每次交换可能不如直接覆盖来的快,这时我们可以取一个折中的方法:在新建一个变量tmp,将a[end+1]的值存在tmp,让后直接覆盖就可以了,但是tmp不着急让在end处,我们将继续比较,找到正确的位置直接将tmp放入即可

由上图,可以写出代码:

//插入排序
//直接插入排序
void InsertSort(int* a, int n)
{for (int i = 0; i < n - 1; i++){//单趟,[0,end]为有序数组,将a[end+1]插入数组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希尔排序

希尔排序通过名字可以看出是一个名叫希尔的大佬创造的方法,而当时这位大佬在写这个代码的时候正是想通过自己的方法,改良直接插入排序,所以希尔排序的思想和上面的直接插入排序很相似。

希尔排序和直接插入排序相比所做出的优化就是,先进行预排序,让数组十分接近有序,在进行一次插入排序即可。

预排序,就是将间隔gap的数分为一组,在这一小组中先进行排序,排完这几个小组后,再将gap变小,继续进行进行预排序,随着gap的减少,每一小组的成员不断增加,知道gap减少到1,也就是直接插入排序,之后数组有序

控制一组颜色排序:

    for (int i = 0; i < n - gap; i+= gap){int end = i;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;}

排完一种颜色,再排下一组颜色,知道所有组排完,一趟排序结束

for(int j = 0; j< gap; j++)
{for (int i = j; i < n - gap; i++){int end = i;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;}
}

此时此刻,我们排完一趟排序,已经用了三个循环,这样想,你是否觉得时间复杂度已经爆了呢?但是在数据十分庞大时,希尔排序也即是说比快排慢一点而已,所以大佬之所被称为大佬不是没有原因的。各位,请继续往下看,静等希尔排序的奇迹!

其实,前面排完一趟是,希尔当时的原版本,在这之后还有大佬,对这一段代码,进行了优化,时间复杂度并没有变化,只是代码更简洁一点用了两个循环。

思路,排每一组的第一个,然后第一个都排完以后,再同时排第二个

    for (int i = 0; i < n - gap; i++){int end = i;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;}

排完一趟以后,在最外面,再套上一层循环控制gap即可

//希尔排序
//预排序:接近有序
//直接排序:gap == 1,插入排序
void ShellSort(int* a, int n)
{int gap = n;while (gap > 1){ gap = gap / 3 + 1;//最后一次是1//先排每一组的第一个,排完以后依次类推for (int i = 0; i < n - gap; i++){int end = i;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;}}}

对于gap的取值,希尔的初始版本是gap /= 2,这样就可以保证gap最后一次是1

后来,又有人经过大量实验数据得出,gap模3时,效率最高,但是/3的最后结果可能是0,所以为了保证最后一次是0,gap = gap/3+1

二:选择排序

2.1选择排序

选择排序就十分简单粗暴,即使遍历一遍数组,选出最小的,然后让最小的与下标为0的数字互换,然后重复

再这里可以做一点小小的优化就是一次选出最大和最小

//选择排序
//选择排序
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]);//如果maxi和bgein重合,前面begin和mini换了值以后,最大的数应该在mini下标if (begin == maxi)maxi = mini;Swap(&a[end], &a[maxi]);end--;begin++;}

2.2堆排序

堆排序分为两个部分就是建堆和排序

建堆:由于升序建大堆

//向下调整
void AdjustDown(int* a, int parent, int n)
{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)
{//建堆:升序见大堆for (int i = (n - 1 - 1) / 2; i >= 0; i--){AdjustDown(a, i , n);}//排序int end = n - 1;while (end > 0){Swap(&a[0], &a[end]);AdjustDown(a, 0, end);end--;}
}

三:交换排序

3.1冒泡排序

冒泡排序就是将数组遍历一遍,建最大的数字冒到最后,然后最后一个位置不参与遍历,在从头开始遍历,找到次大的

代码如下:

//交换排序
//冒泡排序
void BubbleSort(int* a, int n)
{for (int j = 0; j < n - 1; j++){for (int i = 0; i < n - 1-j; i++){if (a[i] > a[i + 1]){Swap(&a[i], &a[i + 1]);}}}}

3.2快速排序 

3.2.1Hoare版本

对于单趟排序,根据上面位置,很多人会写出这样的单趟排序

        int key = a[left];while (left < right){//右边先走,找小//还要保证在数组范围内while (left < right && a[right] > key){right--;}//左边找大while (left < right && a[left] < key){left++;}//此时left所在的位置大于key,right所在的位置小于keySwap(&a[left], &a[right]);}//此时left和right相遇Swap(&key, &a[left]);

 此时,如果调试一下,会发现最后交换时,数组第一个值并没有变,因为创建了一个变量key,用来存最左边的值,相当于创建了一个临时变量,所以最后交换时,是临时变量,与数组中的值交换的,所以我们可以创建一个变量,用于存下标

解决了这个问题,循环条件,还有一个隐藏的条件,如果是以下数组,left和right如何呢?

单趟排序的代码:

        int keyi = left;while (left < right){//右边先走,找小//还要保证在数组范围内while (left < right && a[right] > a[keyi]){right--;}//左边找大while (left < right && a[left] < a[keyi]){left++;}//此时left所在的位置大于key,right所在的位置小于keySwap(&a[left], &a[right]);}//此时left和right相遇Swap(&a[keyi], &a[left]);keyi = left;

排完单趟以后,就是排整个数组,也就是递归,递推

完整的排序代码:

        if (left >= right)return;        //单趟int begin = left, end = right;int keyi = left;while (left < right){//右边先走,找小//还要保证在数组范围内while (left < right && a[right] > a[keyi]){right--;}//左边找大while (left < right && a[left] < a[keyi]){left++;}//此时left所在的位置大于key,right所在的位置小于keySwap(&a[left], &a[right]);}//此时left和right相遇Swap(&a[keyi], &a[left]);keyi = left;//[begin,keyi -1] keyi [keyi+1,end]//递归PartSort1(a, begin, keyi - 1);PartSort1(a, keyi + 1, end);

以上是最基础的快排,但是在厉害的排序也是有自己的不足的,基础版的快排有一个不足就是,如果数组是顺序时,时间复杂度就会从n*logn变成n^2

这个问题会出现是因为我们每次都选择最左边的值作为key,为了解决这个问题,大佬们分别有不同的解决方法,比较常用的是随机值三数取中 

随机值法:顾名思义就是让key是个随机数,不再让key固定即使key,而为了让我们单趟写的代码不发生太大的变化,我们可以选出这个之后,再让这个值与left位置的值互换,这样代码处的left就可以继续使用了

//单趟int begin = left, end = right;//随机选取keyiint randi = rand() % (right - left);randi += left;Swap(&a[randi], &a[left]);int midi = GetMid(a, left, right);Swap(&a[midi], &a[left]);int keyi = left;while (left < right){//右边先走,找小//还要保证在数组范围内while (left < right && a[right] >= a[keyi]){right--;}//左边找大while (left < right && a[left] <= a[keyi]){left++;}//此时left所在的位置大于key,right所在的位置小于keySwap(&a[left], &a[right]);}//此时left和right相遇Swap(&a[keyi], &a[left]);keyi = left;//[begin,keyi -1] keyi [keyi+1,end]//递归PartSort1(a, begin, keyi - 1);PartSort1(a, keyi + 1, end);

三数取中:三数取中并不是取下标的中间值,而是取下标处的数组值的中间值,三数取中应用有序数组可以将复杂度再拉回n*logn

//三数取中
int GetMid(int* a, int left, int right)
{int midi = (left + right) / 2;if (a[left] > a[midi]){if (a[midi] > a[right])return midi;//a[midi] 最小else if (a[left] < a[right])return left;elsereturn right;}else//a[left] < a[midi]{if (a[midi] < a[right])return midi;//a[midi]最大else if (a[left] > a[right])return left;elsereturn right;}
}

而有的大佬,为了让快排更快,有做出了一些优化

递归过程,越往下所占总体比例越大 ,和二叉树类似,最后一层就占总体的50%,而且越往下数组区间越小,因此下面区间小的数组就不值得再往下递归了,之间排序了,一般当数组区间小于10时,就用插入排序

// 快速排序hoare版本//三数取中
int GetMid(int* a, int left, int right)
{int midi = (left + right) / 2;if (a[left] > a[midi]){if (a[midi] > a[right])return midi;//a[midi] 最小else if (a[left] < a[right])return left;elsereturn right;}else//a[left] < a[midi]{if (a[midi] < a[right])return midi;//a[midi]最大else if (a[left] > a[right])return left;elsereturn right;}
}
void  PartSort1(int* a, int left, int right)
{if (left >= right)return;if (right - left + 1 < 10){InsertSort(a, right - left + 1);}else{//单趟int begin = left, end = right;//随机选取keyi//int randi = rand() % (right - left);//randi += left;//Swap(&a[randi], &a[left]);int midi = GetMid(a, left, right);Swap(&a[midi], &a[left]);int keyi = left;while (left < right){//右边先走,找小//还要保证在数组范围内while (left < right && a[right] >= a[keyi]){right--;}//左边找大while (left < right && a[left] <= a[keyi]){left++;}//此时left所在的位置大于key,right所在的位置小于keySwap(&a[left], &a[right]);}//此时left和right相遇Swap(&a[keyi], &a[left]);keyi = left;//[begin,keyi -1] keyi [keyi+1,end]//递归PartSort1(a, begin, keyi - 1);PartSort1(a, keyi + 1, end);}
}

3.2.2双指针法 

 除了Hoare原本的版本以外,还有用双指针法来写快排

代码:

/ 快速排序前后指针法
void  PartSort3(int* a, int left, int right)
{if (left >= right)return;int begin = left, end = right;int midi = GetMid(a, left, right);Swap(&a[midi], &a[left]);//单趟int keyi = left;int prev = left;int cur = left + 1;while (cur <= end){if (a[cur] < a[keyi] && ++prev != cur)Swap(&a[cur], &a[prev]);cur++;}Swap(&a[keyi], &a[prev]);keyi = prev;//[begin, keyi-1] keyi [end, keyi+1]PartSort3(a, begin, keyi - 1);PartSort3(a, keyi+1, end);
}

3.2.3非递归 

快排不一定必须要用递归,也可以借助栈来实现

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/778337.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

自动化测试 —— Pytest fixture及conftest详解

前言 fixture是在测试函数运行前后&#xff0c;由pytest执行的外壳函数。fixture中的代码可以定制&#xff0c;满足多变的测试需求&#xff0c;包括定义传入测试中的数据集、配置测试前系统的初始状态、为批量测试提供数据源等等。fixture是pytest的精髓所在&#xff0c;类似u…

python pytz是什么

pytz模块常用于时区的转换&#xff0c;常常配合datetime一起使用。我们知道datetime除了data方法生成的时间是没有时区概念&#xff0c;其他如time、datetime等都是有时区概念&#xff0c;即指定了tzinfo信息。 >>> import datetime >>> datetime.datetime.n…

绿联 安装DockerCopilot,一键更新已安装的容器

1、镜像 0nlylty/dockercopilot:UGREEN 2、安装 2.1、基础设置 交互、TIY、硬件渲染全部开启&#xff1b; 容器能力&#xff1a;赋予全部能力&#xff1b; 重启策略&#xff1a;容器退出时总是重启容器。 2.2、网络 选择host模式。 2.3、存储空间 装载路径/data不允许修…

Verilog语法之case语句学习

case分支语句是一种实现多路分支控制的分支语句。与使用if-else条件分支语句相比&#xff0c;采用case分支语句来实现多路控制会变得更加的方便直观。 case分支语句通常用于对微处理器指令译码功能的描述以及对有限状态机的描述。Case分支语句有“case”、“casez”、“casex”…

excel 提取数字字符混合文本中的数字(快捷键ctrl+e)

首先&#xff0c;已知A列数据&#xff0c;在B1单元格输入A列中的数据&#xff0c;如3*4*6 第二部&#xff1a;全选对应的B列&#xff0c;然后&#xff1a; ctrld 批量复制 CTRLE 智能复制 由此可见&#xff0c;智能提取汉字与数字混合中的数字方法 。若想分别提取3个数字&am…

原型链-(前端面试 2024 版)

来讲一讲原型链 原型链只存在于函数之中 四个规则 1、引用类型&#xff0c;都具有对象特性&#xff0c;即可自由扩展属性。 2、引用类型&#xff0c;都有一个隐式原型 __proto__ 属性&#xff0c;属性值是一个普通的对象。 3、引用类型&#xff0c;隐式原型 __proto__ 的属…

ZK友好代数哈希函数安全倡议

1. 引言 前序博客&#xff1a; ZKP中的哈希函数如何选择ZK-friendly 哈希函数&#xff1f;snark/stark-friendly hash函数Anemoi Permutation和Jive Compression模式&#xff1a;高效的ZK友好的哈希函数Tip5&#xff1a;针对Recursive STARK的哈希函数 随着Incrementally Ve…

春秋云境CVE-2023-7130

简介 College Notes Gallery 2.0 允许通过“/notes/login.php”中的参数‘user’进行 SQL 注入。利用这个问题可能会使攻击者有机会破坏应用程序&#xff0c;访问或修改数据. 正文 这关有我用了两种办法&#xff0c;第一种是用报错注入进行手注&#xff0c;第二种就是sqlmap…

卡特尔16pf性格测试的用途,HR招聘测评和求职面试测评

卡特尔16pf性格测试&#xff0c;由美国心理学家卡特尔创建&#xff0c;通常简称为16pf&#xff0c;也叫16种人格因素&#xff0c;卡特尔把人格特征提取出16种特性&#xff0c;每种特性的不同并共同组合成丰富各异的人群。 而这16种人格因素&#xff0c;也常常被用于企业人力资…

Flask后端框架搭建个人图库

Hello&#xff0c;我是"小恒不会java" 前言 最近发现自己有一些站点图片丢失&#xff0c;原来是用了人家的链接。考虑到使用对象存储容易被刷流量&#xff0c;可以用flask这种轻量级框架快速实现网页登陆操作&#xff0c;行&#xff0c;也就不考虑正式生产环境那些复…

springcloud微服务项目,通过gateway+nacos实现灰度发布(系统不停机升级)

一、背景 灰度发布的目的是保证系统的高可用&#xff0c;不停机&#xff0c;提升用户体验。在微服务系统中&#xff0c;原有系统不下线&#xff0c;新版系统与原有系统同时在线&#xff0c;通过访问权重在线实时配置&#xff0c;可以让少量用户先应用新版本功能&#xff0c;如…

如何使用 ArcGIS Pro 制作三维建筑

三维地图已经逐渐成为未来地图的趋势&#xff0c;对于大范围应用&#xff0c;只需要普通的建筑体块就行&#xff0c;如果有高程数据&#xff0c;还可以结合地形进行显示&#xff0c;这里为大家介绍一下 ArcGIS Pro 制作三维建筑的方法&#xff0c;希望能对你有所帮助。 数据来…

睿尔曼超轻量仿人机械臂之复合机器人底盘介绍及接口调用

机器人移动平台是一个包含完整成熟的感知、认知和定位导航能力的轮式机器人底盘产品级平台&#xff0c;产品致力于为各行业细分市场的商用轮式服务机器人提供一站式移动机器人解决方案&#xff0c;让合作伙伴专注在核心业务/人机交互的实现。以下是我司产品双臂机器人以及复合升…

在安卓手机上用termux安装完整kali linux的办法

在安卓手机上termux是完整的Linux仿真器&#xff0c;但也有时候需要在手机上装其他集成度更高的Linux发行版的刚需。 在安卓手机上用termux安装完整kali linux的办法&#xff0c;如下&#xff1a; pkg install wget openssl-tool proot tar -y && hash -r &&…

Rust编程(四)PackageCrateModule

这一部分的中文教程/文档都很混乱,翻译也五花八门,所以我建议直接看英文官方文档,对于一些名词不要进行翻译,翻译只会让事情更混乱,本篇从实战和实际需求出发,讲解几个名称的关系。 Module & Crate & Package & Workspace 英文中的意思: Cargo:货物 Crate:…

【详细讲解PostCSS如何安装和使用】

&#x1f308;个人主页:程序员不想敲代码啊&#x1f308; &#x1f3c6;CSDN优质创作者&#xff0c;CSDN实力新星&#xff0c;CSDN博客专家&#x1f3c6; &#x1f44d;点赞⭐评论⭐收藏 &#x1f91d; 希望本文对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提…

在 Mac 上恢复已删除文件的 4 种方法 [完整指南]

几乎每个 Mac 用户都有过因删除、格式化硬盘或清空垃圾箱而导致数据丢失的经历。当您在 Mac 上删除文件并清空垃圾箱或使用“磁盘工具”擦除功能擦除整个硬盘驱动器时&#xff0c;您可能会认为已删除的文件或已擦除的数据已永远消失。事实上&#xff0c;事实并非如此。使用正确…

dou音全系纯算

先说明一个事情&#xff1a; 公众号开通了朋友们&#xff0c;大家可去关注一下&#xff0c;关于国内外的验证系列&#xff0c;或是另类常见的算法都有详情介绍的文章&#xff01;&#xff01;&#xff01;&#xff01;以后有问题可以直接在vx上留言&#xff0c;这样也方便看的多…

智能工具管理系统-智能工具柜系统

智能工具管理系统-智能工具柜系统 智能工具可视化管理系统(智工具DW-S308)是依托互3D技术、云计算、大数据、RFID技术、数据库技术、AI、视频分析技术对RFID工具进行统一管理、分析的信息化、智能化、规范化的系统。 一、工具管理现状 东识RFID工具管理系统是一种便捷化的工具…

前端基础知识html

一.基础标签 1.<h1>-<h6>:定义标题&#xff0c;h最大&#xff0c;h最小 2.<font>&#xff1a;定义文本的字体&#xff0c;尺寸&#xff0c;颜色 3.<b>&#xff1a;定义粗体文本 4.<i>&#xff1a;定义斜体文本 5.<u>&#xff1a;定义文本下…