快速排序(Quick Sort)(C语言) 超详细解析!!!

news2024/11/22 18:18:14

生活的本质是什么呢? 无非就是你要什么就不给你什么. 而生活的智慧是什么呢? 是给你什么就用好什么. ---马斯克

索引

  • 一. 前言
  • 二. 快速排序的概念
  • 三. 快速排序的实现
    • 1. hoare
    • 2. 挖坑法
    • 3. 前后指针法
  • 总结


正文开始

一. 前言

接上文, 前面我们了解了插入排序, 与优化版本希尔排序, 那么本篇博客将详细介绍另外一种排序, 快速排序.

博客主页: 酷酷学!!!

感谢关注~

二. 快速排序的概念

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

其基本思想为:

任取待排序元素序列中的某元素作为基准值,按照该排序码将待排序集合分割成两子序列,左子序列中所有元素均小于基准值,右子序列中所有元素均大于基准值,然后最左右子序列重复该过程,直到所有元素都排列在相应位置上为止。

三. 快速排序的实现

将区间按照基准值划分为左右两半部分的常见方式有三种版本

1. hoare

在这里插入图片描述
如图所示, 排序的思想为

第一步:
两个下标, 一个从后往前走, 一个从前往后走, 然后定义一个基准值key, 下表为keyi,这里要注意要保证end先走, 才能让最后相遇的位置小于keyi的位置, 因为end先走无非就两种情况, 第一种, end找到了比key小的值, 停下来, 最后一次begin相遇end,交换相遇结点与key结点, 第二种情况, end相遇begin, 此时end没有找到比keyi小的值, 然后最后一次与begin相遇, 此时begin的值为上一次交换后的值, 所以比keyi小.然后相遇与keyi交换位置, 此时6这个位置就排好了

在这里插入图片描述
第二步:

因为6这个位置排好了, 所以进行下一次的排序,以6划分为两个区域,然后再次递归调用函数, 如果最后的区间不存在或者只有一个值那么就结束函数.当left==right时此时区间只有一个值就不需要排序, 区间不存在也不需要排序.

void QuickSort(int* a, int left, int right)
{
	if (left >= right)
	{
		return;
	}
	int keyi = left;
	int begin = left,end = right;
	while (begin < end)
	{

		while (begin < end && a[end] >= a[keyi])
		{
			end--;
		}
		while (begin < end && a[begin] <= a[keyi])
		{
			begin++;
		}
		Swap(&a[begin], &a[end]);
	}

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

	QuickSort(a, left, keyi - 1);
	QuickSort(a, keyi + 1, right);
}

我们来分析一下快速排序的时间复杂度, 目前来看, 每一层的时间复杂度为O(N), 高度为logn, 所以时间复杂度为O(NlogN), 但是这是在较好的情况下, 也就是相当于二叉树, 左右区间差不多大, 那么如果是有序的一系列数字, 那么这个排序就很不友好, 很容易栈溢出,递归层次很深, 就不是logn, 那么如何进行优化呢, 就需要我们修改它的key值, 不大不小为最好的情况, 为了避免有序情况下,效率退化, 可以选择 随机选key法 或者 三数取中法

在这里插入图片描述

这里我们来介绍一下三数取中法的优化.

int GetMidi(int* a, int left, int right)
{
	int midi = (right - left) / 2;
	if (a[left] < a[midi])
	{
		if (a[midi] < a[right])
		{
			return midi;
		}
		else if(a[left] < a[right])
		{
			return right;
		}
		else
		{
			return left;
		}
	}
	else//a[left] > a[midi]
	{
		if (a[left] < a[right])
		{
			return left;
		}
		else if (a[midi] > a[right])
		{
			return midi;
		}
		else
		{
			return right;
		}
	}
}

我们可以定义一个函数, 然后选取区间内最左边的值, 最右边的值, 中间的值,那个不大又不小的值作为key, 利用上述函数, 我们的代码可以改写为

void QuickSort(int* a, int left, int right)
{

	if (left >= right)
	{
		return;
	}

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

	int keyi = left;
	int begin = left,end = right;
	while (begin < end)
	{

		while (begin < end && a[end] >= a[keyi])
		{
			end--;
		}
		while (begin < end && a[begin] <= a[keyi])
		{
			begin++;
		}
		Swap(&a[begin], &a[end]);
	}

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

	QuickSort(a, left, keyi - 1);
	QuickSort(a, keyi + 1, right);
}

