排序(2)【选择排序】【快速排序】

一.选择排序

选择排序就是选择一个数组的最大的数字或者最小的数字,放在一整个数组的最后或者开头的位置。

1.选择排序的实现

我们可以对选择排序进行一些加强,普通的选择排序是选择最小的数,然后进行交换。这个加强之后就是我们既要选择出最大的还有选择出最小的进行交换。

具体代码如下:

void SelectSort(int* a, int n)
{int begin = 0;//俩个变量,一个在头一个在尾int end = n - 1;while (begin < end)//这俩个变量互相靠近{int mini = begin;int maxi = begin;//假设最大和最小都是开始的那个位置for (int i = begin + 1; i <= end; i++)//从a[1]开始{if (a[i] < a[mini])//如果比最小的小,就把此处的i值赋值给minimini = i;if (a[i] > a[maxi])//同理,如果此处的比最大的大,就把i给maximaxi = i;}Swap(&a[begin], &a[mini]); //最小值找到了,开始排序,把最小值给初始位置if (maxi == begin)//这里需要处理一个特殊情况,如果最大值在begin处的话,上面在换的过程中,换的就是maxi处的值maxi = mini;//换完之后maxi指向的地方是begin,begin处的值就是最小的,mini指向的值是最大的,需要把mini的值赋值给maxiSwap(&a[end], &a[maxi]);//之后再交换最后和maxi处的值++begin;--end;}
}

2.选择排序的时间复杂度

选择排序很好理解,但是效率不高。如果在逆序,最坏的情况下,它的效率最低,拍好所消耗的时间最长。

需要进行(n-1)+(n-2)+...+2+1次比较,即(n^2-n)/2次比较。每一次比较都要交换元素,所以平均需要(n^2-n)/2次交换操作。时间复杂度就是O(N^2)

二.快速排序

快速排序理解起来就有点复杂了。而且也有不同的方式可以来实现快速排序,比如:hoare方法,挖坑法,前后指针法。

1.hoare方法

 Hoare方法通过选择一个基准值(pivot),将数组分为两个部分,小于等于基准值的部分和大于等于基准值的部分。然后,递归对这两个部分进行快速排序。

像是上图中的L和R,我们给一个基准值key,L往右走找比key大的值,R往左走找比key小的值,它们两个找到了就停止,然后交换两个位置的值,直到它们两个相遇停止,再交换此处和key位置的值。这是整体的进行交换,从它们相遇的地方再次分隔成两个区间,再一次进行上面的步骤。

值得考虑一下的是,它们相遇的位置一定比key位置的值小:

假如我们最左边作为key,让R先走,那么相遇的位置一定比key小。我们可以分为两种情况来看:

(1)一种是L遇到R,R先走,停下来,R停下的条件是遇到比key小的值,R停的位置一定比key小,L没有找到大的,遇到R直接就停下来了。

(2)还有一种的R遇到L,R先走,找小,没有找到比key小的,直接就跟L相遇了,L停留的位置就是上一轮交换的位置,上一轮的交换,把比key小的值交换到L位置了。

 来写代码:

void Swap(int* a, int* b)
{int tmp = *a;*a = *b;*b = tmp;
}
void QuickSort(int* a, int left, int right)
{if (left >= right)return;int keyi = left;//我们假设我们要对比的值是最左边的值int begin = left;int end = right;//一个从最左边开始一个从最右边开始while (begin < end){while (a[end] >= a[keyi] && begin < end)//end往前找比keyi位置小的值--end;while (a[begin] <= a[keyi] && begin < end)//begin找后找比begin大的值,值得注意的是,这里都有一个限定条件begin<end//主要是为了防止end减到begin前面,或者begin加到end后面,因为这都是在一个大的while循环内部,没结束这一层循环之前,不会在大的while循环里判断begin<end++begin;Swap(&a[begin], &a[end]);//每一次找到比begin位置大的值和比end小的值后就交换}Swap(&a[keyi], &a[begin]);//出去循环后再交换keyi和begin位置的值就行了keyi = begin;//此时的begin的值和end的值是一样的QuickSort(a, left, keyi - 1);//之后就是分成两半,左边和右边分别开始递归QuickSort(a, keyi + 1, right);
}

注意这里用到的递归方式跟我们的二叉树比较像的,那个东西理解到位了,这里就非常好理解了。 

2.挖坑法

这里的挖坑法的这种写法是我自己根据动图想的(可能跟其他人有点小出入),这个方法比上面上面的hoare方法更好理解一点。

