排序算法(4)之快速排序(1)

news2024/12/28 2:06:37

 个人主页:C++忠实粉丝
欢迎 点赞👍 收藏✨ 留言✉ 加关注💓本文由 C++忠实粉丝 原创

排序算法(4)之快速排序(1)

收录于专栏【数据结构初阶
本专栏旨在分享学习数据结构学习的一点学习笔记,欢迎大家在评论区交流讨论💌

目录

1.常见排序算法

 2.快速排序

2.1快速排序的概念

2.2快速排序的版本

2.2.1 hoare版本

2.2.2 挖坑法

2.2.3 前后指针版本

3. 快速排序的时间复杂度

4.总结


1.常见排序算法

 

 2.快速排序

 2.1快速排序的概念

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

// 假设按照升序对array数组中[left, right)区间中的元素进行排序
void QuickSort(int array[], int left, int right)
{
	if (right - left <= 1)
		return;
	// 按照基准值对array数组的 [left, right)区间中的元素进行划分
	int div = partion(array, left, right);
}
// 划分成功后以div为边界形成了左右两部分 [left, div) 和 [div+1, right)
// 递归排[left, div)
QuickSort(array, left, div);
// 递归排[div+1, right)
QuickSort(array, div + 1, right);

上述为快速排序递归实现的主框架,发现与二叉树前序遍历规则非常像,同学们在写递归框架时可想想二叉树前序遍历规则即可快速写出来,后序只需分析如何按照基准值来对区间中数据进行划分的方式即可。 

2.2快速排序的版本

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

2.2.1 hoare版本

在 Hoare 版本中,划分过程如下:

  1. 选择基准值: 通常选择数组的第一个元素作为基准值,也可以随机选择数组中的某个元素。

  2. 划分过程: 使用两个指针,一个从左边开始(左指针),一个从右边开始(右指针)。它们分别向中间移动,直到找到需要交换的元素。

  3. 具体步骤:

    • 左指针移动: 从左往右找到第一个大于或等于基准值的元素。
    • 右指针移动: 从右往左找到第一个小于或等于基准值的元素。
    • 交换元素: 如果左指针所指元素大于基准值,并且右指针所指元素小于基准值,则交换这两个元素。
    • 继续移动指针: 继续移动左右指针,直到它们相遇。
  4. 交换基准值: 最后将基准值与右指针所指的元素进行交换,使得基准值左侧的元素都小于或等于基准值,右侧的元素都大于或等于基准值。

假设有一个数组 arr 如下:

arr = [6, 5, 3, 1, 8, 7, 2, 4] 