有了上述的优化, 我们的代码效率就算在有序的情况下也能大大的提升效率, 但是还有一个问题, 我们知道, 二叉树中递归调用, 最后一层递归调用的次数占据了总调用次数的一半, 我们可以把最后几层的递归调用优化掉, 如果区间为十个数那么我们选择其他的排序方法, 如果选堆排序, 十个数还需要建堆比较麻烦, 如果使用希尔排序, 那么就是多此一举, 数据量不大选择希尔排序属实有点浪费了, 那我们就在时间复杂度为O(N^2)的排序中选择一个相对来说效率比较高的插入排序.优化后的代码如下:

void QuickSort(int* a, int left, int right)
{

	if (left >= right)
	{
		return;
	}
	if ((right - left + 1) < 10)
	{
		InsertSort(a + left, right - left + 1);
	}
	else
	{
		//三数取中
		int midi = GetMidi(a, left, right);
		Swap(&a[left], &a[midi]);

		int keyi = left;
		int begin = left, end = right;
		while (begin < end)
		{

			while (begin < end && a[end] >= a[keyi])
			{
				end--;
			}
			while (begin < end && a[begin] <= a[keyi])
			{
				begin++;
			}
			Swap(&a[begin], &a[end]);
		}

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

		QuickSort(a, left, keyi - 1);
		QuickSort(a, keyi + 1, right);
	}

}

此优化在release版本下较为突出.

2. 挖坑法

前面单趟排序的方法由hoare发明之后又有另外的一种排序方法, 挖坑法, 它的思路比hoare排序的思想更容易理解

在这里插入图片描述

//挖坑法
int PartSort3(int* a, int left, int right)
{
	//三数取中
	int midi = GetMidi(a, left, right);
	Swap(&a[left], &a[midi]);
	int keyi = left;

	int begin = left, end = right;
	int tmp = a[keyi];
	while (begin < end)
	{
		while (begin < end && a[end] >= a[keyi])
		{
			end--;
		}
		a[keyi] = a[end];
		keyi = end;
		while (begin < end && a[begin] <= a[end])
		{
			begin++;
		}
		a[keyi] = a[begin];
		keyi = begin;
	}
	a[keyi] = tmp;
	return keyi;
}

void QuickSort(int* a, int left, int right)
{
	if (left >= right)
	{
		return;
	}

	if (right - left + 1 < 10)
	{
		InsertSort(a + left, right - left + 1);
	}
	
	int keyi = PartSort3(a,left,right);

	QuickSort(a, left, keyi - 1);
	QuickSort(a, keyi + 1, right);
}

我们可以直接把单趟排序的算法拿出来封装成一个函数, 如图所示挖坑法特别好理解, 先让keyi的值存放在一个临时变量中, 挖一个坑,先让对面的先走, 如果遇到比end小的就把它拿出来填入坑中, 然后begin在开始走, 遇到大的填入坑中, 这样依次轮换,相遇时将keyi的值填入坑中.

3. 前后指针法

在这里插入图片描述

如图所示, 初始时, prev指针指向序列开头, cur指针指向prev指针的后一个位置, 然后判断cur指针指向的数据是否小于key, 则prev指针后移一位, 并于cur指针指向的内容交换, 然后cur++,cur指针指向的数据仍然小于key,步骤相同, 如果cur指针指向的数据大于key, 则继续++,最后如果cur指针已经越界, 这时我们将prev指向的内容与key进行交换.

可以看到也就是一个不断轮换的过程, 将大的数依次往后翻转, 小的数往前挪动, 这种方法代码写起来简洁, 当然我们也可以优化一下, 让cur和prev相等时就没必要进行交换

