排序算法-交换排序

news2024/12/23 18:01:13

目录

基本思想

一、冒泡排序

二、快速排序分析

1. hoare版本

2. 挖坑法

3. 前后指针版本

4. 快速排序的优化

三、代码示例

1. hoare版本

2. 挖坑法

3. 前后指针版本

四、快速排序(三路划分)

五、总结


基本思想

基本思想:所谓交换,就是根据序列中两个记录键值的比较结果来对换这两个记录在序列中的位置,交换排序的特点是:将键值较大的记录向序列的尾部移动,键值较小的记录向序列的前部移动。

一、冒泡排序

下面我们将以数组[5,3,1,6,2,4]来进行升序排序

排序过程图如下所示

第一趟排序结构为:

当第一趟结束后数组顺序为[3,1,5,2,4,6],因为这里演示的为升序,此时数组中最大值已经排序成功,接下来重复上面的步骤,并且只用排[0-n-1](n为数组的最大下标)区间的数据

第二趟结果为:

第三趟结果为:

第四趟结果:

第五趟结果:

外层循环的结束条件为:i=0时结束最终循环。

代码示例:

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

其中我们可以定义一个flag来优化代码,这样如果循环结束前数据已经有序则不用再继续循环排序。

冒泡排序的特性总结:
1. 冒泡排序是一种非常容易理解的排序
2. 时间复杂度:O(N^2)
3. 空间复杂度:O(1)
4. 稳定性:稳定

二、快速排序分析

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

快速排序经过多年发展目前有三种不同的实现形式,但本质都是一样。

1. hoare版本

首先我们要对下面这组数进行排序,先标记左端的位置,再标记右端的位置,将最左端的位置的数作为基准值。

第一步:先从右向左移动R,当R找到比key值小的值的时候停下来,接着从左向右移动L,当L找到大于key值时停下来,随后交换L和R位置的数据。如图所示:

接下来每一步都重复上面步骤

最后当L和R指向同一个位置时结束循环。

当我门排完一次后基准值回到了他所在的正确位置,此时基准值左侧和右侧的数据还是乱序,我们只需递归左右两个空间即可完成排序。

递归过程大致为如图:

图中绿色的表示递归调用红色的表示递归返回

最终图中红色的数据就是最终排好序的结果。

2. 挖坑法

挖坑法首先是要记录下基准值key的值,然后标记好L和R的位置,还是得先让R从右往左移找小,再将找的小值填入坑的位置,并且让R成为坑,随后再移动L找大,将大值填入坑,再让自己成为坑。

第一道排序

第二道排序:

第三道排序:

第四道排序:

第五道排序:

第六道排序;

这种排序可以更加直观理解为什么必须先移动R位置才能够正确的排序。

3. 前后指针版本

开始状态如下图所示

第一步:

第二步:

第三步:

第三步:

第四步:

第五步:

最终循环结束时,基准值归为,接下来同样递归两边区间就可以实现快速排序。

4. 快速排序的优化

对于快速排序存在一种极端情况例如:存在一组降序的数据需要排升序,当出现这样的情况我们key值如果还取左左端的值,递归的层数就会从原来的\log_{2}N扩大到N层,最终时间复杂度也会变成N^{2},为了避免这种情况,我们有两种优化方案:取随机值作为key、三数取中法选key。

但我们常用的是三数取中,下面是代码示例:

//三数取中(返回中间数的索引位置)
int GetMid(int* a, int left, int right)
{
	int midindex = (right - left) / 2;
	if (a[left] < a[midindex])
	{
		if (a[midindex] < a[right])
		{
			return midindex;
		}
		//a[midindex] > a[right]
		if (a[right] > a[left])
		{
			return right;
		}
		if (a[left] > a[right])
		{
			return left;
		}
	}
	//a[left] > a[midindex]
	if (a[left] > a[midindex])
	{
		if (a[midindex] > a[right])
		{
			return midindex;
		}
		if (a[right] > a[midindex])
		{
			return right;
		}
		if (a[right] > a[left])
		{
			return left;
		}
	}
	return midindex;
}

代码看起来很复杂,其实分析一下很简单,就是取数组第一个数,中间的数,和最后一个数,然后判断大小,找到值为中间的那个数,并返回该数的索引下标。

三、代码示例

1. hoare版本

