【初阶数据结构】排序——交换排序

news2024/10/1 14:50:21

目录

  • 前言
  • 冒泡排序
  • 快速排序
    • Hoare版
    • 前后指针版
    • 优化
      • 三数取中法
      • 取随机数做基准值
      • 小区间优化
    • 快排非递归版

请添加图片描述

前言

对于常见的排序算法有以下几种:
在这里插入图片描述
下面这节我们来看交换排序算法。

冒泡排序

基本思想
在待排序序列中,每一次将相邻的元素进行两两比较,将较大(升序)或者较小(降序)的数字往后走每走完一轮最大或者最小的数都会在最后面

排序过程:
我们给一个要排序的序列,并想让其升序排序:

int arr[] = {5, 3, 9, 6, 2, 4, 7, 1, 8};

在这里插入图片描述
在这里插入图片描述
  我们每排完一趟就会有在剩余待排序序列中的最大值被放到最后。

  我们就可以用两层循环来控制。

  • 外循环控制冒泡排序的趟数,或者叫做轮数,由上图可以看出有n个数,则要排n-1轮
  • 内循环:控制每一轮排序时两两之间的比较,可以发现,随着轮数的增加,每排好一个数,两两之间的比较次数就会少一次。最开始的比较次数同样为n-1次。

由此我们可写得循环为:

for(int i = 0; i < n - 1; i++)
{
	for(int j = 1; j < n - i; j++)//最开始的比较次数共n-1次
	{
	//......
	}
}

完整代码:

