【排序算法】快速排序(四个版本以及两种优化)含动图)

news2025/3/18 3:13:18

制作不易,三连支持一下吧!!!

文章目录

  • 前言
  • 一.快速排序Hoare版本实现
  • 二.快速排序挖坑法版本实现
  • 三.快速排序前后指针版本实现
  • 四.快速排序的非递归版本实现
  • 五.两种优化
  • 总结


前言

前两篇博客介绍了插入和选择排序,这篇博客我们将会介绍一个非常重要的排序算法——快速排序,它的实践价值要大远远于前两种排序。

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


一、快速排序Hoare版本实现

动图展示如下:

这里再简单解释一下:

我们一般选取最左或最后边的值作为基准值(key),如果我们选择最左边的值为key,且我们要排成升序序列,那我们就让最右边先走(为了保证相遇位置的值一定比key小),找到比key小的位置停下来,然后左边再走,找到比key大的位置停下来,两者交换,循环上述操作,直至left和right相遇,再将相遇位置的值与key交换,这样就完成了单趟排序。

key位置的元素就排到了合适的位置。 

之后递归左右区间即可。

代码实现:

void Swap(int* a, int* b)
{
	int tmp = *a;
	*a = *b;
	*b = tmp;
}

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

void QuickSort(int* a, int begin, int end)
{
	//最小子问题,返回条件
	if (begin >= end)
		return;

	int key = begin;
	int left = begin;
	int right = end;

	while (left<right)
	{
		while (left < right && a[right] >= a[key])
		{
			right--;
		}
		while (left < right && a[left] <= a[key])
		{
			left++;
		}

		Swap(&a[left], &a[right]);
	}
	Swap(&a[key], &a[left]);
	key = left;

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

}

 二.快速排序挖坑法版本实现

挖坑法是在hoare大佬版本的基础上为了好让大家理解而改进的一种思路,因为hoare版本如果以最左边的值做key,则必须要让右边先走,让最右边的值做key,则必须要让左边先走,否则会出现错误。 而如果是挖坑法,则无需关心这个问题。大家自然而然会按正确的方式实现。

动图演示如下: 

通俗一点解释:

我们将key位置的值保存下来,将那个位置视为一个坑位,右边先走,找到比key小的值就填到坑位中,坑位就更新到新的位置,左边再找大,再更新坑位的位置,直到left和right相遇,将原来key中的值放到坑位中就完成了单趟排序。

这样我们就避免了考虑哪边先走的问题,因为如果左边为坑,我们自然而然的就会让右边先走!

代码实现如下:

QuickSort2(int* a, int begin, int end)
{
	if (begin >= end)
		return;

	int left = begin, right = end;
	int key = a[begin];
	int hole = begin;

	while (left < right)
	{
		while (left < right && a[right] >= key)
		{
			right--;
		}

		a[hole] = a[right];
		hole = right;

		while (left < right && a[left] <= key)
		{
			left++;
		}

		a[hole] = a[left];
		hole = left;
	}

	a[hole] = key;
	int keyi = hole;

	QuickSort2(a, begin, keyi - 1);
	QuickSort2(a, keyi + 1, end);

}

三.快速排序前后指针版本实现 

前后指针版本是这三种方法里面最值得推荐的一种,因为它实现出来非常的简单。

动图演示如下:

 

 简单解释一下:

定义两个指针prev和cur,如果啊a[cur]比key位置的值小,就让prev前进一步,并交换cur和prev位置的值,如果a[cur]比key位置的值大,就只++cur。

最后,prev和cur之间的值就是整个序列中比key位置的值大的值。prev之前的值就是比key位置的值小的值。

代码实现如下: 

QuickSort3(int* a, int begin, int end)
{
	if (begin >= end)
		return;

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

		cur++;
	}

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

	QuickSort3(a, begin, keyi - 1);
	QuickSort3(a, keyi + 1, end);

}

 

四.快速排序非递归法实现 

之前的三种方法虽然思路略有不同,但本质都是分治递归的思想,这种思想比较容易理解,但是递归深度过深可能会导致栈溢出,因此,我们必须掌握非递归的实现,以适用于所有场景。 

从递归改成非递归的方法一般有两种:

  • 直接改成循环——例如:斐波那契数列
  • 借助于栈或队列的结构。

我们这里就是要借助于栈的数据结构。

整体思路就是压栈,每次取出一个区间进行单趟排序,然后再入栈,如果区间不存在或只有一个元素就不再入栈,直至栈为空时,排序就完成了。 

代码实现如下: 

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);
		STPop(&st);
		int end = STTop(&st);
		STPop(&st);

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

		if (keyi + 1 < end)
		{
			STPush(&st, end);
			STPush(&st, keyi+1);
		}
		if (begin < keyi - 1)
		{
			STPush(&st, keyi - 1);
			STPush(&st, begin);
		}
	}

	STDestroy(&st);
}

 

五 两种优化(小区间优化和三数取中)

1.小区间优化

