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

news2024/10/7 11:21:31

一.选择排序

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

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值赋值给mini
				mini = i;
			if (a[i] > a[maxi])//同理,如果此处的比最大的大,就把i给maxi
				maxi = i;
		}
		Swap(&a[begin], &a[mini]); //最小值找到了,开始排序,把最小值给初始位置
		if (maxi == begin)//这里需要处理一个特殊情况,如果最大值在begin处的话,上面在换的过程中,换的就是maxi处的值
			maxi = mini;//换完之后maxi指向的地方是begin,begin处的值就是最小的,mini指向的值是最大的,需要把mini的值赋值给maxi
		Swap(&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 right
	if (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.coloradmin.cn/o/1830631.html

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

相关文章

北京物业管理app开发,便民服务,智慧管理

居民的现代化生活离不开物业的支持&#xff0c;物业作为服务、保障居民生活的管理单位&#xff0c;从门禁、电梯、快递、停车等方方面面都影响着居民的日常生活。随着经济与科技的不断发展&#xff0c;居民对物业的服务也提出了新的要求。 一&#xff0e; 北京物业管理的现状 …

element--el-table合计换行显示

el-table合计换行显示 效果图实现1、使用到的参数2、代码演示 效果图 实现 1、使用到的参数 官网链接&#xff1a;element-table 将show-summary设置为true就会在表格尾部展示合计行。默认情况下&#xff0c;对于合计行&#xff0c;第一列不进行数据求合操作&#xff0c;而是…

勾八头歌网安之标准ACL、扩展ACL

标准ACL vs 扩展ACL 我是分享我的快乐的终于写完啦&#xff01;&#xff01;&#xff01;&#xff01;我有时间会把步骤写上来的 也不知有人需要吗哈哈哈 vs

LabVIEW利用旋转编码器脉冲触发数据采集

利用旋转编码器发出的脉冲控制数据采集&#xff0c;可以采用硬件触发方式&#xff0c;以确保每个脉冲都能触发一次数据采集。本文提供了详细的解决方案&#xff0c;包括硬件连接、LabVIEW编程和触发设置&#xff0c;确保数据采集的准确性和实时性。 一、硬件连接 1. 旋转编码…

PyCharm配置教程,手把手教你如何配置

文章目录 引言1. 安装 PyCharm1.1 下载和安装1.2 初次启动 2. 基本配置2.1 设置界面2.2 常用配置项 3. 项目配置3.1 创建新项目3.2 配置解释器 4. 虚拟环境配置4.1 创建虚拟环境4.2 使用已有虚拟环境4.3 管理依赖 5. 插件和扩展5.1 安装插件5.2 推荐插件 6. 调试配置6.1 配置调…

基于Django + Web + MySQL的智慧校园系统

基于Django Web MySQL的智慧校园系统 由于时间紧迫&#xff0c;好多功能没实现&#xff0c;只是个半吊子的后台管理系统&#xff0c;亮点是项目安全性还算完整&#xff0c;权限保护加密功能检索功能有实现&#xff0c;可参考修改 功能如下&#xff08;服务为超链接&#xff0…

DC/AC电源模块:提升光伏发电系统的能源利用率

BOSHIDA DC/AC电源模块&#xff1a;提升光伏发电系统的能源利用率 随着环境保护意识的提高和能源需求的增加&#xff0c;光伏发电系统作为一种清洁能源的代表&#xff0c;受到了越来越多的关注。然而&#xff0c;光伏发电系统在实际应用中还存在一些问题&#xff0c;如发电效率…

MLOps模型部署的三种策略:批处理、实时、边缘计算

机器学习运维&#xff08;MLOps&#xff09;是一组用于自动化和简化机器学习&#xff08;ML&#xff09;工作流程和部署的实践。所选择的部署策略可以显著影响系统的性能和效用。所以需要根据用例和需求&#xff0c;采用不同的部署策略。在这篇文章中&#xff0c;我们将探讨三种…

如何通过墙面互动投影打造全新娱乐体验?

随着展厅设计技术的飞速发展&#xff0c;我们见证了无数令人惊叹的创意墙面互动设计形式的涌现。其中&#xff0c;墙面互动投影凭借其独特的魅力&#xff0c;成为了备受欢迎的创意墙面设计典范。它巧妙地融合了展示内容与互动体验&#xff0c;彻底革新了观众与数字内容的交流方…

Python基础语法学习(工程向)-Stage1

输出的方式&#xff1a; print(fabscwdasd {num}) print(asbduwiu %d, a) print(asnidoian %d %d %d,a,b,c)不换行 print(asbdiuabw,end )输入 a input(输入) 只能输入字符串形式&#xff0c;如果相当做数字用则将其转化为数字 只有合法的数字才能转化成功 a int(input()…

一些个人电脑用的小工具软件

1 个人电脑信息管理 如下&#xff1b; 整理自己的电脑信息&#xff1b;录入&#xff0c;保存&#xff0c;查询&#xff1b;添加和更新界面如下&#xff0c; 每次添加、更新之后重新点一下菜单的浏览&#xff1b; 下载&#xff0c; https://download.csdn.net/download/bcb…

C#语言入门详解 --- 方法(含传值 输出 引用 数组)

方法 方法标准式 <Access Specifier> <Return Type> <Method Name>(Parameter List) { Method Body } 让我们逐一对每一个模块进行解释&#xff1a; Access Specifier&#xff1a;访问修饰符&#xff0c;这决定了接下来的主题的可见性&#xff0c;包含p…

在微信公众号上怎么添加预定房间功能

在这个快节奏的现代社会&#xff0c;人们对于便捷与高效的需求日益增加。特别是在旅行或出差时&#xff0c;能够快速、方便地预订一间舒适的房间&#xff0c;无疑是每个人心中的小确幸。今天&#xff0c;我们为您带来了一项革命性的服务——微信公众号上的房间预定功能&#xf…

QQ登录测试用例

QQ登录测试用例 常见测试方法&#xff08;可参考软件测试<用例篇>&#xff09; 等价类&#xff1a; 1、有效等价类 &#xff1a;满足需求的数据集合 2、无效等价类&#xff1a;不满足需求的数据集合 边界值错误猜测法场景法 QQ测试用例设计&#xff1a;xmind 需要完整…

位运算算法:编程世界中的魔法符号

✨✨✨学习的道路很枯燥&#xff0c;希望我们能并肩走下来! 文章目录 目录 文章目录 前言 一. 常见位运算总结 二、常见位运算题目 2.1 位1的个数 2.2 比特数记位&#xff08;典型dp&#xff09; 2.3 汉明距离 2.4 只出现一次的数字&#xff08;1&#xff09; 2.5 只出…

JavaScript-事件监听

添加事件监听 语法&#xff1a;对象名.addEventListener(事件类型,要执行的函数) 作用&#xff1a;当事件触发时&#xff0c;就调用这个函数 事件类型&#xff1a;比如用鼠标点击&#xff0c;或用滚轮滑动&#xff0c;鼠标经过这些 要执行的函数&#xff1a;要做的事 &l…

区区微服务,何足挂齿?

背景 睿哥前天吩咐我去了解一下微服务&#xff0c;我本来想周末看的&#xff0c;结果周末没带电脑&#xff0c;所以只能周一看了。刚刚我就去慕课网看了相关的视频&#xff0c;然后写一篇文章总结一下。这篇文章算是基础理论版&#xff0c;等我之后进行更多的实践&#xff0c;…

【无标题】Pycharm执行报错

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

[Shell编程学习路线]——if条件语句(单,双,多分支结构)详细语法介绍

&#x1f3e1;作者主页&#xff1a;点击&#xff01; &#x1f6e0;️Shell编程专栏&#xff1a;点击&#xff01; ⏰️创作时间&#xff1a;2024年6月17日7点50分 &#x1f004;️文章质量&#xff1a;95分 文章目录 ————前言———— &#x1f4af;趣站&#x1f4af…

【NOI】C++程序结构入门之循环结构四——带余除法

文章目录 前言一、带余除法1.1 概念1.2 编程中的使用1.2.1 模运算1.2.2 判断奇偶性1.2.3 判断倍数关系1.2.4 循环和迭代控制1.2.5 密码学与安全1.2.6 算法设计1.2.7 数据验证与错误处理 二、例题讲解问题&#xff1a;1389 - 数据分析问题&#xff1a;1750 - 有0的数问题&#x…