//前后指针版
int PartSort2(int* a, int left, int right)
{
	//三数取中
	int midi = GetMidi(a, left, right);
	Swap(&a[left], &a[midi]);
	int keyi = left;

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

void QuickSort(int* a, int left, int right)
{
	if (left >= right)
	{
		return;
	}

	if (right - left + 1 < 10)
	{
		InsertSort(a + left, right - left + 1);
	}
	
	int keyi = PartSort2(a,left,right);

	QuickSort(a, left, keyi - 1);
	QuickSort(a, keyi + 1, right);
}

以上是递归版本的实现, 那么我们知道, 递归层次太深会容易栈溢出, 那么能不能采用非递归的实现呢, 答案肯定是可以的, 我们可以使用栈这个数据结果, 然后将区间入栈中, 栈不为空循环对区间进行排序, 一般linux系统下函数栈帧的空间的大小为8M, 而堆空间大小为2G, 所以这个空间是非常大的, 而且栈也是动态开辟的空间, 在堆区申请, 所以几乎不需要考虑空间不够, 我们可以将区间压栈, 先压入右区间, 在压入左区间, 然后排序的时候先排完左区间再排右区间, 这是一种深度优先算法(DFS), 当然也可以创建队列, 那么走的就是一层一层的排序, 每一层的左右区间都排序, 是一种广度优先算法(BFS).
代码实现:

这里可以创建一个结构体变量存放区间, 也可以直接压入两次, 分别把左值和右值压入

//非递归版
#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);
		STPop(&st);
		int end = STTop(&st);
		STPop(&st);

		int keyi = PartSort1(a, begin, end);
		//[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);
		}
	}
	Destroy(&st);
}

总结

快速排序是一种常用的排序算法,其时间复杂度为O(nlogn)。它的基本思想是通过一趟排序将待排序序列分割成独立的两部分,其中一部分的所有元素都比另一部分的所有元素小,然后再对这两部分分别进行排序,最终得到一个有序序列。


期待支持, 感谢关注~

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

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

相关文章

ssh远程管理yum源进阶

文章目录 sshNFS 共享存储服务实验yum的进阶使用Apanche做一个网页形式的源用vsftpd做一个源混合源 ssh ssh是一种安全通道协议&#xff0c;用来实现字符界面的远程登录&#xff0c;远程复制&#xff0c;远程文本传输 ssh对通信双方的数据进行了加密 用户名和密码登录 秘钥…

Mysql学习(四)——SQL通用语法之DQL

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 DQLDQL-语法基本查询条件查询聚合函数分组查询排序查询分页查询 DQL DQL数据查询语言&#xff0c;用来查询数据库中表的记录。 DQL-语法 select 字段列表 from 表…

C++ | Leetcode C++题解之第136题只出现一次的数字

题目&#xff1a; 题解&#xff1a; class Solution { public:int singleNumber(vector<int>& nums) {int ret 0;for (auto e: nums) ret ^ e;return ret;} };

图相似度j计算——SimGNN

图相似性——SimGNN 论文链接&#xff1a;个人理解&#xff1a;数据处理: feature_1 [[1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], # "A"[0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0], # "B"[0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0] # "C" 第二个循环&#xff…

Keil MDK Armcc6 总是全编译项目的问题

我碰到的问题是因为使用lib库待代替原本的源码引起的&#xff0c;把lib库去除&#xff0c;使用源码编译就不会出现全编译的问题了。但是至于一定要使用LIB库但是又不想全编译暂时不知道怎么弄&#xff0c;具体为什么会这样暂不清楚。但是可以确定的是编译器参数可能选的不对&am…

代理结算不再繁琐,Xinstall让App推广更轻松

在移动互联网时代&#xff0c;App的推广与获客已成为企业发展的重要一环。然而&#xff0c;随着推广模式的多样化&#xff0c;如何高效地管理App推广的代理结算&#xff0c;成为了许多企业面临的难题。Xinstall凭借其强大的超级渠道功能&#xff0c;为企业提供了一个完美的解决…

php实现抖音小程序支付

开发者发起下单_小程序_抖音开放平台 第一步、抖音小程序发起支付 tt.pay_小程序_抖音开放平台 前端提交订单数据到后端接口&#xff0c;然后使用 tt.pay发起支付 请求参数 属性 类型 必填 说明 order_id string 是 担保交易服务端订单号 order_token string 是 …

C语言 | Leetcode C语言题解之第136题只出现一次的数字

题目&#xff1a; 题解&#xff1a; class Solution { public:vector<int> singleNumbers(vector<int>& nums) {int eor 0;for (int num:nums)eor ^ num;int rightOne eor & (~eor 1); // 提取出最右的1int onlyOne 0;for (int cur : nums) {if ((cur…

图像算法---自动对焦AF

一&#xff0c;CDAF反差对焦原理 CDAF&#xff0c;全称Contrast Detection Auto Focus&#xff0c;即反差式对焦或对比度检测自动对焦&#xff0c;是一种广泛应用于入门级数码相机和相机模块化智能手机上的自动对焦技术。以下是关于CDAF反差对焦的详细介绍&#xff1a; 工作原…

nginx动静分离和反向代理