从分治的角度看,快速排序是一种二叉树结构的排序,有些大佬觉得最后几层占了整个递归次数的百分之八九十,消耗有些大,所以当区间长度小于一定数量(一般是10)时就直接用直接插入排序就好了。

也就是说最小子问题不再是区间不存在或只有一个值了,而是区间长度小于10.

if (end - begin + 1 <= 10)
InsertSort(a, end - begin + 1);

2.三数取中 

快速排序在序列有序时,效率是不高的,时间复杂度不再是O(N*logN),而是O(N^2)。

为了尽量避免出现最坏的情况,我们可以采用三数取中的方法,防止key是最大或最小的值。

//三数取中
int GetMid(int* a, int left, int right)
{
	int mid = (left + right) / 2;
	if (a[left] < a[mid])
	{
		if (a[right] > a[mid])
		{
			return mid;
		}
		else if (a[right] < a[left])
		{
			return left;
		}
		else {
			return right;
		}

	}
	else {
		if (a[mid] > a[right])
		{
			return mid;
		}
		else if (a[right] > a[left])
		{
			return left;
		}
		else {
			return right;
		}
	}
}

 

以hoare版本为例,加入三数取中后,就是这样: 

//快速排序Hoare版本
void QuickSort(int* a, int left, int right)
{
	if (left >= right)
		return;

	int begin = left, end = right;

	//随机数作key
	//int randi=rand()%(right-left+1);
	//randi += left;
	//Swap(&a[randi], &a[left]);

	//三数取中
	int mid = GetMid(a, left, right);
	Swap(&a[mid], &a[left]);


	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]);
	}
	Swap(&a[left], &a[keyi]);
	keyi = left;

	QuickSort(a, begin, keyi - 1);
	QuickSort(a, keyi + 1, end);

}

 


总结

快速排序是实践中运用非常多的一种排序,它的价值非常高,我们应该熟练掌握快速排序的思想。

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

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

相关文章

nuxt: generate打包后访问资源404问题

现象 使用Nuxt.js开发的个人页面&#xff0c;部署到nginx服务器中&#xff0c;/_nuxt/*.js、/_nuxt/*.css等静态问题不能访问&#xff0c;提示404错误。 而我们的这些资源文件是存在的。 解决方法 加上此处代码进行上下文配置 baseURL: /nuxt/ 此时在nginx配置 /nuxt 代理 lo…

使用 Django Admin 进行高效的后台管理

文章目录 创建超级用户注册模型到 Admin 后台自定义 Admin 后台界面定制 Admin Actions结语 当使用 Django Admin 进行后台管理时&#xff0c;开发者可以通过简单的配置和定制来满足项目的需求。可以根据不同的模型和数据结构&#xff0c;轻松地创建和管理数据条目、进行搜索和…

微信小程序--微信开发者工具使用小技巧(3)

一、微信开发者工具使用小技巧 1、快速创建小程序页面 在app.json中的pages配置项&#xff0c;把需要创建的页面填写上去 2、快捷键使用 进入方式 1&#xff1a; 文件–>首选项–> keyboard shortcuts 进入快捷键查看与设置 进入方式 2&#xff1a; 设置–>快捷键…

C#应用的用户配置窗体方案 - 开源研究系列文章

这次继续整理以前的代码。本着软件模块化的原理&#xff0c;这次笔者对软件中的用户配置窗体进行剥离出来&#xff0c;单独的放在一个Dll类库里进行操作&#xff0c;这样在其它应用程序里也能够快速的复用该类库&#xff0c;达到了快速开发软件的效果。 笔者其它模块化应用的例…

AcW木棒-XMUOJ恢复破碎的符咒木牌-DFS与剪枝

题目 思路 话不多说&#xff0c;直接上代码 代码 /* AcW木棒-XMUOJ恢复破碎的符咒木牌 搜索顺序&#xff1a;从小到大枚举最终的长度 len从前往后依次拼每根长度为len的木棍 优化&#xff1a; 1.优化搜索顺序&#xff1a;优先选择深度短的来搜索&#xff0c;故从大到小去枚…

java —— 封装、继承、接口和多态

一、封装 封装是将数据和操作这些数据的方法整合成一个类。在这个类中&#xff0c;用 private 修饰符将某些数据隐藏起来&#xff0c;只通过特定的方法实现这些数据的访问和修改&#xff0c;以此实现数据的完整和安全性。 封装的步骤&#xff1a; 二、继承 继承是指把子类共有…

深度学习之基于Matlab编写BP神经网络汉字识别系统

欢迎大家点赞、收藏、关注、评论啦 &#xff0c;由于篇幅有限&#xff0c;只展示了部分核心代码。 文章目录 一项目简介 二、功能三、系统四. 总结 一项目简介 一、项目背景与意义 随着信息技术的快速发展&#xff0c;文本识别和处理技术在各个领域中扮演着越来越重要的角色。…

helloworld 可执行程序得到的过程