//1.hoare版本
void QuickSort1(int* a, int left, int right)
{
	//递归结束条件
	if (left >= right)
		return;
	int begin = left, end = right;

	//随机取key
	//int randkeyi = left + rand() % (right - left)+1;
	//Swap(&a[left], &a[randkeyi]);
    //三数取中
	int midindex = GetMid(a, left, right);
	Swap(&a[left], &a[midindex]);

	int keyi = left;
	while (left < right)
	{
		//找小
		while (left < right && a[right] >= a[keyi])
		{
			right--;
		}
		//找大
		while (left < right && a[left] <= a[keyi])
		{
			left++;
		}
		Swap(&a[left], &a[right]);
	}
	Swap(&a[keyi], &a[left]);
	//[begin,left-1] left  [left+1,end]
	QuickSort1(a, begin, left - 1);
	QuickSort1(a, left + 1, end);
}

2. 挖坑法

//2.挖坑法
void QuickSort2(int* a, int left, int right)
{
	//递归结束条件
	if (left >= right)
		return;
	int begin = left, end = right;
	//三数取中
	int midindex = GetMid(a, left, right);
	Swap(&a[left], &a[midindex]);

	int value = a[left];
	int hole = left;   //需要填坑的位置
	while (left < right)
	{
		while (left < right && a[right] >= value)
		{
			right--;
		}
		a[hole] = a[right];
		hole = right;
		while (left < right && a[left] <= value)
		{
			left++;
		}
		a[hole] = a[left];
		hole = left;
	}
	a[hole] = value;
	//[begin,hole-1] hole  [hole+1,end]
	QuickSort2(a, begin, hole - 1);
	QuickSort2(a, hole + 1, end);
}

3. 前后指针版本

//3.前后指针法
void QuickSort3(int* a, int left, int right)
{
	//递归结束条件
	if (left >= right)
		return;
	//三数取中
	int midindex = GetMid(a, left, right);
	Swap(&a[left], &a[midindex]);

	int prev = left ;
	int cur = left+1;

	while (cur <=  right)
	{
		if (a[cur] >= a[left])
		{
			cur++;
		}
		else
		{
			prev++;
			Swap(&a[prev], &a[cur]);
			cur++;
		}
	}
	Swap(&a[prev], &a[left]);
	//[begin,prev-1] prev  [prev+1,end]
	QuickSort3(a, left, prev - 1);
	QuickSort3(a, prev + 1, right);
}

四、快速排序(三路划分)

快速排序还存在一个及其特别的场景会导致效率变慢,如果一组数全是(大量)一样的数据也会使递归的层数就会从原来的\log_{2}N扩大到N层,对于这种情况有一种特殊的解决办法叫做三路划分。

下面是全是一样数据的递归图:

其中箭头表示每一层递归排完序后的结果,绿色表示递归调用,红色是递归返回,这组n个数据都相同,调用的深度到达了n-1,也就相当于n层调用了当数据越多时调用深度是及其大的。

接下来我们用三路划分来演示对于大量相同数的排序:

先进行初始化

排序大致过程为:

判断cur与L的值大小

       1. cur==L时cur就一直往前走

       2. cur<L时    交换L与cur的值    cur++    L++

       3. cur>L时    交换R与cur的值    R--

结束条件cur超过了R或者L超过了R。

上图是第一趟排序,可见排完序后再次递归的空间少了很多,大大解决了对于大量相同数据的递归太深问题。

代码示例:

//4.三路划分
void QuickSortThreeWay(int* a, int left, int right)
{
	if (left >= right)
	{
		return;
	}
	int begin = left, end = right;

	//三数取中
	int keyi = GetMid(a, left, right);
	Swap(&a[keyi], &a[left]);

	int cur = left + 1;
	while (cur <= right && left <= right)
	{
		if (a[cur] < a[left])
		{
			Swap(&a[cur], &a[left]);
			cur++;
			left++;
		}
		else if (a[cur] > a[left])
		{
			Swap(&a[cur], &a[right]);
			right--;
		}
		else 
		{
			cur++;
		}
	}
	//排完一次序后:
	//[begin,left-1][left,right][right+1,end]
	QuickSortThreeWay(a, begin, left - 1);
	QuickSortThreeWay(a, right + 1, end);
}

五、总结

快速排序的特性总结:
1. 快速排序整体的综合性能和使用场景都是比较好的,所以才敢叫快速排序
2. 时间复杂度:O(N*logN)

