排序 “叁” 之交换排序

news2024/11/10 20:42:07


目录

1. 基本思想

2.冒泡排序

2.1 基本思想

2.2 代码示例

2.3 冒泡排序的特性总结

3.快速排序

3.1 基本思想

🌵hoare版本

🌵挖坑法

​编辑

🌵前后指针版本

​编辑

3.2 快速排序优化

🌻三数取中法选key

3.4 快速排序的特性总结


1. 基本思想

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

2.冒泡排序

2.1 基本思想

冒泡排序是一种简单直观的排序算法,其基本思想是通过不断比较相邻的元素并交换位置,将最大(或最小)的元素逐步“冒泡”到最后(或最前)。

冒泡排序的具体步骤:

  1. 比较相邻元素:从数组的第一个元素开始,依次比较相邻的两个元素,如果顺序不对则交换它们的位置,确保较大的元素向后移动。
  2. 一轮冒泡:经过一轮比较和交换操作,最大的元素会沉到数组的最后位置。重复这个过程,直到没有需要交换的元素为止。
  3. 多轮冒泡:重复进行上述步骤,每次冒泡操作都会将当前未排序部分的最大元素移动到正确的位置。经过多轮冒泡,整个数组就会逐步有序。
  4. 优化:可以在每一轮冒泡中记录是否有元素交换的标志,如果某一轮没有元素交换,说明数组已经有序,可以提前结束排序。

下面是一个简单的示例,演示冒泡排序的步骤:

假设要排序的数组为:[5, 3, 8, 2, 1]

  • 第一轮冒泡:比较并交换,数组变为 [3, 5, 2, 1, 8]
  • 第二轮冒泡:比较并交换,数组变为 [3, 2, 1, 5, 8]
  • 第三轮冒泡:比较并交换,数组变为 [2, 1, 3, 5, 8]
  • 第四轮冒泡:比较并交换,数组变为 [1, 2, 3, 5, 8]

经过四轮冒泡排序,数组变为有序状态:[1, 2, 3, 5, 8]。

2.2 代码示例

//交换
void Swap(int* p, int* q)
{
	int tmp = *p;
	*p = *q;
	*q = tmp;
}

//冒泡排序
void BubbleSort(int* a, int n)
{
	for (int i = 0; i < n - 1; i++)
	{
		//标志位,用于记录本轮是否发生了元素交换
		bool flag = false;
		for (int j = 0; j < n - i - 1; j++)
		{
			if (a[j + 1] < a[j])
			{
				//交换相邻元素
				Swap(&a[j + 1], &a[j]);
				flag = true;
			}
		}
		//如果本轮没有发生元素交换,说明数组已经有序,提前结束排序
		if (!flag)
			break;
	}
}

//打印
void PrintSort(int* a, int n)
{
	for (int i = 0; i < n; i++)
	{
		printf("%d ", a[i]);
	}
	printf("\n");
}

//测试
int main()
{
	int a[] = { 1, 5, 7, 9, 0, 2, 4, 8, 3, 8, 6 };
	BubbleSort(a, sizeof(a) / sizeof(int));
	PrintSort(a, sizeof(a) / sizeof(int));

	return 0;
}

2.3 冒泡排序的特性总结

  1. 时间复杂度:冒泡排序的平均时间复杂度为O(n^2),最坏情况下为O(n^2),最好情况下为O(n)。因此,冒泡排序对于大规模数据集并不是一个高效的选择。
  2. 空间复杂度:冒泡排序的空间复杂度为O(1),因为它只需要一个额外的临时变量来进行元素交换。
  3. 稳定性:冒泡排序是一种稳定的排序算法,相同元素的相对位置在排序前后不会改变。
  4. 适用场景:由于冒泡排序的效率较低,通常不推荐在实际应用中使用。但对于小规模数据集或者教学目的,冒泡排序是一个很好的入门算法。
  5. 优缺点:冒泡排序的优点是实现简单,代码易于理解;缺点是效率低下,不适用于大规模数据集。

3.快速排序

3.1 基本思想

快速排序是Hoare于1962年提出的一种二叉树结构的交换排序方法,它的核心思想是通过分治法来实现排序。

具体步骤如下:

  1. 选择一个基准值(通常选择第一个元素)。
  2. 将小于基准值的元素移到基准值的左边,将大于基准值的元素移到基准值的右边。
  3. 对基准值左右两侧的子数组分别递归地进行快速排序。

