【数据结构】快速排序(详解)

目录

快速排序

历史:

基本思想:

主框架:

下面解释实现单次排序的几种版本:

1.Hoare版本

2. 挖坑法

3. 前后指针法

快速排序的实现包括递归与非递归:

1. 递归实现:(即开头的基本框架)

2. 非递归:(迭代)

下面我们通过栈来演示非递归实现快速排序:


快速排序

历史:

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

基本思想:

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

主框架:

上述为快速排序递归实现的主框架,我们可以发现它和二叉树的前序遍历十分相似 

下面解释实现单次排序的几种版本:
1.Hoare版本

单趟动图演示:

步骤:

  1. 选择基准值:通常选择序列的第一个元素或最后一个元素作为基准值。

  2. 设置两个int变量记录数组下标:一个记录序列的开始的下标(left),另一个记录序列的末尾的下标(right)。

  3. 开始分区

    • 先让right++,直到找到一个比基准值大的元素。
    • 再让left--,直到找到一个比基准值小的元素。
    • 交换这两个元素的位置,并继续right++。
    • 重复上述步骤,直到left >= right。
    • 注意:right先加,保证下一步骤与基准值交换的值小于基准值
  4. 交换基准值与right位置的值:此时,基准值左边的所有元素都比基准值小,基准值右边的所有元素都比基准值大。

代码实现:

int PartSort1(int* a, int left, int right) {
	int key = left;
	while (left < right) {
		while(a[right] >= a[key]&&left < right) {
			right--;
		}
		while(a[left] <= a[key]&&left < right) {
			left++;
		}
		swap(&a[left], &a[right]);
	}
	swap(&a[key], &a[right]);
	return left;
}
2. 挖坑法

单趟动图演示:

步骤:

  1. 选择基准元素:在待排序的序列中,选择一个元素作为基准元素(key)。这个元素可以是序列的第一个元素、最后一个元素或者任意一个元素,甚至是随机选取的一个元素。

  2. 挖坑:将基准元素从原位置移除,形成一个“坑”(hole)。

  3. 填坑:从序列的一端开始(可以是左端或右端),将遇到的第一个比基准元素小(或大)的元素填入坑中,并形成一个新的坑。这里有两种情况:

    • 如果从右向左遍历,遇到比基准元素小的元素,则将其填入坑中,并继续从左向右遍历。这里代码实现选择该情况
    • 如果从左向右遍历,遇到比基准元素大的元素,则将其填入坑中,并继续从右向左遍历。
  4. 此时,基准元素左边的所有元素都小于基准元素,右边的所有元素都大于或等于基准元素。

代码实现:

int PartSort2(int* a, int left, int right) {
	int x = a[left];
	int key = left;
	while (left < right) {
		while (a[right] >= x && left < right) {
			right--;
		}
		a[key] = a[right];
		key = right;
		while (a[left] <= x && left < right) {
			left++;
		}
		a[key] = a[left];
		key = left;
	}
	a[key] = x;
	return key;
}
3. 前后指针法

单趟动图演示:

基本步骤:

  • 从待排序的数组中选择一个元素(key)作为基准。通常选择第一个或最后一个元素作为基准,但也可以随机选择。
  • 设置两个下标,一个记录序列的开始的下标(pre),即pre=left,另一个记录pre下一个元素的下标的下标(cur),即cur=pre+1;
  • 当(cur<=right)时,循环执行:cur找比key小的元素,如果a[cur]<=a[key],交换a[cur]和a[pre],否则pre++,每次循环cur++

代码实现:

int PartSort3(int* a, int left, int right) {
	int key = left;
	int pre = left;
	int cur = pre + 1;
	while (cur <= right) {
		if (a[cur] <= a[key] && ++pre != cur) {
			swap(&a[cur], &a[pre]);
		}
		cur++;
	}
	swap(&a[key], &a[pre]);
	return pre;
}
快速排序的实现包括递归与非递归:
1. 递归实现:(即开头的基本框架)
void QuickSort(int* a, int left, int right) {
	if (left >= right) {
		return;
	}
	//int key = PartSort1(a, left, right);
	//int key = PartSort2(a, left, right);
	int key = PartSort3(a, left, right);
	QuickSort(a, left, key - 1);
	QuickSort(a, key + 1, right);
}
2. 非递归:(迭代)

在非递归版本中,我们使用栈(或队列)来保存待排序的子数组的起始和结束索引,而不是直接递归调用;栈相当于前序遍历,而队列相当于层序遍历

下面我们通过栈来演示非递归实现快速排序:

思路与步骤:

  1. 循环处理栈:当栈不为空时,执行以下步骤:

  • 弹出栈顶的两个元素,它们分别表示当前子数组的起始和结束索引。
  • 如果起始索引等于结束索引(即子数组只有一个元素),那么直接跳过,不需要排序。
  • 调用PartSort3函数对当前子数组进行划分,并返回划分后的基准索引key
  • 弹出之前压入的起始和结束索引(因为已经处理过这个子数组了)。
  • 根据key的位置,决定下一步的操作:
    • 如果key等于起始索引left,说明基准左边的元素都已经排好序,只需要对基准右边的元素进行排序,所以将key + 1right压入栈中。
    • 如果key等于结束索引right,说明基准右边的元素都已经排好序,只需要对基准左边的元素进行排序,所以将leftkey - 1压入栈中。
    • 如果key既不等于left也不等于right,说明基准左右两边都有需要排序的元素,所以将leftkey-1key+1right的子数组分别压入栈中
  • 2.完成:当栈为空时,表示所有子数组都已排序完成,此时整个数组也已排序完成。