如果还想了解更多排序欢迎大家前往我的主页。

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

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

相关文章

VS Code终端命令执行后老是出现 __vsc_prompt_cmd_original: command not found

VS Code终端命令执行后老是出现 __vsc_prompt_cmd_original: command not found。 如下图&#xff08;vscode终端中&#xff09;&#xff1a; 解决方案&#xff1a; 1、vim ~/.bashrc 2、在~/.bashrc里面加入命令&#xff1a;unset PROMPT_COMMAND 3、source ~/.bashrc

【AI大模型】Kimi API大模型接口实现

一、Kimi大模型概述 Kimi&#xff0c;月之暗面旗下国产大模型。是北京月之暗面科技有限公司&#xff08;Moonshot AI&#xff09;于2023年10月9日推出的一款智能助手&#xff0c;主要应用场景为专业学术论文的翻译和理解、辅助分析法律问题、快速理解API开发文档等&#xff0c…

关于http的206状态码和416状态码的意义、断点续传以及CORS使用Access-Control-Allow-Origin来允许跨域请求

一、关于http的206状态码和416状态码的意义及断点续传 HTTP 2xx范围内的状态码表明客户端发送的请求已经被服务器接受并且被成功处理了,HTTP/1.1 206状态码表示客户端通过发送范围请求头Range抓取到了资源的部分数据&#xff0c;一般用来解决大文件下载问题&#xff0c;一般CDN…

直接的强化学习与间接的强化学习

强化学习是一种机器学习方法&#xff0c;用于让智能体在与环境的交互中学习最优策略&#xff0c;以获得最大的奖励。根据强化学习的方式&#xff0c;可以分为直接强化学习和间接强化学习。直接强化学习注重直接从奖励信号中学习最优策略&#xff0c;而间接强化学习则通过学习环…

【吊打面试官系列-MySQL面试题】MySQL 中有哪几种锁?

大家好&#xff0c;我是锋哥。今天分享关于【MySQL 中有哪几种锁&#xff1f;】面试题&#xff0c;希望对大家有帮助&#xff1b; MySQL 中有哪几种锁&#xff1f; 1000道 互联网大厂Java工程师 精选面试题-Java资源分享网 1、表级锁&#xff1a;开销小&#xff0c;加锁快&…

2024.9.16 day 1 pytorch安装及环境配置

一、配置pytorch环境&#xff0c;安装pytorch 1.查看python版本 python --version 2.在anaconda命令中创建pytorch环境 conda create -n pytorch python3.12(python版本&#xff09; 3.pytorch安装 pytorch首页 PyTorchhttps://pytorch.org/ os为windows推荐package选择…

在jenkins作业中如何增加git fetch的超时时间

在jenkins作业中如何增加git fetch的超时时间 可以通过以下几种方式来增加 Jenkins 中 git fetch 的超时时间: 1.在 Jenkins 的构建配置中设置超时时间: 在 Jenkins 的构建配置页面,找到 "Git" 部分,在 "Additional Behaviours" 中选择 "Advanced c…

Sui与3DOS合作推动3D打印网络的去中心化

制造业创新者3DOS宣布将其庞大的3D打印网络与Sui集成。这一集成使用户、3D打印机和制造商能够连接到全球可访问的去中心化网络。 解锁去中心化3D打印的全部潜力依赖于精确的实时协调。Sui作为通用协调层&#xff0c;将用户、3D打印机和制造商同步到一个高效、统一的网络中。通…

注册登录案列

案列需求&#xff1a; 在主测页面中输入用户数据&#xff0c;点击注册按钮完成用户注册 实现步骤&#xff1a; 1.创建数据库表&#xff0c;Mysql代码如下&#xff1a; CREATE TABLE tb_user( id int primary key auto_increment, username VARCHAR(32), password VARCHAR(3…

【题解】【枚举】—— [USACO1.5] 回文质数 Prime Palindromes

【题解】【枚举】—— [USACO1.5] 回文质数 Prime Palindromes [USACO1.5] 回文质数 Prime Palindromes题目描述输入格式输出格式输入输出样例输入 #1输出 #1 提示 思路1.素数筛法1.1.思路解析1.2.参考代码 解法1.打表1.1.思路解析1.2.AC代码 解法2.构造回文数2.1.思路解析2.2.…