void Swap(int* px, int* py)
{
	int tmp = *px;
	*px = *py;
	*py = tmp;
}
void BubbleSort(int* a, int n)
{
	for(int i = 0; i < n - 1; i++)
	{
		for(int j = 1; j < n - i; j++)//最开始的比较次数共n-1次
		{
			if(a[i - 1] > a[i]
			{
				Swap(a[i - 1],a[i]);
			}
		}
	}
}

  排序代码已经结束了,但是我们通过第二个图可以发现,到后面已经有序了,但还是在进行判断冒泡,因此我们可以对上述程序进行改良:
  多增加一个变量exchange = 0,如果它进行了排序,则exchange = 1,如果没有排序则exchange = 0,证明此时已经完成了排序,则直接跳出循环即可。
改良后的排序代码:

void BubbleSort(int* a, int n)
{
	for(int i = 0; i < n - 1; i++)
	{
		int exchange = 0;
		for(int j = 1; j < n - i; j++)
		{
			if(a[i - 1] > a[i]
			{
				exchange = 1;
				Swap(a[i - 1],a[i]);
			}
		}
		if(exchange == 0)//没有变化证明已经有序了,直接结束即可
			break;
	}
}

冒泡排序特性总结:

  • 时间复杂度:O(N^2^)
  • 空间复杂度:O(1)
  • 稳定性:稳定

快速排序

  快速排序是Hoare于1962年提出的一种二叉树结构的交换排序算法。

Hoare版

基本思想
1.任取待排序元素序列中的的某元素作为基准值,并定义两个指针分别指向序列的开始结尾,遍历该数据,根据规则进行交换以及指针的移动直至两指针相遇。
2.此时将基准值两指针相遇指向的值交换,此时基准值就到了正确的位置。
3.此时将该序列分成了左右两边,左边序列的数都比此基准值右边序列的数都比此基准值
4.再重复上述步骤,可将所有数排好。

其实也就是递归。

当说可能不太清楚,下面我们通过图来解释:
在这里插入图片描述
在这里插入图片描述

总体代码如下:

void Swap(int* px, int* py)
{
	int tmp = *px;
	*px = *py;
	*py = tmp;
}

void QuickSort1(int* a, int left, int right)
{
	if (left >= right)//递归结束条件,如果区间中只有一个值或不存在
		return;
	int begin = left, end = right;
	int key = left;//用的都是下标
	while (left < right)
	{
		while (left < right && a[right] >= a[key])//让右指针先走,右指针要找到比a[key]小的
			right--;
		while (left < right && a[left] <= a[key])//左指针后走
			left++;
		Swap(&a[left], &a[right]);
	}
	//此时left和right相遇
	Swap(&a[left], &a[key]);
	//更新key的下标
	key = left;
	//分了左右两个区间[begin, key-1] key [key+1, end]
	//递归,使剩余区间都按上面操作
	QuickSort1(a, begin, key - 1);
	QuickSort1(a, key + 1, end);
}

拓展:我们最后是让left和right相遇所指向的值与基准值交换,我们是要升序排序,这也就是二者相遇指向的值必定小于基准值,这是为什么呢?
原因如下:

  1. 很重要的一点就是右边先走保证的,二者相遇无非就两种情况,一种是left遇上right,另一种就是right遇上left。下面来分别讨论:
  2. left遇上right:right先走,当right走到比key小的值就停了下来,left再走,要找比key大的值,但是它找不到,一直走就会和right想遇,此时相遇所指向的值就比key小。
  3. right遇上left:如果第一轮二者就相遇:right先走,right没有找到比key小的值,就一路左移,遇到left,也就是key的位置。
  4. right遇上left:二者第一轮以后相遇,此时二者是经过第一轮的交换,也就是left此时指向的位置比key小,right先走,它没有找到比key小的值,一直左移就和left相遇,此时指向的还是比key小的值。

因此,只要保证让right先走,在升序时就能保证相遇时所指向的值小于基准值。

前后指针版

给定两个指针,一个prev指向key位置处,另一个cur指向prev的下一个位置。
基本思想

  1. a[cur] >= a[key],++cur;
  2. a[cur] < a[key],++prev,交换prev和cur位置的值,++cur;

在这里插入图片描述
  通过上述步骤可以发现,prev和cur之间的数都是比基准值大的,也就是通过不断交换,将比基准值大的数都被放在了prev和cur之间,则当cur走到最后时,二者之间就都是比cur大的了。
后面的同样根据上述步骤重复进行即可得到。
具体代码如下:

void QuickSort2(int* a, int left, int right)
{
	if (left >= right)//递归结束条件,如果区间中只有一个值或不存在
		return;
	int prev = left, cur = left + 1;
	int key = left;
	while (cur <= right)
	{
		if (a[cur] < a[key])
		{
			++prev;
			Swap(&a[prev], &a[cur]);
			cur++;
		}
		else
			cur++;
	}
	Swap(&a[key], &a[prev]);
	key = prev;
	//分了左右两个区间[begin, key-1] key [key+1, end]
	//递归,使剩余区间都按上面操作
	QuickSort2(a, left, key - 1);
	QuickSort2(a, key + 1, right);
}

优化

  由于快排是一种递归式的排序,时间复杂度和堆排差不多,都是O(NlogN)。但如果序列有序或者接近有序时,此时时间复杂度就会高很多,就几乎达到了O(N^2^)。这是取基准值一直取的第一个导致的。

在这里插入图片描述
因此解决办法就是在每次对基准值的查找不要一直是第一个。下面有几种方式:

三数取中法

基本思想
在需要排序的序列中,找到最左边最右边以及中间的三个数,将这三个数比较大小,取中间大的数字。并将此数字作为基准值,然后将其与最左边的数交换,使基准值保持在最左边的位置,便于后续遍历。

适用序列:有序或接近有序的情况。
核心:找到中间大的数。
在这里插入图片描述
具体代码如下:

int GetMid(int* a, int left, int right)//三数取中
{
	int mid = (left + right) / 2;
	if (a[left] < a[mid])
	{
		if (a[mid] < a[right])
			return mid;
		else if (a[left] < a[right])//a[mid]>=a[right]
			return right;
		else
			return left;
	}
	else//a[left]>=a[mid]
	{
		if (a[right] < a[mid])
			return mid;
		else if (a[left] < a[right])//a[mid]<=a[right]
			return left;
		else
			return right;
	}
}
void QuickSort1(int* a, int left, int right)
{
	if (left >= right)//递归结束条件,如果区间中只有一个值或不存在
		return;
	int begin = left, end = right;
	//找到中间的数
	int mid = GetMid(a, left, right);
	//与最左边的值交换
	Swap(&a[mid], &a[left]);
	int key = left;//用的都是下标
	//......
}

取随机数做基准值

  我们不想每次都取最左边的值做基准值,那我们在[left, right]此区间内找随机值即可。思想还是比较简单的。
具体代码如下:

void QuickSort1(int* a, int left, int right)
{
	if (left >= right)//递归结束条件,如果区间中只有一个值或不存在
		return;
	int begin = left, end = right;
	//要使得所取的随机数在[left, right]区间中
	int randi = rand() % (right - left +1);
	//left不一定为0
	randi += left;
	//与最左边的值交换
	Swap(&a[randi], &a[left]);
	int key = left;//用的都是下标
	//......
}

小区间优化

  正如堆所示,最后两到三层的节点数量占了整个的百分之八十左右,此时我们用递归消耗是比较大的,因此我们可以在最后的区间内使用插入排序。减少消耗
代码如下:

void QuickSort1(int* a, int left, int right)
{
	if (left >= right)//递归结束条件,如果区间中只有一个值或不存在
		return;
	int begin = left, end = right;
	//小区间优化
	if (right - left + 1 < 10)
	{
		InsertSort(a + left, right - left + 1);
	}
	else
	{
		//要使得所取的随机数在[left, right]区间中
		int randi = rand() % (right - left + 1);
		//left不一定为0
		randi += left;
		//与最左边的值交换
		Swap(&a[randi], &a[left]);
		/*int mid = GetMid(a, left, right);
		Swap(&a[mid], &a[left]);*/
		int key = left;//用的都是下标
		//.........
		QuickSort1(a, begin, key - 1);
		QuickSort1(a, key + 1, end);
	}
	
}

快排特性总结:

  1. 时间复杂度:O(NlogN),每趟确定的元素呈现指数增长。
  2. 空间复杂度:O(logN),递归是需要用的栈空间
  3. 稳定性:不稳定

快排非递归版

  将递归转化为非递归,可以借助循环来解决。
  我们可以借助栈后进先出的思想来模拟递归区间的使用。
如图:
在这里插入图片描述
  判断是否入栈:看区间是否有两个以上的值,如果只有一个值就没有必要入栈排序了。
  判断while循环结束的条件:栈不为空则继续,为空则结束。
代码如下:

#include"Stack.h"
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);//左是后进的,所以取出来就是begin
		STPop(&st);

		int end = STTop(&st);
		STPop(&st);

		// 单趟
		int keyi = begin;
		int prev = begin;
		int cur = begin + 1;

		while (cur <= right)
		{
			if (a[cur] < a[key])
			{
				++prev;
				Swap(&a[prev], &a[cur]);
				cur++;
			}
			else
				cur++;
		}
		Swap(&a[keyi], &a[prev]);
		keyi = prev;
		// [begin, keyi-1] keyi [keyi+1, end] 
		if (keyi + 1 < end)//如果区间有两个值以上则入区间
		{
			STPush(&st, end);
			STPush(&st, keyi + 1);
		}
		if (begin < keyi-1)
		{
			STPush(&st, keyi-1);
			STPush(&st, begin);
		}
	}

	STDestroy(&st);//要记得把栈销毁
}

