【六大排序详解】终篇 :冒泡排序 与 快速排序

news2024/12/23 2:00:35

终篇 :冒泡排序 与 快速排序

  • 1 冒泡排序
    • 1.1 冒泡排序原理
    • 1.2 排序步骤
    • 1.3 代码实现
  • 2 快速排序
    • 2.1 快速排序原理
      • 2.1.1 Hoare版本
        • 代码实现
      • 2.1.2 hole版本
        • 代码实现
      • 2.1.3 前后指针法
        • 代码实现
      • 2.1.4 注意
        • 取中位数
        • 局部优化
      • 2.1.5 非递归版本
        • 非递归原理
        • 代码实现
    • 2.2 特性总结
  • 谢谢阅读Thanks♪(・ω・)ノ
  • 下一篇文章见!!!

1 冒泡排序

1.1 冒泡排序原理

冒泡排序如同泡泡上升一样,逐个逐个向上冒,一个接一个的冒上去。两两比较,较大者(较小者)向后挪动。全部遍历一遍即可完成排序。
在这里插入图片描述

1.2 排序步骤

  1. 首先从头开始,两两相互比较。每次排好一个最大(最小)
  2. 然后在从头开始,两两比较 至已排序部分之前。
  3. 依次往复,全部遍历一遍。
  4. 完成排序。
    在这里插入图片描述

1.3 代码实现

以排升序为例


void BubbleSort(int* a, int n)
 {
	for (int i = 0; i < n; i++) //从头开始遍历
	{
	     //每次遍历 会排完一个 需排序部分减少 1 
		for (int j = 0; j < n - 1 - i;j++) 
		{   //结束条件 a[n-2] > a[n-1]
			if (a[j] > a[j + 1]) //如果大,向上冒
			{      
				int tmp = a[j];
				a[j] = a[j + 1];
				a[j + 1] = tmp;
			}
		}
	}
}

排序结果,非常顺利。
在这里插入图片描述
冒泡排序的特性总结:

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

2 快速排序

2.1 快速排序原理

快速排序是一种高效快速的算法,是Hoare于1962年提出的一种二叉树结构的交换排序方法,
其基本思想为:任取待排序元素序列中的某元素作为基准值,按照该排序码将待排序集合分割成两子序列,左子序列中所有元素均小于基准值,右子序列中所有元素均大于基准值,然后最左右子序列重复该过程,直到所有元素都排列在相应位置上为止。
根据其思想 ,便有递归版本 和 非递归版本。
递归版本中有 Hoare版本, Hole版本,前后指针版本
非递归版本使用 栈 来实现

2.1.1 Hoare版本

Hoare版本是一种非常巧妙的版本,其思路大致为(以排升序为例)

  1. 确定一个key值,然后右找较大值,左找较小值
  2. 交换,直到左右相遇,
  3. 相遇时, 相遇位置的值一定小于key值(取决于先找大还是先找小,先找大,则为较小值,否则反之),交换key 与 相遇位置的值。
  4. 此时满足左边都比key小,右边都比key大
  5. 然后再分别进行左部分和右部分的排序。
  6. 全部递归完毕,排序完成。
    在这里插入图片描述
代码实现
//交换函数
void Swap(int* p1, int* p2) {
	int tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}
//key 取中位数
int getmidi(int *a,int begin,int end) {
	int midi = (begin + end) / 2;
	if (a[midi] > a[begin]) {
		if (a[midi] < a[end]) return midi;
		else if (a[begin] > a[end]) return begin;
		else return end;
	}
	else {//a[midi]<a[begin]
		if (a[begin] < a[end])	return begin;
		else if (a[midi] > a[end]) return midi;
		else return end;
}

//Hoare版本快速排序
int PartSort1(int* a, int left, int right) {
	//取key 为首元素
	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]);
	}
	//将key与相遇位置值交换,
	//满足左边都比key小,右边都比key大
	Swap(&a[keyi], &a[left]);
	keyi = left;
}
//快速排序主体
void QuickSort(int* a, int begin, int end) {
	//递归实现
	if (begin >= end) return;
    // 定义左边,右边与key相应位置。
	int left = begin, right = end ;
	int keyi = begin;
	//该步骤优化十分重要。
	int midi = getmidi(a, begin, end);
	Swap(&a[left], &a[midi]);
	//排序
	int key = PartSort1(a, left, right);

	QuickSort(a, begin, key-1);
	QuickSort(a, key+1, end);
}

我们来看看运行效果。
在这里插入图片描述

2.1.2 hole版本