Java集合进阶--双列集合

双列集合的特点&#xff1a; 1 双列集合一次需要存一对数据&#xff0c;分别为键和值 2 键不能重复&#xff0c;值能重复 3 键和值是一一对应的&#xff0c;每一个键只能找到自己对应的值 4 键值这个整体 我们称之为 “键值对” 或者 “键值对对象” &#xff0c;在Java中叫做 …

react hooks--useState

概述 useState 可以使函数组件像类组件一样拥有 state&#xff0c;也就说明函数组件可以通过 useState 改变 UI 视图。那么 useState 到底应该如何使用&#xff0c;底层又是怎么运作的呢&#xff0c;首先一起看一下 useState 。 问题&#xff1a;Hook 是什么? 一个 Hook 就是…

【思博伦】史上最详细思博伦测试仪使用精讲(三)!图解超赞超详细!!!

目录 2.2.14 抓包过滤条件配置 2.2.14.1 配置抓Tx或Rx方向的包 2.2.14.2 添加自定义过滤条件 2.2.14.3 按照包类型配置Qualify Events 2.2.14.4 按照包类型配置Start Events ​​​​​​​2.2.14.5 按照包类型配置Stop Events ​​​​​​​2.2.15 端口计数器统计 ​​…

大数据处理技术:HBase的安装与基本操作

目录 1 实验名称 2 实验目的 3 实验内容 4 实验原理 5 实验过程或源代码 5.1 Hbase数据库的安装 5.2 创建表 5.3 添加数据、删除数据、删除表 5.4 使用Java操作HBase 6 实验结果 6.1 Hbase数据库的安装 6.2 创建表 6.3 添加数据、删除数据、删除表 6.4 使用Java操…

25嘉士伯笔试测评希音笔试测评秋招校招SHL笔试题型分享

25嘉士伯笔试测评用的SHL笔试测评题库&#xff0c;分为两部分&#xff1a; 综合能力部分有计算题 图形推理题 连线题 逻辑题 日历题等等&#xff0c;36min24道题&#xff0c;新手很难做完&#xff1b; 岗位匹配度测评分为8道综合能力性格测试题&#xff0c;给三个选项选出最符…

【系统架构师】-论文-2024-2009年系统架构师历年论文题目

2024年5月 大数据Lambda架构的应用与分析 云原生云上DevOps运维应用与分析 模型驱动软件开发方法与应用 论单元测试在软件回归测试中的应用和分析 2023年 论面向对象设计的应用与实现 论多数据源集成的应用与实现 论软件可靠性模型的设计与实现 论边缘计算技术的设计与实现 …

Java:抽象类和接口(1)

一 抽象类 1.什么是抽象类 在 Java SE 中&#xff0c;抽象类是一种用于为其他类提供通用行为的类。它允许你定义一组方法和字段&#xff0c;而具体的实现留给子类来完成。抽象类不能被实例化&#xff0c;必须通过继承它的子类来实现其抽象方法并进行实例化。 public abstrac…

MATLAB系列04:循环结构

MATLAB系列04&#xff1a;循环结构 4. 循环结构4.1 while循环4.2 for循环4.2.1 运算的细节4.2.2 break语句和continue语句4.2.3 嵌套循环 4.3 逻辑数组和向量化4.3.1 逻辑数组的重要性4.3.2 用 if/else 结构和逻辑数组创建等式 4.4 总结 4. 循环结构 循环(loop)是一种 MATLAB …

初识Linux · 进程(4)

目录 前言&#xff1a; 进程的状态 直接谈论进程的状态 僵尸进程和孤儿进程 纯理论部分 运行态&#xff1a; 阻塞态&#xff1a; 挂起态&#xff1a; 进程的优先级以及切换问题 切换&#xff1a; 优先级&#xff1a; 前言&#xff1a; 承接上文&#xff0c;进程1到…

CPU 和 GPU:为什么GPU更适合深度学习?

目录 什么是 CPU &#xff1f; 什么是 GPU &#xff1f; GPU vs CPU 差异性对比分析 GPU 是如何工作的 &#xff1f; GPU 与 CPU 是如何协同工作的 &#xff1f; GPU vs CPU 类型解析 GPU 应用于深度学习 什么是 CPU &#xff1f; CPU&#xff08;中央处理器&#xff09;…