void QuickSort1(int* a, int left, int right)
{if (left >= right)return;int key = a[left];//我们默认最左边的值是坑位,我们先把最左边的值保存起来int keng = left;int begin = left;int end = right;while (begin < end){while (a[end] >= key && begin < end)//找到比坑位的值小的值--end;a[keng] = a[end];//找到了我们就把这里的值放到坑里面去keng = end;//移动坑位到我们的end位置while (a[begin] <= key && begin < end)//然后从左边找到比坑位的值大的值++begin;a[keng] = a[begin];//把这里的值放到坑里面keng = begin;//然后这里的begin成为新的坑,继续循环}a[keng] = key;//最后begin和end相遇的位置就是坑,把我们保存的key放在这里,后面就开始递归QuickSort1(a, left, keng - 1);QuickSort1(a, keng + 1, right);
}

3.前后指针法

void QuickSort2(int* a, int left, int right)
{if (left >= right)return;int keyi = left;int prev = left;//一个指针int cur = prev + 1;//另一个指针指向下一个位置while (cur <= right)//到指针越界跳出循环{if (a[cur] < a[keyi])//如果cur位置的值小于keyi位置的,进入if语句{++prev;//交换之前prev要先移动到下一位,如果prev和cur只相差一位的话,实际上就是交换自身等于没有交换Swap(&a[cur], &a[prev]);}++cur;//不论cur位置的值是大于keyi位置的还是小于,cur都要往后走,如果cur位置的值一直大于keyi位置的,它们两个之间的距离会越来越大}Swap(&a[prev], &a[keyi]);//prev位置的值一定小于或者等于keyi位置的,交换它们两个QuickSort2(a, left, prev - 1);QuickSort2(a, prev + 1, right);
}

简单说一下思想,就是前面走一个指针,一直找比keyi位置的值小的值,找到了就跟prev的下一个位置进行交换(因为prev的位置一定是left或者上一轮交换完毕的比keyi位置小的值)。

4.快速排序的改进

本来这一个应该在前面就应该说的,但是我想快速排序就只是上面的代码就可以了。虽然上面的代码已经比较好了,但是依然有很大的弊端。一个是关于keyi的取值,我们都是默认在最左边的那个值的,但是这种方式有很大的弊端。

对于已经有序的数组,快速排序在每次选择基准元素时都选择最左边或者最右边的元素作为基准,这样导致快速排序的时间复杂度变为O(n^2),而不是理想情况下的O(nlogn)。

在数组中存在大量重复元素的情况下,快速排序可能出现分割不均匀的情况,导致快速排序的时间复杂度退化为O(n^2)。

而且如果待排数组的量非常大的时候,递归深度也会非常大,可能导致栈溢出。

还有一个就是我们可以优化一下当数据量很小的时候的排序。假如我们有10个数,我们用递归遍历从中间分开,就跟二叉树一样:

我们在递归的时候,就像是一个金字塔形,我们在排列最下面的数据的时候我们不用递归了,用其他的排序方式排列。我们可以省下将近百分之八十的效率。

4.1三数取中

这个就纯纯的逻辑问题了,注意看就行了:

int GetMidi(int* a, int left, int right)
{int midi = (left + right) / 2;// left midi rightif (a[left] < a[midi]){if (a[midi] < a[right]){return midi;}else if (a[left] < a[right]){return right;}else{return left;}}else // a[left] > a[midi]{if (a[midi] > a[right]){return midi;}else if (a[left] < a[right]){return left;}else{return right;}}
}

 4.2小区间优化

小区间我们就用插入排序来实现,小于10的意思就是,把下面三层的代码用插入排序代替。

if ((right - left + 1) < 10){InsertSort(a+left, right - left + 1);}else{//快速排序代码...}

假如优化代码的话就是这样:

void QuickSort(int* a, int left, int right)
{if (left >= right)//如果只剩一个元素,直接跳出这一层return;if ((right - left + 1) < 10){InsertSort(a + left, right - left + 1);}else{int midi = GetMidi(a, left, right);Swap(&a[left], &a[midi]);int keyi = left;//我们假设我们要对比的值是最左边的值int begin = left;int end = right;//一个从最左边开始一个从最右边开始while (begin < end){while (a[end] >= a[keyi] && begin < end)//end往前找比keyi位置小的值--end;while (a[begin] <= a[keyi] && begin < end)//begin找后找比begin大的值,值得注意的是,这里都有一个限定条件begin<end//主要是为了防止end减到begin前面,或者begin加到end后面,因为这都是在一个大的while循环内部,没结束这一层循环之前,不会在大的while循环里判断begin<end++begin;Swap(&a[begin], &a[end]);//每一次找到比begin位置大的值和比end小的值后就交换}Swap(&a[keyi], &a[begin]);//出去循环后再交换keyi和begin位置的值就行了keyi = begin;//此时的begin的值和end的值是一样的QuickSort(a, left, keyi - 1);//之后就是分成两半,左边和右边分别开始递归QuickSort(a, keyi + 1, right);}
}

5.非递归的方式

我们上面用的都是递归的方式,这种方式当然很好的实现了快速排序,但是我们可不可以不用递归的方式来实现这个问题?

这里我们就需要用到一种我们之前学过的一种东西叫做栈。我们了解栈的特性就是先进后出,如果不了解的可以看我的另一篇博客:栈和队列

这里我就直接写函数的主体了。

int PartSort(int* a, int left, int right)
{int midi = GetMidi(a, left, right);Swap(&a[midi], &a[left]);int prev = left;int cur = prev+1;int keyi = left;while(cur<=right){if (a[cur] < a[keyi]){++prev;Swap(&a[cur], &a[prev]);}++cur;}Swap(&a[prev], &a[keyi]);return prev;
}
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);//提取出来的第一个元素是刚才最后入栈的元素STTop(&st);int end = STTop(&st);//紧接着就是刚才入栈的倒数第二个元素STTop(&st);int keyi = PartSort(a, begin, 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.快速排序的时间复杂度

