拒绝水文!八大排序(三)【适合初学者】快速排序

news2024/11/25 14:32:09

文章目录

  • 快速排序递归实现
    • 霍尔法
      • 优化
    • 挖坑法
    • 前后指针法
  • 快速排序非递归

大家好,我是纪宁,这篇文章将向大家介绍非常有名气的一款排序:快速排序
回忆到我们刚开始学习C语言的时候。经常会使用到一个库函数: qsort函数 ,O(N*logN) 的时间复杂度不知道比冒泡排序强了多少倍,那时候经常会想,我靠,这效率牛。那么这篇文章,就带大家深入理解快排的原理及它的多种实现方式。

时间复杂度:O(N*logN)
空间复杂度:O(1)
稳定性:不稳定

快速排序递归实现

霍尔法

假如现在有一个数组,要对它进行升序排列,那么排好序后,每个数的位置都应该是固定的,快排的思路就是这样:先将一个数作为key(一般选择数组的最左边),然后定义两个指针分别从左右遍历数组(从右边开始),将比 key 大的数全部放在数组偏右边,将比 key 小的数全部放在数组偏左边,然后在两个指针相遇的地方,将这个位置的值与最左边的key 值进行交换,那么key 就放在了正确的位置,即 key 左边全部是小于 key 的,右边全部是大于 key 的数据。
在这里插入图片描述
结束后,以当前的key 为分界线,key 左边的为一组,右边为一组,再递归重复上面的步骤。

int QuickSortPart(int*a, int left, int right)
{
	int keyi = left;
	while (left < right)
	{
		while (right > left)
		{
			if (a[right] < a[keyi])
				break;
			right--;
		}
		while (left < right)
		{
			if (a[left] > a[keyi])
				break;
			left++;
		}
		Swap(&a[right], &a[left]);
	}
	Swap(&a[left], &a[keyi]);
	return left;
}
void QuickSort(int* arr, int begin, int end)
{
	if (begin >= end)//等于是只有一个数需要排,大于是没有数需要排
	{
		return;
	}
	int keyi = QuickSortPart(arr, begin, end);
	QuickSort(arr, begin, keyi - 1);
	QuickSort(arr, keyi + 1, end);
}

代码解释:用 keyi 把 left 表示出来是因为left的值会在调整的过程中改变;函数传参传进 QuickSort 的部分是数组首元素的下标和数组最后一个数据的下标;当递归再次进入函数 QuickSort 的时候,如果begin>=end,说明某一边已经没有元素要排或者只有一个元素不用排。

优化

优化1:三数取中
理想情况下,每次将数组二分。每次遍历数组的时间复杂度为O(N),二分的时间复杂度为O(logN),所以这个过程下来,时间复杂度就是O(logN)

但如果情况不理想呢?假如数组已经接近有序的状态了,且第一个数是最小值,那么在右边根本找不到比它小的,一直左移,最后就只能将第一个数确定位置,如此往复,那么分割的时候也要分割N次,时间复杂度瞬间就变成了(N^2)。
改进方法:在QuickSortPart函数实现中添加一个三数取中函数,实现一个找最开始、中间、最后面三个数中最大值的功能,然后将这个数与 left 对应的值进行交换。这样,最左边就变成了偏中间的值,那么就可以做到将数组分割的更好。

优化2:小区间优化
当区间比较小的时候,继续使用递归显然是不太合适的,递归太深的话函数栈帧的压力是非常大的,所以在区间范围比较小的时候,可以将排序改为插入排序,较为高效一些。

优化后的代码

int QuickSortPart(int*a, int left, int right)
{
	int* Maxi = (&a[left], &a[right], &(a[(left + right) / 2]));//三数取中
	Swap(&a[left], Maxi);//换到最左边
	int key = a[left];
	int keyi = left;
	while (left < right)
	{
		while (right > left)
		{
			if (a[right] < a[keyi])
				break;
			right--;
		}
		while (left < right)
		{
			if (a[left] > a[keyi])
				break;
			left++;
		}
		Swap(&a[right], &a[left]);
	}
	Swap(&a[left], &a[keyi]);
	return left;
}
void QuickSort(int* arr, int begin, int end)
{
	if (begin >= end)//等于是只有一个数需要排,大于是没有数需要排
	{
		return;
	}
	int keyi = QuickSortPart(arr, begin, end);
	if (end - begin >= 5)
	{
		QuickSort(arr, begin, keyi - 1);
		QuickSort(arr, keyi + 1, end);
	}
	else
	{
		InsertSort(arr + begin, end - begin + 1);
		InsertSort(arr + keyi + 1, end - keyi);
	}
}