代码实现:

typedef int StackNode;
typedef struct Stack {
	StackNode* a;
	int real;
	int capacity;
}Stack;
void InitStack(Stack* p) {
	assert(p);
	p->a = NULL;
	p->capacity = p->real = 0;
}
//扩容
void expansion(Stack*p) {
	int newcapacity = p->capacity == 0 ? 4 : 2 * p->capacity;
	StackNode* tmp = (StackNode*)realloc(p->a, sizeof(StackNode) * newcapacity);
	if (tmp == NULL) {
		perror("expansion::realloc::NULL");
		exit(0);
	}
	p->a = tmp;
	p->capacity = newcapacity;
}
//入栈
void Push(Stack*p,int x) {
	assert(p);
	if (p->capacity == p->real) {
		expansion(p);
	}
	p->a[p->real++] = x;
}
//出栈
void Pop(Stack* p) {
	assert(p);
	p->real--;
}
void StackDestroy(Stack* p) {
	assert(p);
	free(p->a);
	p->a = NULL;
	p->capacity = p->real = 0;
}
//快排代码实现
void QuickSortNonR(int* a, int left, int right) {
	if (left >= right) {
		return;
	}
	Stack stack;
	InitStack(&stack);
	Push(&stack, left);
	Push(&stack, right);
	while (stack.real) {
		left = stack.a[stack.real - 2];
		right = stack.a[stack.real - 1];
		if (left == right) {
			Pop(&stack);
			Pop(&stack);
			continue;
		}
		int key = PartSort3(a, left, right);
		Pop(&stack);
		Pop(&stack);
		if (key == left) {
			Push(&stack, key + 1);
			Push(&stack, right);
			continue;
		}
		if (key == right) {
			Push(&stack, left);
			Push(&stack, key - 1);
			continue;
		}
		Push(&stack, key + 1);
		Push(&stack, right);
		Push(&stack, left);
		Push(&stack, key - 1);
	}
	StackDestroy(&stack);
}

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

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

相关文章

正在直播:Microsoft Copilot Studio 新增支持Copilot代理、Copilot扩展等多项功能

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

现代C++ 如何使用 Lambda 使代码更具表现力、更容易理解?

使用 Lambda 使代码更具表现力 一、Lambda VS. 仿函数二、总结 一、Lambda VS. 仿函数 Lambda 是 C11 中最引人注目的语言特性之一。它是一个强大的工具&#xff0c;但必须正确使用才能使代码更具表现力&#xff0c;而不是更难理解。 首先&#xff0c;要明确的是&#xff0c;…

上位机图像处理和嵌入式模块部署(mcu之串口控制gpio)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 前面我们陆续学习了gpio输入、输出&#xff0c;串口输入、输出。其实有了这两个接口&#xff0c;就可以做产品了。因为我们可以通过发送串口命令&a…

【智能算法应用】北方苍鹰算法求解二维栅格路径规划问题

目录 1.算法原理2.二维路径规划数学模型3.结果展示4.参考文献5.代码获取 1.算法原理 【智能算法】北方苍鹰优化算法&#xff08;NGO)原理及实现 2.二维路径规划数学模型 栅格法模型最早由 W.E. Howden 于 1968 年提出&#xff0c;障碍物的栅格用黑色表示&#xff0c;可通过的…

element-plus表格的表单校验如何实现,重点在model和prop

文章目录 vue&#xff1a;3.x element-plus&#xff1a;2.7.3 重点&#xff1a; 1) tableData放到form对象里 2) form-item的prop要写成tableData.序号.属性 <!--table-表单校验--> <template><el-form ref"forms" :model"form"><e…

百度墨斗鱼文库创作中心上传验证码分析

之前发表过一篇&#xff0c;百度墨斗鱼后台上传文章分析的文章。时隔几个月&#xff0c;文件预览接口加了asc- token 和 验证码&#xff08;百度v2&#xff09;。闲来没事分析一下。所有的分析都基于协议&#xff0c;不基于web自动化。 验证码目前有四种&#xff0c;验证码刷…

【Flutter】线性布局弹性布局层叠布局

&#x1f525; 本文由 程序喵正在路上 原创&#xff0c;CSDN首发&#xff01; &#x1f496; 系列专栏&#xff1a;Flutter学习 &#x1f320; 首发时间&#xff1a;2024年5月25日 &#x1f98b; 欢迎关注&#x1f5b1;点赞&#x1f44d;收藏&#x1f31f;留言&#x1f43e; 目…

第十五届“北斗杯”全国青少年空天科技体验与创新大赛安徽赛区阜阳市解读会议