Hole版本即为挖坑法,是对Hoare版本的优化,避免了许多容易出现的错误。其基本思路为(排升序为例)

  1. 确定一个key值,在该处形成坑位
  2. 右找较大值,进入坑位,然后在该较大值处形成新的坑位
  3. 左找较小值,进入坑位,然后在该较小值处形成新的坑位。
  4. 。。。反复进行至相遇时,把key值放入该坑位。
  5. 此时满足左边都比key小,右边都比key大
  6. 然后再分别进行左部分和右部分的排序。
  7. 全部递归完毕,排序完成。
    在这里插入图片描述
代码实现

主体与上面的Hoare相同,这里提供挖坑法的函数部分。

int PartSort2(int* a, int left, int right) {
	int key = a[left]; //key取左值
	int holei = left;
    //开始排序
	while(left < right){
        //右找大
		while (a[right] >= key && right > left) {
			right--;
		}
        //进坑 挖坑
		a[holei] = a[right];
		holei = right;
        //左找小
		while (a[left] <= key && right > left) {
			left++;
		}
        //进坑 挖坑		
        a[holei] = a[left];
		holei = left;
	}
    //结束时,key进坑。完成排序
	a[holei] = key;
	return left;
}

2.1.3 前后指针法

前后指针算法是不同于上面两种的独特算法,较为简单。其基本思路为(以排升序为例):

  1. 首先取key值,并定义两个指针,分别指向当前位置与下一位置
  2. 如果cur 指向的值比key小,prev++。然后交换prev和cur指针指向的内容
  3. cur++;
  4. 直到cur到末位置。
  5. 交换key和prev指针指向的内容交换。
  6. 此时满足左边都比key小,右边都比key大
  7. 然后再分别进行左部分和右部分的排序。
  8. 全部递归完毕,排序完成。
    在这里插入图片描述
代码实现

主体与上面的Hoare相同,这里提供前后指针法的函数部分。

void Swap(int* p1, int* p2) {
	int tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}
int PartSort3(int* a, int begin, int end) {
	int key = a[begin];
	int prev = begin, cur = prev + 1;

	while (cur <= end) {
		if (a[cur] < key) {
			prev++;
			if (prev != cur)
				Swap(&a[cur], &a[prev]);
		}
		cur++;
	}
	Swap(&a[begin], &a[prev]);
	return prev;
}

2.1.4 注意

取中位数

接下来来看两组测试数据,一组为随机十万数据,一组为有序十万数据。
不取中位数版本 与 取中位数版本。
在这里插入图片描述
这是肉眼可见的性能提升,防止了再有序情况下的逐个遍历。因此取中位数是很重要的一步,当然一般情况下不会遇到最坏情况。

局部优化

根据二叉树的相关知识,最后一层包含50%数据,倒数第二层包含25%数据,倒数第三层包含12.5%数据。
第n层 递归 1 次 第 n-1 层 递归 2 次 第 n - 2 层 递归 4 次 … 第 1 层 递归 2^n 次
所以在进行绝大部分的排序后,如果继续进行递归会存在问题,此时递归次数非常多。所以我们进行局部优化,在数据小于10个(取决于具体数据)时改换为插入排序等稳定算法即可。

2.1.5 非递归版本

非递归算法通常要使用一些循环来达到全部遍历的目的。也使得 非递归版本 比 递归版本 更需要对“递归”的深入理解,这里快速排序的非递归版本使用栈来模拟递归过程

非递归原理

先看递归的实现过程,先对整体排,然后排左部分,排右部分。接着对左进行相同处理,对右进行相同处理。
这样的过程可以通过栈来实现(当然使用数组进行指定操作也可以)
在这里插入图片描述

栈里面依次存放了应该排序的部分,每次取出两个,来进行排序(注意取出顺序与存入顺序相反,若先入左 则先取的为右),排序完毕,存入左右部分的开始位置与结束位置,直到有序。
排序步骤

  1. 存入开始位置begin 结束位置end ,key值取左值。
  2. 依次出栈 记录右位置 right ,左位置 left(读取顺序很重要),排序 该部分
  3. 以key值分割左右两部分,压栈存入左部分的开始与结束位置,压栈存入右部分的开始与结束位置。(若left >= key不读取左部分 若 right<=key 不读取右部分)
  4. 依次出栈 记录右位置 right ,左位置 left(读取顺序很重要),排序 该部分
  5. 重复2 - 3步骤,直到栈为空。
  6. 完成排序
代码实现

需要使用栈的相应函数,栈的具体内容请看
栈相关知识