挖坑法

在这里插入图片描述

挖坑法思路和霍尔法基本相同,但思路更好理解一点。
选择一个‘坑位’(位置),这个坑位上就是要放 key 值的地方,先将坑位的这个值保存下来,右指针先向左走,找比key小的值,找到后将这个位置的值放入坑位,自己形成新的坑位,然后左指针向右走,找比key大的值,找到后将这个位置的值放入新生成的坑位,然后自己形成新的坑位…如此往复,直到 left 和 right 相遇形成的最后的坑位,将原来保留的key 值放入该坑位中,一趟就完成了。

挖坑法代码

int QuickSortPart(int*a, int left, int right)
{
	int* Maxi = (&a[left], &a[right], &(a[(left + right) / 2]));//三数取中
	Swap(&a[left], Maxi);//换到最左边
	int holei = left;
	int hole = a[left];
	while (left < right)
	{
		while (left < right && a[right] >= hole)
		{
			right--;
		}
		a[holei] = a[right];
		holei = right;
		while (left < right && a[left] <= hole)
		{
			left++;
		}
		a[holei] = a[left];
		holei = left;
	}
	return left;
}
void QuickSort(int* arr, int begin, int end)
{
	if (begin >= end)//等于是只有一个数需要排,大于是没有数需要排
	{
		return;
	}
	int keyi = QuickSortPart(arr, begin, end);
	if (end - begin >= 5)
	{
		QuickSort(arr, begin, keyi - 1);
		QuickSort(arr, keyi + 1, end);
	}
	else
	{
		InsertSort(arr + begin, end - begin + 1);
		InsertSort(arr + keyi + 1, end - keyi);
	}
}

前后指针法

依旧是三数取中后取最左边的数的下标为keyi。定义一个指针prev指向left ,定义一个指针cur指向left+1,cur先往后遍历,如果找到比 key 小的数,则和prev先自加,再将prev位置和数和cur位置的数交换位置,如果找到大于等于key的数,cur 就继续往后遍历,而 prev 不动。
最后,将prev位置的值和 keyi 位置的值交换,再返回prev,相当于也是找到了那个key:调整后prev左边都是小于key的,右边都是大于key的。
在这里插入图片描述

int QuickSortPart4(int* a, int left, int right)
{
	int* Maxi = (&a[left], &a[right], &(a[(left + right) / 2]));
	Swap(&a[left], Maxi);
	int keyi =left;
	int cur = left+1;
	int prev = left;
	while (cur <= right)
	{
		if(a[cur] >= a[keyi])
		{
			cur++;
		}
		else
		{
			prev++;
			if(cur!=prev)//防止无用交换
			{
				Swap(&a[cur], &a[prev]);
			}
			cur++;
		}
	}
	Swap(&a[prev], &a[keyi]);
	return prev;
}
void QuickSort(int* arr, int begin, int end)
{
	if (begin >= end)//等于是只有一个数需要排,大于是没有数需要排
	{
		return;
	}
	int keyi = QuickSortPart4(arr, begin, end);
	if (end - begin >= 5)//小区间优化
	{
		QuickSort(arr, begin, keyi - 1);
		QuickSort(arr, keyi + 1, end);
	}
	else
	{
		InsertSort(arr + begin, end - begin + 1);
		InsertSort(arr + keyi + 1, end - keyi);
	}
}

精简版
建议学会上面的后再看这个

int QuickSortPart3(int* a, int left, int right)//快排快慢指针
{
	int* Maxi = (&a[left], &a[right], &(a[(left + right) / 2]));
	Swap(&a[left], Maxi);
	int keyi = left;
	int prev = left;
	int cur = left+1;
	while (cur <= right)
	{
		if (a[cur] < a[keyi]&& ++prev!= cur)
		{
			Swap(&a[prev], &a[cur]);
		}
		cur++;
	}
	Swap(&a[prev],&a[keyi]);
	return prev;
}

快速排序非递归

快排的非递归版本要借助栈来实现,但也需要使用找 keyi 的函数。先将组需要排序的数据的最后一个元素和第一个元素依次入栈,保存后一次出栈,然后进行分割(分割的时候就已经调整过位置了)。分割后再将前面序列和后面序列的尾和首依次入栈,再保存前面的序列的首尾元素,依次出栈…直到栈空为止,栈空后,意味着数组所有数据已经调整结束。