5月19日&#xff0c;第十五届“北斗杯”全国青少年空天科技体验与创新大赛安徽赛区阜阳解读活动在阜阳市图书馆隆重举行。共青团阜阳市委员会学少部副部长丁晓龙、阜阳市师范大学物理系副主任黄银生教授、安徽科技报阜阳站站长李伟、市人工智能学会秘书长郭广泽、“北斗杯”安徽…

牛客NC236 最大差值【simple 动态规划 Java/Go/PHP】

题目 题目链接&#xff1a; https://www.nowcoder.com/practice/a01abbdc52ba4d5f8777fb5dae91b204 思路 不难看出该题可以使用动态规划的方式解题。 在循环数组的过程中&#xff0c;记录截止到当前位置-1的最小值&#xff0c; 然后用当前的值去计算最大的差值。Java代码 im…

AI时代下对于bloger的影响

前言 AI已经不是一个新词儿了&#xff0c;相信能看到这篇文章的同学都使用AI工具。AI工具已经渗透各行各业了。今天简谈一下AI对我们技术bloger的影响。 文章的访问量变少了 文章的访问量主要是两个渠道&#xff0c;博客平台内部推送&#xff0c;搜索引擎推送。AI的兴起肯定会…

每日练习之深度优先搜索——最大的湖

最大的湖 题目描述 运行代码 #include<iostream> using namespace std; bool mp[102][102]; int sum,num; int N,M,K; int dfs(int x,int y ) {if( mp[x][y] ){mp[x][y]0;sum;dfs(x1,y);dfs(x-1,y);dfs(x,y1);dfs(x,y-1);}return 0; } int main() {cin>>N>>…

【B站 heima】小兔鲜Vue3 项目学习笔记Day02

文章目录 Pinia1.使用2. pinia-计数器案例3. getters实现4. 异步action5. storeToRefsx 数据解构保持响应式6. pinia 调试 项目起步1.项目初始化和git管理2. 使用ElementPlus3. ElementPlus 主题色定制4. axios 基础配置5. 路由设计6. 静态资源初始化和 Error lens安装7.scss自…

无人机+飞行服务:无人机飞防服务(打药+施肥+播种)技术详解

无人机飞防服务&#xff0c;结合了先进的无人机技术与农业实践&#xff0c;为现代农业提供了高效、精准的打药、施肥和播种解决方案。以下是对这些技术的详细解析&#xff1a; 一、无人机打药技术 无人机打药技术利用无人机搭载喷雾设备&#xff0c;对农田进行精准施药。通过…

【Numpy】深入解析numpy.mgrid()函数

numpy.mgrid()&#xff1a;多维网格生成与数值计算的利器 &#x1f308; 欢迎莅临我的个人主页&#x1f448;这里是我深耕Python编程、机器学习和自然语言处理&#xff08;NLP&#xff09;领域&#xff0c;并乐于分享知识与经验的小天地&#xff01;&#x1f387; &#x1f393…

vuejs路由和组件系统

前端路由原理 createRouter * hash* window.addEventListener(hashChange)* 两种实现路由切换的模式&#xff1a;UI组件&#xff08;router-link&#xff0c;router-view&#xff09;&#xff0c;Api&#xff08;push()方法&#xff09; * history * HTML5新增的API &#xff0…

Qt下使用QImage和OpenCV实现图像的拼接与融合

文章目录 前言一、使用QImage进行水平拼接二、使用OpenCV进行水平拼接三、使用OpenCV进行图像融合四、示例完整代码总结 前言 本文主要讲述了在Qt下使用QImage和OpenCV实现图像的拼接与融合&#xff0c;并结合相应的示例进行讲解&#xff0c;以便大家学习&#xff0c;如有错误…

分割文本文件

分割一个.txt文件&#xff0c;可以选择在命令行中使用split指令&#xff0c;或者编写一段脚本进行操作。以下是一个简单的Python脚本来分割文本文件&#xff1a; def split_file(file, lines):# Open source filewith open(file, r) as source:count 0atEOF Falsewhile not …

齐护K210系列教程(三十四)_视觉PID巡线小车

视觉PID巡线小车 1.前言2.简介3.代码讲解3.1初始化3.2.色块查找3.3色块分析3.3.1 区域13.3.2 区域2 3.4 侦测关键点部分3.4.1正常巡线3.4.2 右转路口 3.4.3十字路口3.4. PID计算 4.完整代码5.小车端程序6.参考程序联系我们 1.前言 本课程主要讲述如何使用AIstart_k210主板完成…

SpringMVC接收请求参数的方式:

接收简单变量的请求参数 直接使用简单变量作为形参进行接收&#xff08;这里简单变量名称需要与接收的参数名称保持一致&#xff0c;否则需要加上RequestParam注解&#xff09;&#xff1a; 细节&#xff1a; 1&#xff1a;SpringMVC会针对常见类型&#xff08;八种基本类型及…

【Crypto】一眼就解密

文章目录 前言一眼就解密解题感悟 前言 Basic写累了&#xff0c;写写别的 一眼就解密 一眼md5试一试 小小flag 拿下&#xff01; 解题感悟 30秒搞定