一、动静分离 动静分离指的是将动态内容和静态内容分开处理。动态内容通常由后端应用程序生成&#xff0c;例如PHP、Python或Node.js&#xff0c;静态内容则包括图片、CSS、JavaScript等文件。 例子&#xff1a; #代理服务器一 server{listen 80;server_name www.dj.com;r…

C语言 | Leetcode C语言题解之第135题分发糖果

题目&#xff1a; 题解&#xff1a; int candy(int* ratings, int ratingsSize) {int ret 1;int inc 1, dec 0, pre 1;for (int i 1; i < ratingsSize; i) {if (ratings[i] > ratings[i - 1]) {dec 0;pre ratings[i] ratings[i - 1] ? 1 : pre 1;ret pre;inc…

ceph对象储存的使用

radosgw-admin user create --uid“user1” --display-name“user1” #创建用户 sudo apt install s3cmd cephadminceph-mgr01:~/ceph-cluster/s3$ s3cmd --configure Enter new values or accept defaults in brackets with Enter. Refer to user manual for detailed desc…

YUM安装httpd实验配置apache

实验目的及实验要求&#xff1a; 实验目的&#xff1a; 2.实验要求&#xff1a; &#xff08;1&#xff09;完成命令的编写&#xff0c;并能正确运行&#xff1b; &#xff08;2&#xff09;从中熟练掌握命令的功能及作用。 实验设备及软件&#xff1a; pc机 配置好Lin…

我们设计制造MW级水冷负载电阻器-数据中心船舶岸电发电机组测试大功率负载RLC阻感容集装箱负载

UEPR系列电阻采用先进材料制造&#xff0c;采用专利设计&#xff0c;将电阻与冷却液完全隔离&#xff0c;为水冷应用提供重量轻、体积小、超大功率的解决方案。其革命性的模块化设计意味着它们可以串联在一起&#xff0c;以满足您的电力需求。应用于发电、电力传输、电气传动等…

2024年汉字小达人活动还有4个多月开赛:来做18道历年选择题备考

根据近年的安排&#xff0c;2024年第11届汉字小达人比赛还有4个多月就启动&#xff0c;那么孩子们如何利用这段时间有条不紊地备考呢&#xff1f;我的建议是两手准备&#xff1a;①把小学1-5年级的语文课本上的知识点熟悉&#xff0c;重点是字、词、成语、古诗。②把历年真题刷…

JavaSE中的if语句、switch语句:如何控制程序流程?

哈喽&#xff0c;各位小伙伴们&#xff0c;你们好呀&#xff0c;我是喵手。运营社区&#xff1a;C站/掘金/腾讯云&#xff1b;欢迎大家常来逛逛 今天我要给大家分享一些自己日常学习到的一些知识点&#xff0c;并以文字的形式跟大家一起交流&#xff0c;互相学习&#xff0c;一…

安防监控平台智能边缘分析一体机视频监控系统室内消防通道占用检测算法

随着城市化进程的加速&#xff0c;高层建筑如雨后春笋般涌现。这些建筑的消防安全问题日益凸显&#xff0c;尤其是消防通道的占用问题。为了解决这一问题&#xff0c;智能边缘分析一体机被引入到室内消防通道占用检测中&#xff0c;以提高检测效率和准确性。本文将探讨这一技术…

ctfshow-web入门-命令执行(web30-web36)

目录 1、web30 2、web31 3、web32 4、web33 5、web34 6、web35 7、web36 命令执行&#xff0c;需要严格的过滤 1、web30 代码差不多&#xff0c;就是过滤的东西变多了&#xff1a; preg_match("/flag|system|php/i", $c) 这里不让用 system &#xff0c;我们…

RainBond 制作应用并上架【以ElasticSearch为例】

文章目录 安装 ElasticSearch 集群第 1 步:添加组件第 2 步:查看组件第 3 步:访问组件制作 ElasticSearch 组件准备工作ElasticSearch 集群原理尝试 Helm 安装 ES 集群RainBond 制作 ES 思路源代码Dockerfiledocker-entrypoint.shelasticsearch.yml制作组件第 1 步:添加组件…

JSONPath使用指南(掌握JSON数据提取)

大家好&#xff0c;在处理 JSON&#xff08;JavaScript Object Notation&#xff09;数据时&#xff0c;有时需要从复杂的结构中提取特定部分。JSONPath 就是一个非常有用的工具&#xff0c;它提供了一种简洁而强大的方式来定位和提取 JSON 数据中的元素。无论是在 Web 开发中处…