void QuickSortNorn(int* a, int begin, int end)
{
	ST st;
	STInit(&st);
	STPush(&st,end);
	STPush(&st,begin);
	while (!STEmpty(&st))
	{
		int left =STTop(&st);
		STPop(&st);
		int right =STTop(&st);
		STPop(&st);
		int keyi = QuickSortPart1(a, left, right);
		if (keyi + 1 < right)
		{
			STPush(&st, right);
			STPush(&st, keyi + 1);
		}
		if (keyi - 1 > left)
		{
			STPush(&st, keyi - 1);
			STPush(&st, left);
		}
	}
	STDestroy(&st);
}

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

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

相关文章

【JVM】垃圾回收(GC)详解

垃圾回收&#xff08;GC&#xff09;详解 一. 死亡对象的判断算法1. 引用计数算法2. 可达性分析算法 二. 垃圾回收算法1. 标记-清除算法2. 复制算法3. 标记-整理算法4. 分代算法 三. STW1. 为什么要 STW2. 什么情况下 STW 四. 垃圾收集器1. CMS收集器&#xff08;老年代收集器&…

kubernetes教程-基本学习环境配置

kubernetes教程-基本学习环境配置 安装必要工具 kubectl Kubernetes的命令行工具&#xff0c; kubectl, 允许你在Kubernetes集群中运行命令. 你可以用kubectl来部署应用, 检查和管理集群资源, 并查看日志。有关更多信息&#xff0c;包括 kubectl的全部命令完整列表, 查看 ku…

string类的使用方式的介绍

目录 前言 1.什么是STL 2. STL的版本 3. STL的六大组件 4.STL的缺陷 5.string 5.1 为什么学习string类&#xff1f; 5.1.1 C语言中的字符串 5.2 标准库中的string类 5.3 string类的常用接口的使用 5.3.1 构造函数 5.3.2 string类对象的容量操作 5.3.3 string类对象…

【iptables 实战】06 iptables网络防火墙实验

一、现状说明 在上一节中&#xff0c;我们将两个网段的机器&#xff0c;通过中间机器的网络转发&#xff0c;能达到互通。再来回顾一下这个网络连接的图 这一节&#xff0c;我们将通过设置机器B的iptables规则&#xff0c;来做一些防火墙实验 机器A模拟公网的一台服务器&#…

【C++进阶之路】C++11(上)

文章目录 一、列表初始化1.{}2.initializer_list 二、声明1.auto2.deltype 三、右值与左值1.基本概念2.应用场景1.左值引用2.右值引用3.完美转发4.万能引用 四、新增默认成员函数五、lambda表达式1.基本语法1.1捕捉列表1.2参数列表1.3返回类型1.4函数体 2.底层原理 总结 一、列…

GPT系列模型解读:GPT-1

GPT系列 GPT&#xff08;Generative Pre-trained Transformer&#xff09;是一系列基于Transformer架构的预训练语言模型&#xff0c;由OpenAI开发。以下是GPT系列的主要模型&#xff1a; GPT&#xff1a;GPT-1是于2018年发布的第一个版本&#xff0c;它使用了12个Transformer…

2021-06-20 51单片机基于STC89C52RC的简易秒表的设计与实现(外部中断1和2)

缘由基于STC89C52RC的简易秒表的设计与实现_编程语言-CSDN问答 1.功能要求&#xff1a; K1键做启动停止秒表&#xff08;外部中断0&#xff09;&#xff0c;K2键做秒表归零&#xff08;外部中断1&#xff09;&#xff0c;4位数码管动态扫描显示&#xff0c;定时范围改成0到00…

房产政策松绑,VR看房助力市场回春

近日房贷利率、房产限购开始松绑&#xff0c;房地产市场逐渐被激活&#xff0c;房产行业的线上服务能力&#xff0c;也愈发的受到了重视。随着房贷利率、首付比例变化的消息逐渐推出&#xff0c;部分用户开始入手房产市场&#xff0c;因此房产行业的线上服务也需要不断升级&…

【计算机组成原理】读书笔记第五期:通过汇编语言了解程序的实际构成

目录 写在开头 汇编语言和本地代码的关系 汇编语言的源代码 伪指令 汇编的基本语法 常见的汇编指令 mov push和pop 函数的使用机制 函数的调用 函数参数的传递与返回值 全局变量 局部变量 程序的流程控制 循环语句 条件分支 通过汇编语言了解程序运行方式的必…