将区间按照基准值划分为左右两半部分的常见方式有:

🌵hoare版本

思想步骤:

  1. 选择一个基准元素(通常选择数组的中间元素)。
  2. 使用两个指针,一个指向数组的起始位置,另一个指向数组的末尾位置。
  3. 右指针向左移动,直到找到一个小于基准元素的元素。
  4. 左指针向右移动,直到找到一个大于基准元素的元素。
  5. 左右指针分别找到元素后,交换它们各自位置的数值。
  6. 重复步骤3到步骤5,直到左指针和右指针相遇,将该位置的值与基准元素位置的值交换。
  7. 此时基准元素左边的元素都小于等于它,右边的元素都大于等于它。
  8. 递归地对基准元素左右两边的子数组进行快速排序。

//交换
void Swap(int* p, int* q)
{
	int tmp = *p;
	*p = *q;
	*q = tmp;
}

//快速排序
void QuickSort(int* a, int begin, int end)
{
	//如果子数组长度为1或0,则直接返回
	if (begin >= end)
		return;

	//初始化左右指针和基准值
	int left = begin, right = end;
	int keyi = begin;

	while (left < right)
	{
		//从右向左找第一个小于基准值的元素
		while (left < right && a[right] >= a[keyi])
		{
			--right;
		}
		//从左向右找第一个大于基准值的元素
		while (left < right && a[left] <= a[keyi])
		{
			++left;
		}
		//将找到的元素位置进行交换
		Swap(&a[right], &a[left]);
	}
	//将基准元素放到正确的位置上
	Swap(&a[left], &a[keyi]);
	keyi = left;
	//递归的对基准元素左右两侧的子数组进行快速排序
	QuickSort(a, begin, keyi - 1);
	QuickSort(a, keyi + 1, end);
}

//打印
void PrintSort(int* a, int n)
{
	for (int i = 0; i < n; i++)
	{
		printf("%d ", a[i]);
	}
	printf("\n");
}

//测试
int main()
{
	int a[] = { 1, 5, 7, 9, 0, 2, 4, 8, 3, 8, 6 };
	QuickSort(a, 0, sizeof(a) / sizeof(int) - 1);
	PrintSort(a, sizeof(a) / sizeof(int));

	return 0;
}

🌵挖坑法

思想步骤:

  1. 先将第一个数据存放在临时变量key中,形成一个坑位。
  2. right开始向前移动,找比key位置小的值,找到后将right位置的值放进坑里,此时right位置作为新的坑。
  3. left开始向后移动,找比key位置大的值,找到后将left位置的值放进坑里,此时left位置作为新的坑。
  4. right接着向前找,left接着向后找,直到leftright相遇。
  5. key放入相遇时的坑里,排序完毕。

// 挖坑法
int PartSort(int* a, int begin, int end)
{

	//选择第一个元素作为基准元素
	int key = a[begin];
	int hole = begin;

	while (begin < end)
	{
		//从右往左找到第一个小于基准元素的元素
		while (begin < end && a[end] >= key)
		{
			--end;
		}
		//将找到的元素填入左边的坑
		a[hole] = a[end];
		//更新坑的位置
		hole = end;

		//从左往右找到第一个大于基准元素的元素
		while (begin < end && a[begin] <= key)
		{
			++begin;
		}
		//将找到的元素填入右边的坑
		a[hole] = a[begin];
		//更新坑的位置
		hole = begin;
	}
	//将基准元素放入最终的坑
	a[hole] = key;

	return hole;
}

void QuickSort(int* a, int begin, int end)
{
	//如果子数组长度为1或0,则直接返回
	if (begin >= end)
		return;

	int keyi = PartSort(a, begin, end);
	QuickSort(a, begin, keyi - 1);
	QuickSort(a, keyi + 1, end);
}

//打印
void PrintSort(int* a, int n)
{
	for (int i = 0; i < n; i++)
	{
		printf("%d ", a[i]);
	}
	printf("\n");
}


//测试
int main()
{
	int a[] = { 1, 5, 7, 9, 0, 1, 2, 4, 8, 1, 3, 8, 6 };
	QuickSort(a, 0, sizeof(a) / sizeof(int) - 1);
	PrintSort(a, sizeof(a) / sizeof(int));

	return 0;
}

 

🌵前后指针版本

