NzN的数据结构--交换排序

        篇接上文,今天要学习的就是交换排序,这么励志的日更博主,你怎么能不三连一下呢?

目录

一、基本思想

二、冒泡排序

三、快速排序 

1. hoare版本

2. 挖坑法

3. 前后指针法

4. hoare版本优化

5. 非递归实现快速排序


一、基本思想

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

二、冒泡排序

 

void Swap(int* p1, int* p2)
{
	int tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}
void BubbleSort(int* a, int n)
{
	for (int j = 0; j < n - 1; j++)
	{
		for (int i = 1; i < n - j; i++)
		{
			int flag = 0;
			if (a[i - 1] > a[i])
			{
				Swap(&a[i - 1], &a[i]);
				flag = 1;//发生交换就把flag改成1
			}
			//说明数组本来就有序
			if (flag == 0)
				break;
		}
	}
}

冒泡排序的特性总结:

  • 容易理解
  • 时间复杂度:
  • 空间复杂度:
  • 稳定性:稳定

三、快速排序 

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

        快速排序递归实现的主框架与二叉树前序遍历规则非常像,在写递归框架时可根据二叉树前序遍历规则即可快速写出来,后序只需分析如何按照基准值来对区间中数据进行划分的方式即可。将区间按照基准值划分为左右两半部分的常见方式有:

1. hoare版本

//快速排序(hoare版本)
void Quicksort(int* a, int begin, int end)
{

	if (begin >= end)
	{
		return;
	}
	int left = begin;
	int right = end;
	int key = begin;//把begin的位置作为key
	//小于a[key]  a[key]  大于a[key](升序为例)
	while (left < right)
	{
		//循环里依旧需要加上left < right
		//防止right--和left++导致left < right不成立
		//当left找到大于key的,且right在小于key的位置,两个交换
		while (left < right && a[right] >= a[key])
		{
			right--;
		}
		while (left < right && a[left] <= a[key])
		{
			left++;
		}
		Swap(&a[right], &a[left]);
	}
	Swap(&a[left], &a[key]);
	key = left;//更新key所在的位置,a[key]此时元素位置固定
	//以key为分界,左右两边分别再次进行快速排序
	Quicksort(a, begin, key - 1);
	Quicksort(a, key + 1, end);
}

Q为什么left和right相遇时所在的位置一定比a[key]小?

A因为right先走,right负责找比a[key]小的位置,left跟right相遇时,right肯定已经停下来了,也就是right停在了比a[key]小的位置。

2. 挖坑法

void QuickSortHole(int* a, int begin, int end) 
{
	if (begin >= end) 
	{
		return; //递归结束条件
	}
	int key = a[begin]; //选取第一个元素作为key
	int left = begin;
	int right = end;
	//首元素作为坑位,把首元素取出,下标为0的位置变成坑位
	//right先走,找到小于key的,就把这个数放到首元素,right所在位置变成坑位
	//left再走,找到大于key的,放到坑位里,left所在位置再变成坑位
	//最后也能够达成 小于key  key  大于key
	while (left < right) {
		while (left < right && a[right] >= key) {
			right--;
		}
		a[left] = a[right]; //将找到的较小元素填到左边的坑里,此时右边形成一个新的坑

		while (left < right && a[left] <= key) {
			left++;
		}
		a[right] = a[left];
	}
	a[left] = key;
	QuickSortHole(a, begin, left - 1);
	QuickSortHole(a, left + 1, end);
}

3. 前后指针法

        这种方法是我们最为推荐的!!

void QuickSort_p(int* a, int begin, int end) 
{
	if (begin >= end) 
	{
		return;
	}
	//首元素作为a[key],a[key]保持不动
	//prev从首元素出发,cur从首元素的后一个元素开始走
	//cur≥prev,cur++
	//cur<prev,prev++,交换cur和prev的值,cur++
	//cur走到空的时候,比较prev所在位置的值和首元素a[key]
	//prev<a[key]就交换
	int prev = begin;
	int cur = begin + 1;
	int key = begin;
	while (cur <= end)
	{
		if (a[cur] < a[key] && ++prev != cur) 
		{
			Swap(&a[prev], &a[cur]);
		}
		++cur;
	}
	Swap(&a[key], &a[prev]);
	key = prev;
	QuickSortHole(a, begin, key - 1);
	QuickSortHole(a, key + 1, end);
}