我们选择数组的第一个元素作为基准值(pivot)。现在,我们按照 Hoare 版本的方法来进行划分和排序。

  1. 初始状态:

    数组:[ [6, 5, 3, 1, 8, 7, 2, 4] ],左指针 (left) 初始在数组开头,右指针 (right) 初始在数组结尾。
  2. 第一轮划分过程:

    现在数组变为:[ [4, 5, 3, 1, 6, 7, 2, 8] ]

    继续交换 arr[left]arr[right]

    数组变为:[ [4, 5, 3, 1, 6, 2, 7, 8] ]

    现在 leftright 相遇,停止第一轮划分。

  3. 左指针 left 开始向右移动,直到找到一个大于或等于基准值 6 的元素。在这个例子中,left 移动到 8 处。
    • 右指针 right 开始向左移动,直到找到一个小于或等于基准值 6 的元素。在这个例子中,right 移动到 4 处。
    • 交换 arr[left] 和 arr[right],因为 8 > 6 且 4 < 6,所以交换它们。
  4. left 继续向右移动,移动到 7 处。
    • right 继续向左移动,移动到 6 处。
  5. 基准值位置确定:

    将基准值 6 与 arr[right](即 2)交换,数组变为:[ [4, 5, 3, 1, 2, 6, 7, 8] ]此时 6 左侧的元素小于或等于 6,右侧的元素大于或等于 6
  6. 递归调用:

    分别对基准值左右两侧的子数组递归进行快速排序。

 代码展示:

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[keyi], &a[begin]);
	keyi = begin;
	//[left,keyi-1] keyi [keyi+1, right]
	QuickSort(a, left, keyi - 1);
	QuickSort(a, keyi + 1, right);
}
  1. 函数定义和初始条件:

    void QuickSort(int* a, int left, int right) { if (left >= right) return; int keyi = left; int begin = left, end = right;
    • QuickSort 函数接收一个整数数组 a,以及排序范围的左右边界 left 和 right
    • 如果 left >= right,即排序范围为空或只有一个元素,直接返回,不需要继续排序。
    • keyi 初始化为 left,表示选取第一个元素作为基准值的索引。
    • begin 和 end 初始化为 left 和 right,用于从两端向中间扫描。
  2. 分区过程:

    while (begin < end) { // 右边找小 while (begin < end && a[end] >= a[keyi]) { end--; } // 左边找大 while (begin < end && a[begin] <= a[keyi]) { ++begin; } Swap(&a[begin], &a[end]); } 这是 Hoare 分区的实现部分:
    • 交换这两个元素的位置,确保所有小于基准值的元素都位于基准值的左侧,所有大于基准值的元素都位于右侧。
    • 从数组的左端开始,找到第一个大于基准值 a[keyi] 的元素。
    • 从数组的右端开始,找到第一个小于基准值 a[keyi] 的元素。
  3. 确定基准值的最终位置:

    Swap(&a[keyi], &a[begin]); keyi = begin; 将基准值 a[keyi] 移动到它最终应该处于的位置 begin,并更新 keyi
  4. 递归调用排序左右子数组:

    QuickSort(a, left, keyi - 1); QuickSort(a, keyi + 1, right); 递归调用 QuickSort 函数对基准值左右两侧的子数组进行排序。

这里的递归过程就拿我们上面图解的序列举例

经过一次排序,我们得到左边比key小,右边比key大,然后便是递归的过程. 

2.2.2 挖坑法

 

void QuickSort(int* a, int left, int right) {
	if (left >= right)
		return;
	int key = a[left]; // 选择第一个元素作为基准值
	int low = left, high = right;
	while (low < high) {
		// 从右向左找到第一个小于基准值key的元素
		while (low < high && a[high] >= key)
			high--;
		if (low < high) {
			a[low] = a[high]; // 使用 a[high] 的值填充 a[low] 的坑
			low++;
		}

		// 从左向右找到第一个大于基准值key的元素
		while (low < high && a[low] <= key)
			low++;
		if (low < high) {
			a[high] = a[low]; // 使用 a[low] 的值填充 a[high] 的坑
			high--;
		}
	}
	a[low] = key; // 将基准值放入最终的坑中
	int pivot = low; // 基准值的最终位置
	QuickSort(a, left, pivot - 1); // 对左子数组递归排序
	QuickSort(a, pivot + 1, right); // 对右子数组递归排序
}

 

挖坑法(Lomuto分区)是快速排序的一种经典实现方式,与 Hoare分区方案相比,其主要区别在于如何选择基准元素和如何进行元素交换。下面是使用挖坑法实现的快速排序算法的详细解释:

实现步骤:

  1. 函数定义和初始条件

    QuickSort 函数接收整数数组 a,以及排序范围的左右边界 left 和 right。如果 left >= right,说明排序范围为空或只有一个元素,直接返回。
  2. 挖坑并进行分区:将找到的大于 key 的元素填充到 high 所指的坑中,并将 high 向左移动。接着从数组左端 low 开始,向右寻找第一个大于基准值 key 的元素。

    将找到的小于 key 的元素填充到 low 所指的坑中,并将 low 向右移动。从数组右端 high 开始,向左寻找第一个小于基准值 key 的元素。
  3. 填坑完成分区

    将基准值放入最终的坑中,确定基准值的最终位置
  4. 递归排序左右子数组

    pivot 是基准值的最终位置,左侧元素都小于基准值,右侧元素都大于基准值。递归地对左右两个子数组进行快速排序。
  5. 总结:

  • 挖坑法是快速排序的一种高效分区方案,其核心思想是通过不断填坑的方式将数组分为小于和大于基准值的两部分,然后递归地对这两部分进行排序。
  • 比较与 Hoare分区方案的区别在于处理基准值的交换策略和分区过程中的具体操作顺序。
  • 在实际应用中,挖坑法通常比 Hoare分区方案更高效,因为它减少了元素的移动次数。

2.2.3 前后指针版本

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

	int keyi = left;

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

		cur++;
	}

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

	QuickSort(a, left, keyi - 1);
	QuickSort(a, keyi + 1, right);
}
  • keyi 初始化为 left,作为当前分区的基准元素索引。
  • prev 初始化为 left,表示当前小于基准值的元素应该放置的位置。
  • cur 初始化为 prev + 1,从基准元素的下一个位置开始向右遍历数组。

while 循环中:

  • 如果 a[cur] 小于 a[keyi],则将 a[cur] 与 a[prev+1] 位置的元素交换,并增加 prev 的值。
  • 遍历完成后,将基准元素 a[keyi] 与 a[prev] 位置的元素交换,这样基准元素就被放置到了正确的位置,并返回基准元素的新索引 prev

在实现上,它遵循了快速排序的基本思路:选择一个基准元素,通过分区操作将数组分为两部分,然后递归地对每部分进行排序。 

图解:

3. 快速排序的时间复杂度

平均情况时间复杂度