思想步骤:

  1. 选择一个基准元素作为key(通常选择数组的第一个元素)。
  2. 使用两个指针,前指针prev指向序列开头,后指针cur指向prev指针的后一个位置。
  3. 判断cur指针指向的数据是否小于key,若小于,则prev指针后移一位(注意:prev只有在cur找到比key小的数时才加1),并将cur指向的内容与prev指向的内容交换,然后cur指针++。若大于,则cur指针++。
  4. 依次类推直到cur遍历完整个数组,最后将prev位置的值与key位置的值进行交换,则完成单趟排序。

//交换
void Swap(int* p, int* q)
{
	int tmp = *p;
	*p = *q;
	*q = tmp;
}

//前后指针法
int PartSort(int* a, int begin, int end)
{
	int key = begin;

	int prev = begin;
	int cur = prev + 1;

	while (cur <= end)
	{
		//如果cur位置的值小于key位置的值,
		//并且prev位置++后的值如果和cur位置的值不相等,
		//就交换prev位置和cur位置的值
		if (a[cur] < a[key] && ++prev != cur)
			Swap(&a[prev], &a[cur]);

		++cur;
	}

	Swap(&a[prev], &a[key]);
	key = prev;

	return key;
}


void QuickSort(int* a, int begin, int end)
{
	//如果子数组长度为1或0,则直接返回
	if (begin >= end)
		return;

	int keyi = PartSort(a, begin, end);
	QuickSort(a, begin, keyi - 1);
	QuickSort(a, keyi + 1, end);
}


3.2 快速排序优化

🌻三数取中法选key

三数取中法是在快速排序算法中用于选择基准元素的一种策略。它的思想是从待排序数组中随机选择三个元素,然后取它们的中间值作为基准元素,这样可以尽量避免选择到极端的值作为基准元素,从而提高快速排序的效率。

使用三数取中法选择基准元素的具体步骤:

  1. 从待排序数组中随机选择三个元素,通常是选择数组的第一个元素、中间元素和最后一个元素。
  2. 比较这三个元素,找到它们的中间值作为基准元素。可以使用简单的比较排序或者其他方法来找到中间值。
  3. 将选定的基准元素放置在数组的最左边(或者其他位置),并记录其值。
  4. 接下来的快速排序过程中,使用这个选定的基准元素进行分区操作,将小于基准元素的元素放在左边,大于基准元素的元素放在右边。

使用三数取中法选择基准元素可以有效地避免选择到极端值作为基准元素,从而提高快速排序的效率,减少最坏情况下的时间复杂度。这种方法在实际应用中被广泛采用,用以提高快速排序的性能。

🍂代码示例:

//交换
void Swap(int* p, int* q)
{
	int tmp = *p;
	*p = *q;
	*q = tmp;
}

//三数取中
int GetMidi(int* a, int begin, int end)
{
	//选择三个元素的中间值作为基准元素
	int midi = (begin + end) / 2;

	//确保 a[begin] <= a[midi] <= a[end]
	if (a[begin] < a[midi])
	{
		if (a[midi] < a[end])
			return midi;
		else if (a[begin] > a[end])
			return begin;
		else
			return end;
	}
	else //a[begin] > a[midi]
	{
		if (a[midi] > a[end])
			return midi;
		else if (a[begin] < a[end])
			return begin;
		else
			return end;
	}
}

//快速排序
void QuickSort(int* a, int begin, int end)
{
	//如果子数组长度为1或0,则直接返回
	if (begin >= end)
		return;

	int midi = GetMidi(a, begin, end);
	Swap(&a[midi], &a[begin]);

	//初始化左右指针和基准值
	int left = begin, right = end;
	int keyi = begin;

	while (left < right)
	{
		//从右向左找第一个小于基准值的元素
		while (left < right && a[right] >= a[keyi])
		{
			--right;
		}
		//从左向右找第一个大于基准值的元素
		while (left < right && a[left] <= a[keyi])
		{
			++left;
		}
		//将找到的元素位置进行交换
		Swap(&a[right], &a[left]);
	}
	//将基准元素放到正确的位置上
	Swap(&a[left], &a[keyi]);
	keyi = left;
	//递归的对基准元素左右两侧的子数组进行快速排序
	QuickSort(a, begin, keyi - 1);
	QuickSort(a, keyi + 1, end);
}

