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

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

  • 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♪(・ω・)ノ

下一篇文章见!!!

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

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

相关文章

负责任的人工智能与人机环境系统智能

负责任的人工智能是指在人工智能系统的设计、开发、管理、使用和维护过程中&#xff0c;所有相关的角色&#xff08;包括设计者、开发者、管理者、使用者、维护者等等&#xff09;都承担其行为的道义、法律和社会责任。这意味着这些角色需要确保人工智能系统的设计与使用符合伦…

网络安全B模块(笔记详解)- Web渗透测试

Web信息收集 1.通过Kali对服务器场景Linux进行Web扫描渗透测试(使用工具nikto,查看该命令的完整帮助文件),并将该操作使用命令中固定不变的字符串作为Flag提交; Flag:nikto -H 2.通过Kali对服务器场景Linux进行Web扫描渗透测试(使用工具nikto,扫描目标服务器8080端口,…

阻止持久性攻击改善网络安全

MITRE ATT&CK框架是一个全球可访问的精选知识数据库&#xff0c;其中包含基于真实世界观察的已知网络攻击技术和策略。持久性是攻击者用来访问系统的众多网络攻击技术之一;在获得初始访问权限后&#xff0c;他们继续在很长一段时间内保持立足点&#xff0c;以窃取数据、修改…

文章解读与仿真程序复现思路——电网技术EI\CSCD\北大核心《考虑多元不确定性和备用需求的微电网双层鲁棒容量规划》

本专栏栏目提供文章与程序复现思路&#xff0c;具体已有的论文与论文源程序可翻阅本博主免费的专栏栏目《论文与完整程序》 这个标题涉及微电网&#xff08;Microgrid&#xff09;的双层鲁棒容量规划&#xff0c;考虑了多元不确定性和备用需求。让我们逐步解读这个标题&#xf…

【软考中级-软件设计师】day1:CPU、数据的表示、校验码

考点分布目录 中央处理单元CPU 练习题 数据的表示 二进制转十进制 练习题 十进制转二进制 练习题 原码 练习题 反码 练习题 补码 练习题 练习题 移码 浮点数 练习题 奇偶校验 练习题 校验码 模2除法 循环冗余校验CRC 练习题 练习题 练习题 奇偶校验码 只…

docker kingbase

docker kingbase run 命令 docker run -tid \ -e ENABLE_CIyes \ -e NEED_STARTyes \ -e DB_MODEoracle \ -e DB_USERkingbase \ -e DB_PASSWORD123456 \ --privileged \ -p 4321:54321 \ -v /home/admin/SoftWare/volume/kingbase/userdata/data:/home/kingbase/userdata/da…

基于seatunnel实现mysql同步clickhouse验证

场景&#xff1a; 需求想要实现mysql同步到clickhouse&#xff0c;seatunnel部署见前面文档linux环境seatunnel安装运行-CSDN博客。 官方说明文档 Clickhouse | Apache SeaTunnel mysql同步配置 server-id1 log_bin/var/lib/mysql/bin.log binlog_formatROW #binlog-do-db 具…

astadmin安装querylist插件Puppeteer

我本来是想在linux服务器上安装&#xff0c;折腾了一天也没安装成功&#xff0c;由于急着用&#xff0c;就先做window10上安装了&#xff0c;以后有时间再研究centos7上安装 一 首先需要安装fastadmin 框架和querylist插件 这个大家可以自行安装&#xff0c;querylist安装地址…

B059-权限管理系统01

目录 知识点介绍项目演示项目搭建动态菜单查询分析(权限表分析)权限系统表分析角色模块pageInfopageHelper实现前端动态分页高级查询新增与修改删除角色 分配权限-表分析角色授权数据-一级和二级权限查询 知识点介绍 项目演示 准备数据库 准备工程auth_new tips&#xff1a;…

三极管组成的光控开关电路原理图

什么是光控开关 光控开关/光控时控器采用先进的嵌入式微型计算机控制技术&#xff0c;融光控功能和普通时控器两大功能为一体的多功能高级时控器&#xff08;时控开关&#xff09;&#xff0c;根据节能需要可以将光控探头&#xff08;功能&#xff09;与时控功能同时启用&…

【QT 自研上位机 与 ESP32下位机联调>>>串口控制GPIO-基础样例-联合文章】

【QT 自研上位机 与 ESP32下位机联调&#xff1e;&#xff1e;&#xff1e;串口控制GPIO-基础样例-联合文章】 1、概述2、实验环境3、 自我总结4、 实验过程1、验证上位机QT程序1、下载样例代码2、修改qt程序3、运行测试验证 2、验证下位机ESP32程序1、下载样例代码2、更改ESP3…

RocketMQ源码 发送消息源码分析

前言 DefaultMQProducer 是默认生产者组件&#xff0c;是生产者客户端中&#xff0c;绝大部分关于生产者和broker、nameSrv进行网络通信的功能入口。其中&#xff0c;包含发送各种形式&#xff08;同步、异步、事务、顺序&#xff09;的消息&#xff0c;针对发送消息部分的实现…

第11章 GUI Page462~476 步骤二十三 步骤二十四 Undo/Redo ②“添加操作”支持“Undo/Redo”

工程二 1.为AddAction类添加Undo() Redo() GetName()成员函数 2.实现AddAction类的Undo() Redo()函数 3.运行效果&#xff0c;但是日志窗口没有记录 原因&#xff1a;AddAction(EditAction* newAction)函数没有实现&#xff0c;另外参数是EditAction类型 所以我们还需要在基…

基于PyTorch的Transformer组件实现

最近看了不少介绍LLM工作原理的文章&#xff0c;发现每一篇都会试图跟读者讲明白作为baseline的Transformer架构到底长啥样。但是好像比较少有代码实现的示例和具体的例子帮助理解。于是自己也想尝试着写一篇含有代码实现和具体例子解释的文章&#xff0c;希望能够给喜欢编程朋…

金蝶Apusic应用服务器 loadTree JNDI注入漏洞

产品介绍 金蝶Apusic是一款企业级应用服务器&#xff0c;支持Java EE技术&#xff0c;适用于各种商业环境。 漏洞概述 由于金蝶Apusic应用服务器权限验证不当&#xff0c;使用较低JDK版本&#xff0c;导致攻击者可以向loadTree接口执行JNDI注入&#xff0c;远程加载恶意类&a…

C++学习day--25 俄罗斯方块游戏图像化开发

项目分析 项目演示、项目分析 启动页面 启动页面&#xff1a; 分析&#xff1a; 开发环境搭建 1&#xff09;安装vc2010, 或其他vs版本 2&#xff09;安装easyX图形库 代码实现: # include <stdio.h> # include <graphics.h> void welcome(void) { initgraph(55…

LINUX服务器防火墙nf_conntrack问题一例

一、故障现象 业务反馈服务异常,无法响应请求&#xff0c;从系统日志 dmesg 或 /var/log/messages 看到大量以下记录&#xff1a;kernel: nf_conntrack: table full, dropping packet. 二、问题分析 业务高峰期服务器访问量大&#xff0c;内核 netfilter 模块 conntrack 相关参…

听GPT 讲Rust源代码--compiler(19)

File: rust/compiler/rustc_target/src/spec/mips_unknown_linux_gnu.rs 该文件&#xff08;rust/compiler/rustc_target/src/spec/mips_unknown_linux_gnu.rs&#xff09;是Rust编译器针对MIPS架构上的Linux系统的目标描述文件。它的作用是定义了在这个目标上编译时的一些配置…

mysql之数据类型、建表以及约束

目录 一. CRUD 1.1 什么是crud 1.2 select(查询) 1.3 INSERT(新增) 1.4 UPDATE(修改&#xff09; 1.5 DELETE(删除) 二. 函数 2.1 常见函数 2.2 流程控制函数 2.3聚合函数 三. union与union all 3.1 union 3.2 union all 3.3 具体不同 3.4 结论 四、思维导图 一. CRUD 1.1…

建站指南,如何将拥有的域名自定义链接到wordpress

关于Dynadot Dynadot是通过ICANN认证的域名注册商&#xff0c;自2002年成立以来&#xff0c;服务于全球108个国家和地区的客户&#xff0c;为数以万计的客户提供简洁&#xff0c;优惠&#xff0c;安全的域名注册以及管理服务。 在Dynadot上&#xff0c;我们可已经账户中管理的…