感谢大家观看,如果大家喜欢,希望大家一键三连支持一下,如有表述不正确,也欢迎大家批评指正。
请添加图片描述

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

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

相关文章

CSS内边距

内边距&#xff08;padding&#xff09;是指元素内容区与边框之间的区域&#xff0c;与外边距不同&#xff0c;内边距会受到背景属性的影响。您可以通过下面的属性来设置元素内边距的尺寸&#xff1a; padding-top&#xff1a;设置元素内容区上方的内边距&#xff1b;padding-…

2024-09-06 深入JavaScript高级语法十六——JS的内存管理和闭包

目录 1、JS内存管理1.1、认识内存管理1.2、JS的内存管理1.3、JS的垃圾回收1.3.1、常见的 GC 算法 - 引用计数1.3.2、常见的 GC 算法&#xfe63;标记清除 2、JS闭包2.1、JS中函数是一等公民2.2、JS中闭包的定义2.3、闭包的访问过程2.4、闭包的内存泄漏2.5、JS闭包内存泄漏案例2…

数据分析-28-交互式数据分析EDA工具和低代码数据科学工具

文章目录 1 数据分析的七步指南1.1 第一步:问题定义和数据采集1.2 第二步:数据清洗和预处理1.3 第三步:数据探索和分析1.4 第四步:模型建立和分析1.5 第五步:数据可视化1.6 第六步:结果解释和报告1.7 第七步:部署和维护1.8 基础的数据分析库1.9 低代码数据科学工具2 EDA…