//测试
int main()
{
	int a[] = { 1, 5, 7, 9, 0, 2, 4, 8, 3, 8, 6 };
	QuickSort(a, 0, sizeof(a) / sizeof(int) - 1);
	PrintSort(a, sizeof(a) / sizeof(int));

	return 0;
}

3.4 快速排序的特性总结

  1. 时间复杂度:平均时间复杂度为O(N*logN),最坏情况下为O(N^2),最好情况下为O(N*logN)
  2. 空间复杂度:快速排序是一种原地排序算法,不需要额外的存储空间,空间复杂度为O(1)
  3. 稳定性:快速排序是一种不稳定的排序算法,即相同元素的相对位置可能会发生变化。
  4. 分治思想:快速排序使用分治思想,将数组分为两部分,分别对左右子数组进行排序。

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

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

相关文章

systemverilog中位的选择

常用的变量类型就是 reg 和 wire &#xff0c;这两种类型可以定义 一位的变量&#xff0c;也可以定义多位&#xff0c;其中 1 bit 的变量称为 标量(scalar)&#xff0c;多 bit 的变量称为 向量(vector)&#xff0c;如下所示&#xff1a; wire o_nor; // singl…

【JavaWeb】Day51.Mybatis动态SQL

什么是动态SQL 在页面原型中&#xff0c;列表上方的条件是动态的&#xff0c;是可以不传递的&#xff0c;也可以只传递其中的1个或者2个或者全部。 而在我们刚才编写的SQL语句中&#xff0c;我们会看到&#xff0c;我们将三个条件直接写死了。 如果页面只传递了参数姓名name 字…

Web前端框架/库/工具

前言 前端从步枪&#xff08;原生js&#xff09;到了半自动武器&#xff08;jQuery&#xff09;并进化为全自动武器&#xff08;三大框架&#xff08;angular&#xff0c;react&#xff0c;vue及其生态链&#xff09;&#xff09;。 常说工欲善其事必先利其器。对于那些想要提…

网络原理-IP协议

一、IP协议报头 版本号:用来表示IP协议的版本,现在常用的IP协议有两个版本,IPv4和IPv6&#xff0c;其他版本可能只存在于实验室中&#xff0c;并没有被广泛的使用。 首部长度:用来表示IP报头的长度,因为存在"选项"字段&#xff0c;所以IP报头是可变长的,此处单位为4…

B树和B+树试题解析

一、单项选择题 01&#xff0e;下图所示是一棵&#xff08;A ). A.4阶B树 B.3阶B树 C.4阶B树 D.无法确定 02.下列关于m阶B树的说法中&#xff0c;错误的是( C ). A.根结点至多有m棵子树 B.所有叶结点都在同一层次上 C.非叶结点至…

【MySQL】查询(进阶)

文章目录 前言1、新增2、聚合查询2.1聚合函数2.1.1count2.1.2sum2.1.3avg2.1.4max和min 2.2、GROUP BY子句2.3HAVING 3、联合查询/多表查询3.1内连接和外连接3.2自连接3.3子查询3.4合并查询 前言 在前面的内容中我们已经把查询的基本操作介绍的差不多了&#xff0c;接下来我们…

基于SpringBoot + Vue实现的学生心理咨询评估管理系统设计与实现+毕业论文+开题报告+答辩PPT

介绍 系统有管理员和用户。 管理员可以管理个人中心&#xff0c;用户管理&#xff0c;试题管理&#xff0c;试卷管理&#xff0c;考试管理等。用户参加考试。 学生心理咨询评估系统的登录流程&#xff0c;针对的角色就是操作员的操作角色。在登录界面需要的必填信息就是账号信…

创新案例|Amazon.com 2023 年营销策略:电子商务零售巨头商业案例研究

2022 年最后一个季度&#xff0c;亚马逊报告净销售额超过 1,492 亿美元。这种季节性峰值是亚马逊季度报告的典型特征&#xff0c;但增长是不可否认的&#xff0c;因为这是该公司有史以来最高的季度。毫无疑问&#xff0c;这家电商零售巨头继续引领电商增长。本文将介绍我们今天…

Flink学习(七)-单词统计

前言 Flink是流批一体的框架。因此既可以处理以流的方式处理&#xff0c;也可以按批次处理。 一、代码基础格式 //1st 设置执行环境 xxxEnvironment env xxxEnvironment.getEnvironment;//2nd 设置流 DataSource xxxDSenv.xxxx();//3rd 设置转换 Xxx transformation xxxDS.…