快速排序的时间复杂度为O(nlogn)。在最坏的情况下,即待排序的序列已经有序或者近乎有序时,快速排序的时间复杂度接近O(n^2)。但是平均情况下,快速排序的时间复杂度为O(nlogn)。

到这里这两种排序就差不多结束了,感谢大家的观看如有错误还请多多指正。

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

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

相关文章

Python:哈希查找法

哈希查找&#xff08;Hash Search&#xff09;是一种高效的搜索算法&#xff0c;它利用哈希函数将键映射到存储位置&#xff0c;并在该位置查找目标元素。哈希查找适用于快速查找和检索&#xff0c;特别适用于大型数据集合。以下是哈希查找的详细解释和示例&#xff1a; 1.哈希…

从ES的JVM配置起步思考JVM常见参数优化

目录 一、真实查看参数 &#xff08;一&#xff09;-XX:PrintCommandLineFlags &#xff08;二&#xff09;-XX:PrintFlagsFinal 二、堆空间的配置 &#xff08;一&#xff09;默认配置 &#xff08;二&#xff09;配置Elasticsearch堆内存时&#xff0c;将初始大小设置为…

堆排序!!

堆排序 算法思想maxHeapify函数maxHeapify完整代码 HeapSort函数HeapSort完整代码 问题描述完整代码 算法思想 堆排序基于完全二叉树的堆数据结构&#xff0c;其中每个节点的值都大于或等于其子节点的值&#xff08;最大堆&#xff09;&#xff0c;或者小于或等于其子节点的值…

ElasticSearch + kibana:类型声明

当我们使用 kibana 创建索引时&#xff0c;如果不申明数据类型&#xff0c;默认字符串赋予 text类型&#xff0c;如下图所示 接下来我们继续创建多条数据如下&#xff1a; 下面我们来检索下&#xff1a; 通过以上两个案例我们发现&#xff0c;使用 match 模糊查询 li-3 明明…

别再问别人了,这是小白都能懂的拓扑图指南

号主&#xff1a;老杨丨11年资深网络工程师&#xff0c;更多网工提升干货&#xff0c;请关注公众号&#xff1a;网络工程师俱乐部 上午好&#xff0c;我的网工朋友。 老杨的网工交流群里经常会有这种现象&#xff1a; 一群小伙伴在问各类型拓扑图的问题&#xff0c;怎么设计&…

monitor-zabbix

监控体系理论 学习本篇文章&#xff0c;了解运维监控系统的前世今生 zabbix官网仓库地址 zabbix官网 https://www.zabbix.com/cn/zabbix官网仓库地址 http://repo.zabbix.com/zabbix/ http://repo.zabbix.com/zabbix/4.0/ubuntu/pool/main/z/zabbix-release/zabbix-release_…

Hi3861 OpenHarmony嵌入式应用入门--基于HI-12F开发板烧写程序

首先需要一个开发板&#xff0c;我已经在嘉立创上进行了开源&#xff0c;基于安信可hi-12f模块的开发板&#xff0c;集成了两个按键&#xff0c;一个三色灯&#xff0c;一个滑动变阻器&#xff0c;可外接一个0.96寸液晶。 【HI-12F】基于安信可hi-12f模块的开发板 - 嘉立创EDA…

借助Aspose.Email,使用 C# .NET 创建 PST 文件并填充内容

PST&#xff08;个人存储表&#xff09;文件是管理 Outlook 数据的重要组成部分&#xff0c;方便存储电子邮件、联系人、日历和其他项目。在 C# .NET 开发领域&#xff0c;创建和管理存储文件的过程对于各种应用程序至关重要。 在本文中&#xff0c;我们将探讨如何使用 C# .NE…

内窥镜窄带光

文章目录 NBI相关信息 NBI相关信息 第一不知道哪家有这个技术&#xff1f; 第二直接搜索找不到相关信息 第三只能搜企业官网 搜集到的与NBI&#xff0c;相关的信息如下 英美达医疗公司 https://www.innermed.com/index.php/gongsixinwen/139.html 新光维医疗公司 官网页面…