yjs09——pandas介绍及相关数据结构

1.什么是pandas 同样&#xff0c;pandas、matplotlib、numpy是python三大库&#xff0c;pandas就像是把matplotlib和numpy结合在一起&#xff0c;让数据以“表格”的形式表现出来&#xff0c;是一个强大的数据处理和分析库&#xff0c;它建立在NumPy库之上&#xff0c;提供了高…

笔试-笔记

前言 记录一下自己遇到的笔试题 1.(单选)下列链表中&#xff0c;其逻辑结构属于非线性结构的是() A.二叉链表 B.双向链表 C.循环链表 D.带链的的栈 解析&#xff1a; 常见线性结构&#xff1a;线性表&#xff0c;栈&#xff0c;队列&#xff0c;双队列&#xff0c;串&…

05-函数传值VS传引用

函数传值 一、没法改变值的方式&#xff1a; 一个变量拷贝到另一个变量, 这种形式的函数调用被称为: 传值调用 局部变量的生命周期在函数的运行期间会一直存在. void Increment(int a)//假设一个 x(只是为了验证实参会被映射到形参这件事情),a的值会被拷贝到x {a a 1; //1…

【d57】【sql】1661. 每台机器的进程平均运行时间

思路 一方面考察自连接&#xff0c;另一方面考察group by 这里主要说明 group by 用法&#xff1a; 1.在 SQL 查询中&#xff0c;GROUP BY 子句用于将结果集中的行分组&#xff0c;目的通常就是 对每个组应用聚合函数&#xff08;如 SUM(), AVG(), MAX(), MIN(), COUNT() 等…

如何理解业务系统的复杂性

鹅厂万人热议&#xff5c;如何理解业务系统的复杂性&#xff1f;-腾讯云开发者社区-腾讯云 腾小云导读 业务系统复杂性一直是令开发者头痛的问题。复杂的不是增加一个需求需要耗费多少时间&#xff0c;而是在增加一个需求后带来的蝴蝶效应&#xff1a;其它功能会不会受到影响、…

MES数据的集成方式

为了实现与其他关键系统的数据共享和协同工作&#xff0c;不同的集成方式应运而生。MES系统与其他系统的常见集成模式&#xff0c;包括封装接口调用模式、直接集成模式、数据聚合模型、中间件集成模式以及XML的信息集成模式等。 1. 封装接口调用模式 封装接口调用是一种常见的…

防反接电路设计

方案1 串联二极管&#xff0c; 优点&#xff1a;成本低、设计简单 缺点&#xff1a;损耗大&#xff0c;P ui 方案2 串联自恢复保险丝 当电源反接的时候&#xff0c;D4导通&#xff0c;F2超过跳闸带你留&#xff0c;就会断开&#xff0c;从而保护了后级电路 方案3 H桥电路…

修改ID不能用关键字作为ID校验器-elementPlus