RTP/RTCP 协议讲解

文章目录 前言一、RTP 协议1、RTP 协议概述2、RTP 工作机制3、RTP 协议的报文结构4、wireshark 抓取 RTP 报文 二、RTCP 协议1、RTCP 协议概述2、RTCP 工作机制3、RTCP 数据报4、wireshark 抓取 RTCP 报文 三、RTSP 和 RTP 的关系四、易混淆概念1、RTP over UDP 和 RTP over RT…

typescript 泛型详解

typescript 泛型 泛型是可以在保证类型安全前提下&#xff0c;让函数等与多种类型一起工作&#xff0c;从而实现复用&#xff0c;常用于: 函数、接口、class中。 需求:创建一个id 函数&#xff0c;传入什么数据就返回该数据本身(也就是说&#xff0c;参数和返回值类型相同)。 …

从 0 到 1 ,手把手教你编写《消息队列》项目(Java实现) —— 创建项目 / 创建核心类

文章目录 一、创建SpringBoot项目二、创建核心类创建 Exchange类创建 MSGQueue类创建 Binding类创建Message类 一、创建SpringBoot项目 在项目中添加这四个依赖! 二、创建核心类 交换机 :Exchange 队列 :Queue 绑定关系: Binding 消息 :Message 这些核心类都存在于 BrokerSe…

常见加密和解密方法介绍。

介绍常见的加密和解密方法。 加密是利用数学方法将明文转化为密文&#xff0c;从而达到保护数据的目的。 通过加密可保证数据的机密性、完整性、鉴别性。 机密性&#xff1a;通过数据加密实现。只允许特定用户访问和阅读信息。 完整性&#xff1a;通过数字加密、散列、数字签名…

【生物信息学】使用皮尔逊相关系数进行相关性分析

目录 一、实验介绍 二、实验环境 1. 配置虚拟环境 2. 库版本介绍 3. IDE 三、实验内容 0. 导入必要的工具 1. cal_pearson&#xff08;计算皮尔逊相关系数&#xff09; 2. 主程序 a. 实验1&#xff08;较强的正相关关系&#xff09;&#xff1a; b. 实验2&#xff0…

计算机网络学习易错点(持续更新~~~)

目录 概述 1.internet和Internet的区别 2.面向连接和无连接 3.不同的T 4.传输速率和传播速率 5.传播时延和传输时延&#xff08;发送时延&#xff09; 6.语法&#xff0c;语义和同步 一.物理层 1.传输媒体与物理层 2.同步通信和异步通信 3.位同步&#xff08;比特同…

leetCode 53.最大子数和 图解 + 贪心算法/动态规划+优化

53. 最大子数组和 - 力扣&#xff08;LeetCode&#xff09; 给你一个整数数组 nums &#xff0c;请你找出一个具有最大和的连续子数组&#xff08;子数组最少包含一个元素&#xff09;&#xff0c;返回其最大和。 子数组 是数组中的一个连续部分。 示例 1&#xff1a; 输入…

C++面试题准备

文章目录 一、线程1.什么是进程&#xff0c;线程&#xff0c;彼此有什么区别?2.多进程、多线程的优缺点3.什么时候用进程&#xff0c;什么时候用线程4.多进程、多线程同步&#xff08;通讯&#xff09;的方法5.父进程、子进程的关系以及区别6.什么是进程上下文、中断上下文7.一…

FFMPEG 视频类过滤器学习整理

addroi 作用 在视频帧上标记一块感兴趣的区域。 帧数据被原封不动地传递&#xff0c;但元数据被附加到帧&#xff0c;指示可能影响后续编码行为的感兴趣区域。可以通过多次应用过滤器来标记多个区域。 参数 qoffset: 应用在此区域的量化偏移。 参数范围&#xff1a;-1 ~ …

笔记一:odoo透视表和图表

透视表 1、首先在xml文件添加pivot 说明&#xff1a;&#xff08;1&#xff09;根元素pivot中属性&#xff1a; disable_linking&#xff1a;设置为True&#xff0c;删除表格单元格到列表视图的链接 display_quantity&#xff1a;设置为True&#xff0c;默认显示“数量”列 d…

什么是FOSS

FOSS 是指 自由和开放源码软件(Free and Open Source Software)。这并不意味着软件是免费的。它意味着软件的源代码是开放的&#xff0c;任何人都可以自由使用、研究和修改代码。这个原则允许人们像一个社区一样为软件的开发和改进做出贡献。