4. hoare版本优化

        hoare版本在数组有序时为最坏情况,时间复杂度为 。因此可以在数组中随意选一个数作为a[key]。

void Quicksort(int* a, int begin, int end)
{

	if (begin >= end)
	{
		return;
	}
	//明确left不一定从0开始,end也不一定是数组的最后一个下标
	int left = begin;
	int right = end;
	//选[left,right]区间中的随机数做key
	int randi = rand() % (right - left + 1);//这一步得到的randi不一定在区间内
	randi += left;//能确保randi在区间内
	Swap(&a[left], &a[randi]);
	int key = left;
	//小于a[key]  a[key]  大于a[key](升序为例)
	while (left < right)
	{
		//循环里依旧需要加上left < right
		//防止right--和left++导致left < right不成立
		//当left找到大于key的,且right在小于key的位置,两个交换
		while (left < right && a[right] >= a[key])
		{
			right--;
		}
		while (left < right && a[left] <= a[key])
		{
			left++;
		}
		Swap(&a[right], &a[left]);
	}
	Swap(&a[left], &a[key]);
	randi = left;//更新key所在的位置,a[key]此时元素位置固定
	//以key为分界,左右两边分别再次进行快速排序
	Quicksort(a, begin, randi - 1);
	Quicksort(a, randi + 1, end);
}

        除此之外,还有一种更为经典的写法—三数取中法。

int Getmidi(int* a, int left, int right)
{
	//数组有序时,midi就是key,这样就解决了hoare版本的最坏情况
	int mid = (left + right) / 2;
	//比较begin、midi和end对应元素的大小,选择位于中间大小的作为a[key]
	if (a[left] < a[mid])
	{
		if (a[mid] < a[right])
			return mid;
		else if (a[left] > a[right])
			return left;
		else
			return right;
	}
	else //a[left] > a[mid]
	{
		if (a[mid] > a[right])
			return mid;
		else if (a[right] < a[left])
			return right;
		else
			return left;
	}
}

void Quicksort(int* a, int begin, int end)
{
	if (begin >= end)
	{
		return;
	}
	int left = begin;
	int right = end;
	//小区间走插入排序
	if (right - left + 1 < 15)
	{
		InsertSort(a + left, right - left + 1);
		//a+left指向小区间的起始位置
		//right-left+1是小区间长度
	}
	//三数取中
	int midi = Getmidi(a, left, right);
	Swap(&a[left], &a[midi]);
	int key = left;
	while (left < right)
	{
		while (left < right && a[right] >= a[key])
		{
			right--;
		}
		while (left < right && a[left] <= a[key])
		{
			left++;
		}
		Swap(&a[right], &a[left]);
	}
	Swap(&a[left], &a[key]);
	key = left;
	Quicksort(a, begin, key - 1);
	Quicksort(a, key + 1, end);
}

5. 非递归实现快速排序

        我们前面讲过,当我们需要把递归算法改成非递归算法时,经常会用到栈,这里就需要用到栈的内容。

//非递归实现快速排序
//深度较大时,递归效率很低,可能会出现栈溢出
//递归->非递归有两种方法:变成循环;使用栈
void QuicksortST(int* a, int left, int right)
{
	//核心:栈存区间,取栈顶区间进行单趟排序
	//然后右左子区间入栈
	ST st;
	STInit(&st);
	//先入右,再入左,把数组元素的首尾全部入栈
	STPush(&st, right);
	STPush(&st, left);

	while (!STEmpty(&st))
	{
		//先压入right,先出的就是left
		int begin = STTop(&st);
		STPop(&st);
		//再出的才是right
		int end = STTop(&st);
		STPop(&st);
		//单趟排序
		int prev = begin;
		int cur = begin + 1;
		int key = begin;
		while (cur <= end)
		{
			if (a[cur] < a[key] && ++prev != cur)
			{
				Swap(&a[prev], &a[cur]);
			}
			++cur;
		}
		Swap(&a[key], &a[prev]);
		key = prev;
		//key到达了正确的位置,对两边进行分别排序
		//对大于a[key]的数据进行排序
		if (key + 1 < end)
		{
			STPush(&st, key + 1);
			STPush(&st, end);
		}
		//对小于a[key]的数据进行排序
		if (key - 1 > begin) 
		{
			STPush(&st, key - 1);
			STPush(&st, begin);
		}
	}
	STDestroy(&st);
}

        以上就是交换排序的内容,大家下去重点自主实现快速排序,下一节我们向大家介绍归并排序!记得三连哦!!!

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