在平均情况下,快速排序的时间复杂度为 O(n log n)

  • 分析
    • 快速排序的核心是分区操作,选择一个基准元素并将数组分为两部分。在理想情况下,每次分区后基准元素大约将数组划分为两个大小相等的子数组。
    • 假设每次分区的时间复杂度为 O(n),其中 n 是当前数组的长度。在最均匀的情况下,每次递归都会将问题规模减半。
    • 因此,递归树的高度是 O(log n),每层的分区操作总共需要 O(n) 的时间,因此总体时间复杂度是 O(n log n)。

最坏情况时间复杂度

在最坏情况下,快速排序的时间复杂度为 O(n^2)

  • 分析
    • 最坏情况发生在每次选择的基准元素都是当前子数组中的最大或最小元素,导致分区后一个子数组为空,另一个子数组包含 n-1 个元素。
    • 这种情况下,递归树退化为一个类似于冒泡排序的结构,递归深度为 n,每层的时间复杂度为 O(n)。
    • 因此,总体时间复杂度为 O(n) * O(n) = O(n^2)。

最佳情况时间复杂度

在最佳情况下,即每次分区都能均匀划分数组的情况下,时间复杂度也是 O(n log n)

4.总结

快速排序的平均时间复杂度为 O(n log n),它的实际性能优秀且适用于大部分情况。然而,在处理大部分已经有序的数组时,快速排序可能会退化到 O(n^2),这时可以考虑采用随机化快速排序或者其他改进版本来避免最坏情况的发生。这也就是我下个章节需要讲到的快速排序的优化以及用非递归实现快速排序.

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

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

相关文章

【HarmonyOS】HarmonyOS NEXT学习日记:三、初识ArkUI

【HarmonyOS】HarmonyOS NEXT学习日记&#xff1a;三、初识ArkUI 忘掉HTML和CSS&#xff0c;ArkUI里构建页面的最小单位就是 “组件”&#xff0c;所以今天的目标就是认识一些常用的基础组件&#xff0c;以及他们的用法&#xff0c;对ArkUI形成一个基本认识。 基本组成 了解…

Java反射机制基础知识赏析、接口、实现类、方法

前言 最近打算手写一个RPC&#xff0c;但奈何自己Java基础知识中的反射就很欠缺&#xff0c;第一章就看不太懂了&#xff0c;特地编写了几个小Demo验证一下Java中关于反射的基础知识。 目录组织结构 代码编写 // TestService接口 package reflect.testServices;import main.v…

图神经网络实战(17)——深度图生成模型

图神经网络实战&#xff08;17&#xff09;——深度图生成模型 0. 前言1. 变分图自编码器2. 自回归模型3. 生成对抗网络小结系列链接 0. 前言 我们已经学习了经典的图生成算法&#xff0c;虽然它们能够完成图生成任务&#xff0c;但也存在一些问题&#xff0c;促使基于图神经网…

pytorch学习(四)绘制loss和correct曲线

这一次学习的时候静态绘制loss和correct曲线&#xff0c;也就是在模型训练完成后&#xff0c;对统计的数据进行绘制。 以minist数据训练为例子 import torch from torch import nn from torch.utils.data import DataLoader from torchvision import datasets from torchvisi…

GESP CCF C++ 三级认证真题 2024年6月

第 1 题 小杨父母带他到某培训机构给他报名参加CCF组织的GESP认证考试的第1级&#xff0c;那他可以选择的认证语言有&#xff08;&#xff09;种。 A. 1 B. 2 C. 3 D. 4 第 2 题 下面流程图在yr输入2024时&#xff0c;可以判定yr代表闰年&#xff0c;并输出 2月是29天 &#x…

python-字符金字塔(赛氪OJ)

[题目描述] 请打印输出一个字符金字塔&#xff0c;字符金字塔的特征请参考样例。输入格式&#xff1a; 输入一个字母&#xff0c;保证是大写。输出格式&#xff1a; 输出一个字母金字塔&#xff0c;输出样式见样例。样例输入 C样例输出 A ABA …

【前端8】element ui常见页面布局:注意事项

【前端8】element ui常见页面布局&#xff1a;注意事项 写在最前面遇到的问题Element UI 常见页面布局&#xff1a;注意事项1. 了解基本布局组件常用的菜单1多一个下角 常用的菜单2 2. 栅格系统的使用3. 响应式布局4. Flex 布局的应用5. 避免滥用嵌套6. 处理边距和填充 小结 &a…

基于STC89C51单片机的烟雾报警器设计(煤气火灾检测报警)(含文档、源码与proteus仿真,以及系统详细介绍)

本篇文章论述的是基于STC89C51单片机的烟雾报警器设计的详情介绍&#xff0c;如果对您有帮助的话&#xff0c;还请关注一下哦&#xff0c;如果有资源方面的需要可以联系我。 目录 摘要 原理图 实物图 仿真图 元件清单 代码 系统论文 资源下载 摘要 随着现代家庭用火、…