//非递归排序
void QuickSortNonR(int* a, int begin, int end) {
	//建立栈
	Stack s ;
	StackInit(&s);//初始化
	//压入开始与结束位置
	StackPush(&s, begin);
	StackPush(&s, end);
    //开始排序
	while (!StackEmpty(&s)) {//不为空就继续进行
	    //出栈读入右位置
		int right = StackTop(&s);
		//读取后删除
		StackPop(&s);
		//出栈读入左位置
		int left = StackTop(&s);
		//读取后删除
		StackPop(&s);
        //对该部分进行排序 这里使用前后指针法(使用三种其一即可)
		int keyi = PartSort3(a, left, right);
        //读取左部分 若left>=key不进行读入
		if (left < keyi) {
		   //入栈 
			StackPush(&s, left);
			StackPush(&s, keyi - 1);
		}
        //读取右部分 若right<=key不进行读入
		if (right > keyi) {
		   //入栈
			StackPush(&s, keyi + 1);
			StackPush(&s, right);
		}		
	}
}

2.2 特性总结

快速排序的特性总结

  1. 快速排序整体的综合性能和使用场景都是比较好的,所以才敢叫快速排序

  2. 时间复杂度:O(N*logN)
    在这里插入图片描述

  3. 空间复杂度:O(logN)

  4. 稳定性:不稳定
    总的来说快速排序的内容十分丰富。我个人感觉使用前后指针来实现快速排序比较简单。同时非递归版本可以让我们更深刻的认识递归过程。而且不同版本的性能大差不差,基本相同。

谢谢阅读Thanks♪(・ω・)ノ

下一篇文章见!!!

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

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

相关文章

springboot、spring-kafka、kafka-client的版本对应关系

在使用springboot集成kafka的时候需要注意springboot版本、引用的依赖spring-kafka版本和kafka中间件版本的对应关系&#xff0c;否则可能会因为版本不兼容导致出现错误。 1、含义说明&#xff08;摘自官网&#xff09; Spring Boot&#xff1a;是springboot的版本。Spring fo…

Element ui 改变el-transfer 穿梭框的大小

修改el-transfer 左右两个穿梭框的高度和宽度&#xff0c;具体效果如下正常大小的穿梭框修改之后的&#xff0c;主要在style中加上如下样式即可 /deep/ .el-transfer-panel{ width: 470px; /* 左右两个穿梭框的高度和宽度 */ height: 450px; } /deep/ .el-transfer-panel__li…

C语言编译器(C语言编程软件)完全攻略(第三十部分:Xcode简明教程(使用Xcode编写C语言程序))

介绍常用C语言编译器的安装、配置和使用。 三十、Xcode简明教程&#xff08;使用Xcode编写C语言程序&#xff09; 在 Mac OS X 下学习C语言使用 Xcode。Xcode 是由Apple官方开发的IDE&#xff0c;支持C、C、Objective-C、Swift等&#xff0c;可以用来开发 Mac OS X 和 iOS 上…

微信小程序使用mqtt开发可以,真机不行

以下可以解决我的问题&#xff0c;请一步一步跟着做&#xff0c;有可能版本不一样就失败了 一、下载mqtt.js 前往蓝奏云 https://wwue.lanzouo.com/iQPdc1k50hpe 下载好后将.txt改为.js 然后放入项目里 二、连接mqtt const mqtt require(../../utils/mqtt.min); let cli…

NB三人组(堆排序,归并排序,快速排序)(数据结构课设篇2,python版)(排序综合)

本篇博客主要详细讲解一下NB三人组排序&#xff0c;为什么叫NB三人组呢&#xff1f;因为他们的时间复杂度都为O(n log n)。第一篇博客讲解的是LowB三人组&#xff08;冒泡排序&#xff0c;插入排序&#xff0c;选择排序&#xff09;&#xff0c;第三篇博客会讲解其他排序&#…

Jenkins分布式实现: 构建弹性和可扩展的CI/CD环境!

Jenkins是一个流行的开源持续集成&#xff08;Continuous Integration&#xff0c;CI&#xff09;和持续交付&#xff08;Continuous Delivery&#xff0c;CD&#xff09;工具&#xff0c;它通过自动化构建、测试和部署过程&#xff0c;帮助开发团队更高效地交付软件。Jenkins的…

Linux——系统安全及应用

一、基本安全措施 1、系统账号清理 常见的非登录用户账号包括bin、daemon、 adm、lp、mail等。为了确保系统安全&#xff0c;这些用户账号的登录Shell通常是/ sbin/nologin&#xff0c;表示禁止终端登录&#xff0c;应确保不被人为改动。 //将非登陆用户的Shell设为/sbin/nolo…

STM32G030C8T6:USART串口通信(中断)

本专栏记录STM32开发各个功能的详细过程&#xff0c;方便自己后续查看&#xff0c;当然也供正在入门STM32单片机的兄弟们参考&#xff1b; 本小节的目标是&#xff0c;系统主频64 MHZ,采用高速外部晶振&#xff0c;通过芯片PB6,PB7 的USART1 口&#xff0c;实现串口通信。 原理…

Oracle数据恢复记录一 表数据的恢复