1、校验器方法 - forbiddenCharValidator const idUpdateFormRef ref(null); const forbiddenCharValidator (rule, value, callback) > {const forbiddenCharacters [as,for,default,in,join,left,inner,right,where,when,case,select];for (let forbiddenCharacter o…

劳动与科技、艺术结合更好提高劳动教育意义

在中小学教育中&#xff0c;劳动教育是培养学生基本生活技能和劳动习惯的重要环节。但当代的劳动教育不在单纯的劳动&#xff0c;而是劳动技能的提升与学习&#xff0c;通过学习劳动技能与实践活动&#xff0c;强化劳动教育与其他课程的融合&#xff0c;学生深刻理解劳动的意义…

python如何判断图片路径是否存在

1、在向文件夹中保存数据前&#xff0c;先判断该文件夹(路径)是否存在。 save_path /root/.../image/result if not os.path.exists(save_path):os.makedirs(save_path) 本来路径里只有到image文件夹的&#xff0c;执行完后会自动在image下创建result文件夹。 2、在打开某些图…

滑动窗口->dd爱框框

1.题目&#xff1a; 2.题解&#xff1a; 2.1为什么用滑动窗口优化&#xff1a; 因为元素都是大于0的 所以&#xff1a;当找到大于等于x的值时&#xff0c;right可以不用返回 两个指针都往后走&#xff1b;因此可以使用滑动窗口优化暴力解法 2.2&#xff1a;滑动窗口具体使用步…

骨传导耳机哪个品牌好用?盘点闭眼入都不踩雷的五大爆款机型!

骨传导耳机是智商税还是真有用&#xff1f;哪款骨传导耳机更值得购买&#xff1f;骨传导耳机作为市场中非常热门的机型&#xff0c;相信很多人都想入手一款&#xff0c;但面对市面鱼龙混杂的耳机品牌&#xff0c;往往不知道从何下手&#xff0c;不过市场重确实存在不少劣质产品…

ubutun nginx 安装和解决端口占用问题

目录 一、删除已有nginx 二、安装nginx 三、端口占用问题 分析问题 解决方法&#xff1a;更换默认端口 nginx是一个高性能的 HTTP 和反向代理 web 服务器&#xff0c;同时也提供了 IMAP/POP3/SMTP 服务。是一款轻量级的 Web 服务器/反向代理服务器及电子邮件&#xff08;I…

Sqoop实战-- Sqoop的Job任务、增量导入、数据格式转换与Lombok的使用指南

数据传输是任何数据驱动型组织的关键时刻。Apache Sqoop 在促进关系型数据库和Hadoop之间的高效数据传输方面表现出色&#xff0c;使其成为大数据工作流程中不可或缺的工具。本文将详细介绍如何使用Sqoop执行Job任务以及进行增量导入&#xff0c;如何在HDFS上指定数据存储格式&…

031集——文本文件按空格分行——C#学习笔记

如下图&#xff0c;读取每行文本&#xff0c;每行文本再按空格分开读取一个字符串&#xff0c;输出到另一个文本&#xff1a; CAD环境下&#xff0c;代码如下&#xff1a; using Autodesk.AutoCAD.DatabaseServices; using Autodesk.AutoCAD.Runtime; using System; using Sys…

如何使用ssm实现白云会议管理系统+vue

TOC ssm741白云会议管理系统vue 第1章 绪论 1.1 选题动因 到现在为止&#xff0c;互联网已经进入了千家万户&#xff0c;最普通的平民百姓也有属于自己的智能设备&#xff0c;计算机各种技术的储备也是相当的丰富&#xff0c;并且实现也是没有难度&#xff0c;各行各业&…

Gpt4.0最新保姆级教程开通升级

如何使用 WildCard 服务注册 Claude3 随着 Claude3 的震撼发布&#xff0c;最强 AI 模型的桂冠已不再由 GPT-4 独揽。Claude3 推出了三个备受瞩目的模型&#xff1a;Claude 3 Haiku、Claude 3 Sonnet 以及 Claude 3 Opus&#xff0c;每个模型都展现了卓越的性能与特色。其中&a…