模拟移动端美团案例(react版)

文章目录 目录 概述 项目搭建 1.启动项目&#xff08;mock服务前端服务&#xff09; 2.使用Redux ToolTik(RTK)编写store(异步action) 3.组件触发action并渲染数据 一、渲染列表 ​编辑 二、tab切换类交互 三、添加购物车 四、统计区域功能实现 五、购物车列表功能实现 六、控制…

【Java集合进阶】泛型的通配符和综合练习

&#x1f36c; 博主介绍&#x1f468;‍&#x1f393; 博主介绍&#xff1a;大家好&#xff0c;我是 hacker-routing &#xff0c;很高兴认识大家~ ✨主攻领域&#xff1a;【渗透领域】【应急响应】 【Java】 【VulnHub靶场复现】【面试分析】 &#x1f389;点赞➕评论➕收藏 …

加速度JUSDO | 电子元器件商城行业调研及运营方案

一、行业背景与竞品分析 随着电子元器件行业的快速发展&#xff0c;线上元器件商城已成为行业交易的重要渠道。目前市场上存在多个知名的元器件商城&#xff0c;如立创、云汉芯城、贸泽商城等&#xff0c;它们都提供了丰富的元器件产品和便捷的线上交易服务。 立创商城&#x…

蓝桥杯第十三届c++大学B组详解

目录 1.九进制转十进制 2.顺子日期 3.刷题统计 4.修剪灌木 5.x进制的减法 6.统计子矩阵 7.积木画 8.扫雷 9.李白打酒 10.砍竹子 1.九进制转换十进制 题目解析&#xff1a;就是将2022的每一位拿出来乘以9的n-1次方的和就是最终答案。次方是从0开始的到n-1. #include &…

蓝桥杯物联网竞赛_STM32L071_16_EEPROM

仍然是没有考过的知识点 朴素的讲就是板子中一块不会因为断电重启而导致数值初始化的一片地址 要注意的是有时候容易把板子什么写错导致板子什么地址写坏了导致程序无法烧录&#xff0c;这个时候记得一直按flash键烧录&#xff0c;烧录时会报错&#xff0c;点击确定&#xff0…

什么是MOV视频格式?如何把MP4视频转MOV视频格式?

一&#xff0c;前言 当然可以&#xff0c;MP4视频可以转换为MOV格式。这两种格式都是常见的视频文件格式&#xff0c;它们都可以用于存储和播放视频内容。虽然它们的编码方式和特性有所不同&#xff0c;但使用合适的视频转换工具可以轻松地将MP4视频转换为MOV格式。 二&#…

【洛谷 P8804】[蓝桥杯 2022 国 B] 故障 题解(概率论+条件概率+贝叶斯公式)

[蓝桥杯 2022 国 B] 故障 题目描述 在软件或系统开发中&#xff0c;我们会遇到各种各样的故障。为了从故障现象反推故障原因&#xff0c;工程师们会总结一种叫做相关性矩阵的二维表格&#xff0c;来表示故障原因与故障现象之间的关系。比如: 其中每行表示一种故障原因&#x…

bugku-web-你从哪里来

这里就这一句话提示&#xff0c;问我是不是谷歌的&#xff1f; 用谷歌浏览器访问 没看见什么变化 抓包查看 没有变化 这时我想到爬虫中的反爬策略中有一种&#xff0c;判断请求的当前界面来判断用户的起始判断位置 这时抓取报文 GET / HTTP/1.1 Host: 114.67.175.224:1516…

Ollama教程——兼容OpenAI API:高效利用兼容OpenAI的API进行AI项目开发

相关文章: Ollama教程——入门&#xff1a;开启本地大型语言模型开发之旅 Ollama教程——模型&#xff1a;如何将模型高效导入到ollama框架 Ollama教程——兼容OpenAI API&#xff1a;高效利用兼容OpenAI的API进行AI项目开发 Ollama教程——兼容OpenAI API&#xff1a;高效利用…

阿里云服务器项目部署docker-compose+vue+redis+nginx+minio+springboot

1 阿里云服务器项目部署-单机部署 docker-compose 1.1 搭建背景 服务器 阿里云服务器 前端 vue 后端 springboot 服务 redis 、nginx、minio 都做单机模式部署,不做集群部署 博客内容参考了其他博文&#xff0c;会贴出来 1.2 <重要>端口开放前提说明 任何开放的端…