【Spring】1. Maven项目管理

&#x1f4da;博客主页&#xff1a;爱敲代码的小杨. ✨专栏&#xff1a;《Java SE语法》 | 《数据结构与算法》 | 《C生万物》 |《MySQL探索之旅》 |《Web世界探险家》 ❤️感谢大家点赞&#x1f44d;&#x1f3fb;收藏⭐评论✍&#x1f3fb;&#xff0c;您的三连就是我持续更…

Web前端图片欣赏:视觉盛宴与技术魅力的完美融合

Web前端图片欣赏&#xff1a;视觉盛宴与技术魅力的完美融合 在这个数字化时代&#xff0c;Web前端技术不仅承载着信息的传递&#xff0c;更在视觉呈现上不断追求创新与突破。今天&#xff0c;我们将一同欣赏一场由Web前端技术打造的图片盛宴&#xff0c;感受其独特的魅力与无尽…

教案:Horovod v0.2 介绍与使用

课程目标 了解Horovod的主要功能和优势。学习如何安装和配置Horovod。掌握Horovod在分布式训练中的应用。 教学内容 Horovod的简介和动机 动机 使单GPU训练脚本轻松扩展到多GPU训练。尽量减少代码修改以实现分布式训练。内部采用MPI模型&#xff0c;代码变动较少&#xff0c;…

02-使用jQuery操作页面

操作DOM元素 1. 操作属性(重点) attr() 获取或设置标签(html标签)的属性。 removeAttr() 删除标签的属性。 prop() 获取或设置元素(DOM元素)的属性。 removeProp() 删除元素的属性。 注意&#xff1a;不要使用该方法来移除诸如 style、id 或 checked 之类的 HTML 属性…

【无标题】Pycharm执行报错

file 读取未指定utf-8编码&#xff0c;加上就好了 疑问&#xff1a;为什么 有的电脑可以直接跑呢&#xff1f;该电脑、Pycharm、工程&#xff0c;已经做了修改设置默认值&#xff0c;但是到新的电脑上&#xff0c;就需要重新设置&#xff0c;所以 file 读、写&#xff0c;最好…

鸿蒙轻内核调测-内存调测-内存泄漏检测

1、基础概念 内存泄漏检测机制作为内核的可选功能&#xff0c;用于辅助定位动态内存泄漏问题。开启该功能&#xff0c;动态内存机制会自动记录申请内存时的函数调用关系&#xff08;下文简称LR&#xff09;。如果出现泄漏&#xff0c;就可以利用这些记录的信息&#xff0c;找到…

如何编写测试用例?

&#x1f345; 视频学习&#xff1a;文末有免费的配套视频可观看 &#x1f345; 点击文末小卡片&#xff0c;免费获取软件测试全套资料&#xff0c;资料在手&#xff0c;涨薪更快 在软件测试中&#xff0c;测试用例是执行测试的重要依据&#xff0c;它的质量直接影响测试的效果…

程序员日志之计算机相关专业还值得选择吗?

目录 传送门正文日志1、概要2、专业选择2.1、专业2.2、学校2.3、城市 3、计算机相关专业还值得选择吗&#xff1f; 传送门 SpringMVC的源码解析&#xff08;精品&#xff09; Spring6的源码解析&#xff08;精品&#xff09; SpringBoot3框架&#xff08;精品&#xff09; MyB…

图神经网络实战(14)——基于节点嵌入预测链接

图神经网络实战&#xff08;14&#xff09;——基于节点嵌入预测链接 0. 前言1. 图自编码器2. 变分图自编码器3. 实现变分图自编码器小结系列链接 0. 前言 我们已经了解了如何使用图神经网络 (Graph Neural Networks, GNN) 生成节点嵌入&#xff0c;我们可以使用这些嵌入执行矩…

中华老字号李良济,展现百年匠心之魅力,释放千年中医药文化自信

6月14-16日&#xff0c;“潮品老字号 国货LU锋芒”江苏老字号博览会在南京隆重启幕&#xff0c;中华老字号李良济凭借过硬的品牌实力和优质的口碑再次受邀参加&#xff0c;并在展会上绽放百年匠心魅力&#xff0c;彰显千年中医药文化自信&#xff01; 百年匠心 以实力铸就荣耀…

计算机组成原理之定点乘法运算

文章目录 原码并行乘法与补码并行乘法原码算法运算规则存在的问题带符号的阵列乘法器习题原码阵列乘法器间接补码阵列乘法器直接补码阵列乘法器 补码与真值的转换 原码并行乘法与补码并行乘法 原码算法运算规则 存在的问题 理解流水式阵列乘法器&#xff08;并行乘法器&#x…