TikTok内嵌跨境商城全开源_搭建教程/前端uniapp+后端源码

多语言跨境电商外贸商城 TikTok内嵌商城&#xff0c;商家入驻一键铺货一键提货 全开源完美运营&#xff0c;接在tiktok里面的商城内嵌&#xff0c;也可单独分开出来当独立站运营 二十一种语言&#xff0c;可以做很多国家的市场&#xff0c;支持商家入驻&#xff0c;多店铺等等…

服务器IP和电脑IP有什么不同

服务器IP和电脑IP有什么不同&#xff1f;在当今的信息化时代&#xff0c;IP地址作为网络世界中不可或缺的元素&#xff0c;扮演着举足轻重的角色。然而&#xff0c;对于非专业人士来说&#xff0c;服务器IP和电脑IP之间的区别往往模糊不清。本文旨在深入探讨这两者之间的不同&a…

若依前端和后端时间相差8小时

原因基类未设置时区 实体类继承 BaseEntity 加上timezone"GMT8" /** 创建时间 */ JsonFormat(pattern "yyyy-MM-dd HH:mm:ss" , timezone"GMT8") private Date createTime; 解决

golang程序性能提升改进篇之文件的读写---第一篇

背景&#xff1a;接手的项目是golang开发的&#xff08;本人初次接触golang&#xff09;经常出现oom。这个程序是计算和io密集型&#xff0c;调用流量属于明显有波峰波谷&#xff0c;但是因为各种原因&#xff0c;当前无法快速通过serverless或者动态在高峰时段调整资源&#x…

MViTv2:Facebook出品,进一步优化的多尺度ViT | CVPR 2022

论文将Multiscale Vision Transformers (MViTv2) 作为图像和视频分类以及对象检测的统一架构进行研究&#xff0c;结合分解的相对位置编码和残差池化连接提出了MViT的改进版本 来源&#xff1a;晓飞的算法工程笔记 公众号 论文: MViTv2: Improved Multiscale Vision Transforme…

Fiddler抓包过滤host及js、css等地址

1、如上图所示 在Filter页面中勾选Hide if URL contains&#xff1b;输入框输入 REGEX:\.(js|css|png|google|favicon\?.*) 隐藏掉包含js、css、png、google等的地址&#xff1a; Hide if URL contains: REGEX:\.(js|css|png|google|favicon\?.*) 2、使Filters设置生效 A…

微软新版WSL 2.3.11子系统带来“数百个新内核模块“和新功能

微软今天发布了新版的 Windows Subsystem for Linux(WSL)。与当前的 WSL 2.2.4 稳定版相比&#xff0c;WSL 2.3.11 具有许多特性&#xff1a;它从旧版的 Linux 5.15 LTS 内核转到了 Linux 6.6LTS内核。今天的发布说明指出&#xff0c;WSL 2.3.11 基于 Linux 6.6.36.3&#xff0…

【C++刷题】[UVA 489]Hangman Judge 刽子手游戏

题目描述 题目解析 这一题看似简单其实有很多坑&#xff0c;我也被卡了好久才ac。首先题目的意思是&#xff0c;输入回合数&#xff0c;一个答案单词&#xff0c;和一个猜测单词&#xff0c;如果猜测的单词里存在答案单词里的所有字母则判定为赢&#xff0c;如果有一个字母是答…

力扣622.设计循环队列

力扣622.设计循环队列 通过数组索引构建一个虚拟的首尾相连的环当front rear时 队列为空当front rear 1时 队列为满 (最后一位不存) class MyCircularQueue {int front;int rear;int capacity;vector<int> elements;public:MyCircularQueue(int k) {//最后一位不存…

基于python的三次样条插值原理及代码

1 三次样条插值 1.1 三次样条插值的基本概念 三次样条插值是通过求解三弯矩方程组&#xff08;即三次样条方程组的特殊形式&#xff09;来得出曲线函数组的过程。在实际计算中&#xff0c;还需要引入边界条件来完成计算。样条插值的名称来源于早期工程师制图时使用的细长木条&…

【机器学习】--过采样原理及代码详解

过采样&#xff08;Oversampling&#xff09;是一个在多个领域都有应用的技术&#xff0c;其具体含义和应用方法会根据领域的不同而有所差异。以下是对过采样技术的详细解析&#xff0c;主要从机器学习和信号处理两个领域进行阐述。 一、机器学习中的过采样 在机器学习中&…

未来的社交标杆:如何通过AI让Facebook更加智能化?

在当今信息爆炸的时代&#xff0c;社交媒体平台的智能化已成为提高用户体验和互动质量的关键因素。Facebook&#xff0c;作为全球最大的社交平台之一&#xff0c;通过人工智能&#xff08;AI&#xff09;的广泛应用&#xff0c;正不断推进其智能化进程。本文将探讨Facebook如何…