// -E 预处理 开发过程中可以确定某个宏 // -c 把预处理 编译 汇编 都做了,但是不链接 // -o 指定输出文件 // -I 指定头文件目录 // -L 指定链接库文件目录 // -l 指定链接哪一个库文件 #include <stdio.h> #include <stdlib.h> #include <string.h>int mai…

Java代码审计-XSS审计

一、漏洞简介 XSS是Cross Site Scripting的缩写&#xff0c;意为"跨站脚本攻击"&#xff0c;为了避免与层叠样式表(Cascading Style Sheet&#xff0c;CSS)的缩写混淆&#xff0c;故将跨站脚本攻击缩写为XSS。XSS是一种针对网站应用程序的安全漏洞攻击技术&#xff…

SPI通信(STM32)

一、SPI通信 &#xff11;、SPI&#xff08;Serial Peripheral Interface&#xff09;是由Motorola公司开发的一种通用数据总线 &#xff12;、四根通信线&#xff1a;SCK&#xff08;Serial Clock&#xff09;、MOSI&#xff08;Master Output Slave Input&#xff09;、MIS…

5.18 TCP机械臂模拟

#include <netinet/tcp.h>//包含TCP选项的头文件 #include <arpa/inet.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <linux/input.h>//读取输入事件 #include <sys/types.h> #include <sys/stat.h&…

截图工具PixPin(比Snipaste更强大)

PixPin官网链接&#xff1a;https://pixpinapp.com/ 最近新出的一款截图工具PixPin&#xff0c;比Snipaste功能多一些。在Snipaste功能基础上&#xff0c;还支持长截图&#xff0c;截动图&#xff0c;文本识别。

第一篇【传奇开心果系列】Python的跨平台开发工具beeware技术点案例示例:使用beeware实现跨平台开发,从hello world开始

传奇开心果博文系列 系列博文目录Python的跨平台开发工具beeware技术点案例示例系列 博文目录前言一、BeeWare套件主要功能介绍二、Toga相对于其他Python UI库具有的优势介绍三、使用toga开发安卓手机应用hello world步骤和示例代码四、使用toga写一个iOS 苹果手机应用hello wo…

探索 Vue 3 的动态布局解决方案:Grid Layout Plus

探索 Vue 3 的动态布局解决方案&#xff1a;Grid Layout Plus 文章目录 探索 Vue 3 的动态布局解决方案&#xff1a;Grid Layout PlusGrid Layout Plus 概览0、元信息1、核心特性可拖拽部件可缩放部件静态部件边界检查避免重建栅格可序列化和还原的布局自动化 RTL 支持响应式设…

插件:NGUI

一、版本 安装完毕后重启一下即可&#xff0c;否则可能创建的UI元素不生效 二、使用 Label文字 1、创建Canvs 2、只有根节点的这些脚本全部展开才能鼠标右键创建UI元素 3、选择字体 Sprite图片 1、选择图集 2、选择图集中的精灵 Panel容器 用来装UI的容器&#xff0c;一般UI…

【c语言】了解指针,爱上指针(5)

了解指针&#xff0c;爱上指针&#xff08;5&#xff09; 回调函数qsort函数冒泡排序模拟实现qsort函数 回调函数 回调函数&#xff1a;就是一个通过函数指针调用的函数。 把函数的指针作为参数传给另一个函数&#xff0c;当这个指针被用来调用指向的函数时&#xff0c;此时被…

K8s的常用命令以及yaml文件的创建

目录 一、声明式管理方法&#xff1a;YAML文件 1、yaml文件简介 2、yaml和json的主要区别&#xff1a; 3、YAML的语法格式 4、yaml文件组成部分 ①控制器定义 5、查看api资源版本标签 6、编写nginx-deployment.yaml资源配置清单 6.1创建资源对象 6.2查看创建的pod资源…

vue3 使用css实现一个弧形选中角标样式

文章目录 1. 实现效果2. 实现demo 在前端开发中&#xff0c;ui同学经常会设计这样的样式&#xff0c;用于区分选中的状态 下面抽空简单些了一下&#xff0c;记录下&#xff0c;后面直接复制用 1. 实现效果 实现一个菜单切换&#xff0c;右下角有个角标的样式 2. 实现demo 主要…

通过Kafka-Logger插件收集流量进行漏洞扫描

通过Kafka-Logger插件收集流量进行漏洞扫描 方案 可以通过APISIX kafka-logger 插件将日志作为 JSON 对象推送到 Apache Kafka 集群中&#xff0c;消费Kafka里的数据格式化后添加到MySQL。 方案详情 1、登录APISIX&#xff0c;启用内置的kafka-logger 插件&#xff1a; 2…

数据可视化第9天(利用wordcloud和jieba分析蝙蝠侠评论的关键字)

数据可以在这里下载 https://github.com/harkbox/DataAnalyseStudy WordCloud wordcloud可以很方便的生成词云图&#xff0c;方便的提供可视化可以直接使用pip install wordcloud进行安装如果使用的是Anaconda,可以使用conda install进行安装 下面看一个简单的例子 txt &qu…