【春秋云镜】CVE-2023-43291 emlog SQL注入

靶场介绍 emlog是一款轻量级博客及CMS建站系统&#xff0c;在emlog pro v.2.1.15及更早版本中的不受信任数据反序列化允许远程攻击者通过cache.php组件执行SQL语句。 不感兴趣的可以直接拉到最后面&#xff0c;直接获取flag 备注&#xff1a;没有通过sql注入获取到flag&…

python多线程技术(Threading)

文章目录 前言一、多线程(Threading)是什么?二、threading库1.初识多线程2.增加新线程2.1 多线程的基本使用2.2 对多线程是同时进行的进行一个直观上的演示(非重点--理解是实时就行)2.3 thread.join()功能2.4 使用queue(队列)功能获取多线程的返回值(重要,这就是前面那…

MySql对于时间段交集的处理和通用实现方式(MyBatis-Plus)

问题&#xff1a;一般传统时间筛选是在[ 开始时间 → 结束时间 ]这个区间内的子集&#xff0c;也就是全包含查询方式&#xff0c;这种只会筛选一种情况。如果场景需要是开展一个活动&#xff0c;需要活动时间检索应该但凡包含就返回&#xff0c;也就是需要查询这个时间段有涉及…

Java的Hash算法及相应的Hmac算法

【相关知识】 加密算法知识相关博文&#xff1a;浅述.Net中的Hash算法&#xff08;顺带对称、非对称算法&#xff09;-CSDN博客 【出处与参考】 MessageDigest 类介绍、分多次调用update方法与一次性调用一致的说明引自&#xff1a; https://blog.csdn.net/cherry_chenr…

2024 IDM最新破解版及软件介绍

*IDM&#xff1a;信息时代的高效管理工具** 在快节奏的现代社会中&#xff0c;随着信息的爆炸式增长&#xff0c;如何高效、有序地管理信息成为每个人都需要面对的挑战。IDM&#xff0c;作为一种信息管理工具&#xff0c;正在逐渐受到人们的青睐。 IDM&#xff0c;全称Inform…

HAL STM32 I2C方式读取MT6701磁编码器获取角度例程

HAL STM32 I2C方式读取MT6701磁编码器获取角度例程 &#x1f4cd;相关篇《Arduino通过I2C驱动MT6701磁编码器并读取角度数据》&#x1f388;《STM32 软件I2C方式读取MT6701磁编码器获取角度例程》&#x1f4cc;MT6701当前最新文档资料&#xff1a;https://www.magntek.com.cn/u…

Scanpy(1)数据结构和样本过滤

注&#xff1a;主要讲述scanpy处理数据的结构、数据过滤&#xff08;生信领域&#xff09;和数据预处理&#xff08;和机器学习类似&#xff0c;但是又有不同。&#xff09; 1. Scanpy简介与安装 Scanpy 是一个可扩展的工具包&#xff0c;用于分析与 AnnData&#xff08;一种…

git 小记

一、 github新建仓库 git clone 。。。。。。。。。。。 &#xff08;增删查补&#xff0c;修改&#xff09; git add . git commit -m "修改” git push (git push main) 二、branch 分支 branch并不难理解&#xff0c;你只要想像将代码拷贝到不同目录…

ruoyi-vue前端的一些自定义插件介绍

文章目录 自定义列表$tab对象打开页签关闭页签刷新页签 $modal对象提供成功、警告和错误等反馈信息&#xff08;无需点击确认&#xff09;提供成功、警告和错误等提示信息&#xff08;类似于alert&#xff0c;需要点确认&#xff09;提供成功、警告和错误等提示信息&#xff08…

restful请求风格的增删改查-----修改and删除

一、修改&#xff08;和添加类似&#xff09; 前端&#xff1a; <script type"text/javascript">function update(){//创建user对象var user {id:$("#id").val(),username:$("#username").val(),password:$("#password").val…

排序 “贰” 之选择排序

目录 ​编辑 1. 选择排序基本思想 2. 直接选择排序 2.1 实现步骤 2.2 代码示例 2.3 直接选择排序的特性总结 3. 堆排序 3.1 实现步骤 3.2 代码示例 3.3 堆排序的特性总结 1. 选择排序基本思想 每一次从待排序的数据元素中选出最小&#xff08;或最大&#xff09;的一个…