(3)(3.1) 英特尔Realsense深度摄像头(三)

文章目录 前言 10 系统概述 11 手动设置配套计算机 前言 本文介绍如何将英特尔 Realsense 深度摄像头(Intel Realsense Depth Camera)与 ArduPilot 配合使用&#xff0c;以实现避障(obstacle avoidance)。该方法使用在配套计算机上运行的 Python 脚本&#xff08;非 ROS&am…

【算法】模拟

个人主页 &#xff1a; zxctscl 如有转载请先通知 题目 前言1. 1576. 替换所有的问号1.1 分析1.2 代码 2. 495. 提莫攻击2.1 分析2.2 代码 3. 6. Z 字形变换3.1 分析3.2 代码 4. 38. 外观数列4.1 分析4.2 代码 5. 1419. 数青蛙5.1 分析5.2 代码 前言 模拟算法就是根据题目所给…

怎么开发一个预约小程序_一键预约新体验

预约小程序&#xff0c;让生活更便捷——轻松掌握未来&#xff0c;一键预约新体验 在快节奏的现代生活中&#xff0c;我们总是在不断地奔波&#xff0c;为了工作、为了生活&#xff0c;不停地忙碌着。然而&#xff0c;在这繁忙的生活中&#xff0c;我们是否曾想过如何更加高效…

【力扣】101. 对称二叉树

101. 对称二叉树 题目描述 给你一个二叉树的根节点 root &#xff0c; 检查它是否轴对称。 示例 1&#xff1a; 输入&#xff1a;root [1,2,2,3,4,4,3] 输出&#xff1a;true 示例 2&#xff1a; 输入&#xff1a;root [1,2,2,null,3,null,3] 输出&#xff1a;false 提示…

什么是云原生

什么是云原生 云原生的定义 aws&#xff1a; 云原生是在云计算环境中构建、部署和管理现代应用程序的软件方法。现代公司希望构建高度可伸缩、灵活和有弹性的应用程序&#xff0c;以便能够快速更新以满足客户需求。为此&#xff0c;他们使用了支持云基础设施上应用程序开发的现…

基于YOLOv9的道路缺陷检测,加入DCNv4、自适应阈值焦点损失提升检测精度

&#x1f4a1;&#x1f4a1;&#x1f4a1;本文内容&#xff1a;针对基于YOLOv9的道路缺陷检测进行性能提升&#xff0c;加入各个创新点做验证性试验。 DCNv4结合SPPELAN&#xff1a;mAP从原始的0.923 提升至0.935 自适应阈值焦点损失&#xff1a; mAP从原始的0.923 提升至0.93…

Mysql视图与事物与字符集实验

一 视图 1.视图的定义 视图是一个虚拟表&#xff0c;其内容由查询定义。 2.视图的优点 1&#xff09;视点集中 2&#xff09;简化操作 3&#xff09;定制数据 4&#xff09;分隔合并数据 5&#xff09;安全性好 3.语法格式及限定条件 1&#xff09;语法格式&#xff1…

基于java+springboot+vue实现的兴顺物流管理系统(文末源码+Lw)23-287

摘 要 传统信息的管理大部分依赖于管理人员的手工登记与管理&#xff0c;然而&#xff0c;随着近些年信息技术的迅猛发展&#xff0c;让许多比较老套的信息管理模式进行了更新迭代&#xff0c;货运信息因为其管理内容繁杂&#xff0c;管理数量繁多导致手工进行处理不能满足广…

【域适应】深度域适应常用的距离度量函数实现

关于 深度域适应中&#xff0c;有一类方法是实现目标域和源域的特征对齐&#xff0c;特征对齐的衡量函数主要包括MMD&#xff0c;MK-MMD&#xff0c;A-distance&#xff0c;CORAL loss&#xff0c; Wasserstein distance等等。本文总结了常用的特征变换对齐的函数定义。 工具 …

Vue3学习04 组件通信

Vue3学习04 组件通信 组件通信props 父 ↔ 子自定义事件 子 > 父mitt 任意组件间通信v-model 父↔子$attrs 祖↔孙$refs、$parent案例的完整代码ref注意点 provide、inject 祖↔孙piniaslot① 默认插槽② 具名插槽③ 作用域插槽 组件通信 Vue3组件通信和Vue2的区别&#xf…