当我们误删/修改数据之后&#xff0c;要进行数据恢复&#xff0c;需要有数据库管理员权限才能实现&#xff0c;所以奉劝各位修改数据要好好确认&#xff0c;搞出异常来就很麻烦了。下面是一个数据恢复简单的例子&#xff1a; DML Sql 这里展示了修改的sql UPDATE XX_MES_PROC…

MySQL基础笔记(4)DQL数据查询语句

DQL用于查找数据库中存放的记录~ 目录 一.语法 二.基础查询 1.查询多个字段 2.设置别名 3.去除重复记录 三.条件查询 1.基础语法 2.常见条件 四.分组查询 1.聚合函数 2.语法 五.排序查询 六.分页查询 附注&#xff1a;DQL执行顺序 1.编写顺序 2.执行顺序 ​​​…

Reids原理及简单命令

目录 1.关系数据库与非关系型数据库 关系型数据库 非关系型数据库 关系型数据库和非关系型数据库区别 数据存储方式不同 扩展方式不同 对事务性的支持不同 总结&#xff1a; 2. Redis简介 什么是reids reids优点 reids使用场景&#xff1a; reids快的原因 Redis数…

RFID标签在汽车监管方面的应用与实施方案

RFID技术在汽车工业领域得到了广泛应用&#xff0c;主要体现在汽车资质证书远程监管系统的普及化&#xff0c;系统包括OBD接口监视器、车证监管箱、超高频读写设备、应用系统软件以及大数据采集与处理等组成部分。 在汽车物流监管方面&#xff0c;系统利用OBD接口监控车辆并实时…

无心剑汉英双语诗《一亩三分地》

一亩三分地 My Small World 二十四年世事变幻 拨号上网曾争分夺秒 如今不限流量肆意冲浪 大数据&#xff0c;云计算&#xff0c;人工智能 洗刷着世间各行各业 周围人&#xff0c;生活不断升级 房子&#xff0c;车子&#xff0c;妻子…… 换了一茬又一茬 洋溢着自豪的表情 而我…

乔布斯2005年在斯坦福大学的演讲稿

文章目录 乔布斯2005年在斯坦福大学的演讲稿英语原文译文 乔布斯2005年在斯坦福大学的演讲稿 英语原文 Stay Hungry. Stay Foolish. I am honored to be with you today at your commencement from one of the finest universities in the world. Truth be told, I never gradu…

修改安卓apk设置为安卓主屏幕(launcher)

修改安卓apk 将apk可以设置安卓主屏幕 原理&#xff1a; 将打包好的apk文件进行拆包增加配置文件在重新编译回apk包 需要得相关文件下载 解包 apktool :https://pan.baidu.com/s/1oyCIYak_MHDJCvDbHj_qEA?pwd5j2xdex2jar&#xff1a;https://pan.baidu.com/s/1Nc-0vppVd0G…

Unity真机Log工具 SRDebugger使用记录,GM布局管理

SRDebugger 官方文档安装及初始化常用设置选项布局选项快捷键选项高级设置 使用GM工具案例常用特性GM分组排序GM固定页签 官方文档 文档&#xff1a; https://www.stompyrobot.uk/tools/srdebugger/documentation/ 插件地址&#xff1a; https://assetstore.unity.com/package…

Python基础知识总结1-Python基础概念搞定这一篇就够了

时隔多年不用忘却了很多&#xff0c;再次进行python的汇总总结。好记性不如烂笔头&#xff01; PYTHON基础 Python简介python是什么&#xff1f;Python特点Python应用场景Python版本和兼容问题解决方案python程序基本格式 Python程序的构成代码的组织和缩进使用\行连接符 对象…

【深度学习每日小知识】数据增强

数据增强是通过对原始数据进行各种转换和修改来人工生成附加数据的过程&#xff0c;旨在增加机器学习模型中训练数据的大小和多样性。这对于计算机视觉领域尤为重要&#xff0c;因为图像经常被用作输入数据。 计算机视觉中的数据增强 数据增强的主要目标是解决过拟合问题&…

春招冲刺第一天:Excel入门

春招冲刺第一天 前言&#xff1a; 转行换方向了家人们&#xff0c;准备往数据分析那转了&#xff0c;实习我现在也找不到&#xff0c;打算先猛学两周技术&#xff0c;过完年再投简历了。 时间确实非常紧张&#xff0c;目前一天计划学8小时以上&#xff0c;主要参考视频——&g…

使用IDEA官方docker插件构建镜像

此方法同样适用于jetbrains系列的其他开发软件 在IDEA中&#xff0c;如果是maven项目&#xff0c;可以使用插件 <plugin><groupId>com.spotify</groupId><artifactId>docker-maven-plugin</artifactId><version>1.2.2</version> &…