[排序]快速排序——前后指针法(含非递归实现快排)

上一章我们具体介绍了hoare快速排序的具体实现,本篇我们将介绍快速排序的前后指针法来实现,与hoare快排的排序算法逻辑相似大同小异,本章我们还会实现非递归实现快速排序,希望能帮助到大家。

目录

前后指针法

快排的非递归实现


前后指针法

先来看一下动态逻辑图

前后指针法需要prev和cur两个指针

prev位于起始点key位置,cur在prev的前面,然后cur向前移动,若cur所指向的数小于key,则prev向前移动一位,然后与cur的值相交换,若 cur所指向的数大于key,则cur++,prev不动。

然后继续向下遍历,直到cur越界。最后key位置和prev进行交换

我们先进行单趟排序,再进行整体排序

以下是单趟排序的逻辑图

在单趟排序完成后,我们可以发现当key与prev交换后,key的左边都是比key小的数,key的右边都是比key大的数,和hoare排序的单趟整体逻辑类似,只是实现方式不同。

我们代码实现一下单趟排序

复制代码
int GetMid(int* a, int left, int right)
{
	int mid = (left + right) / 2;
	//left mid right
	if (a[mid] > a[left])
	{
		if (a[mid] < a[right])
			return mid;
		else if (a[left] > a[right])
			return left;
		else
			return right;
	}
	else
	{
		if (a[mid] > a[right])
			return mid;
		else if (a[right] > a[left])
			return left;
		else
			return right;
	}
}
void swap(int* a, int* b)
{
	int tmp = *a;
	*a = *b;
	*b = tmp;
}
int partsort(int a[], int left, int right)
{
	//三数取中
	int mid = GetMid(a, left, right);
	swap(&a[mid], &a[left]);
	int key = left;
	int prev = left;
	int cur = prev + 1;
	while (cur <= right)
	{
		if (a[cur] < a[key]&& ++prev != cur)
		{
			prev++;
			Swap(&a[cur], &a[prev]);
		}
			cur++;
	}
	swap(&a[prev], &a[key]);
    return prev;
}

我们通过比较,可以发现前后指针法相比于hoare更加清晰,虽然逻辑相同,但是这个更好理解。

单趟排序的时间复杂度也是O(N),没有效率上的差别。

二者都可以,大家可以自行理解,自行选择,掌握一种即可。

快排的非递归实现

如果仅仅是掌握递归类型的快排,还是略显不够的,因为递归是有缺陷的,在深度过深的情况下,递归总会有溢出的风险,所以我们可以用非递归进行优化,其实在上一章节小区间优化部分,已经减少了大部分的递归,我们这里将会不用递归实现快排。

先说结论,实现非递归,就需要其他的方式来替代递归,而递归其实就是要建立栈帧,那么这里就要用到栈来模拟递归。

我们可以先大致看一下递归展开图,如下

这只是部分的展开图,我们思考一下,在递归过程中,建立栈帧中所存的核心数据是什么?

其实由上图已经很清楚了,传入的三个参数a是不变的,一直在进行变化的就是left和right,即在栈帧中存的核心数据就是区间

大致的思路就如下所示

栈实现递归,实质上就是将递归中调用的区间,按照顺序压入栈中,再根据递归的顺序调用出栈

我们来辨析一下,递归实际上就调用函数,而调用函数是需要开辟栈的,所以递归过深会导致栈溢出,而在数据结构中,我们所创建的栈是从堆申请空间的,实质上是调用堆的区域,而堆的空间是远大于栈的,因此我们利用栈实现递归,就成功解决了递归过深栈溢出的问题。

这里我提一句,如果用队列实现递归也是可以的,但是由于队列是先进先出的,顺序需要是层序遍历的顺序,才能符合递归的顺序

而终止的条件,当单趟排序分割完成后,我们要进行判断,若区间大于1则继续排序,若等于1或者为0,说明该区间已经排序完成,不需要继续排序。

思路如上,我们实现代码。

代码实现如下

复制代码
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 key = partsort(a, begin, end);
		//[begin, key-1]  key  [key+1,end]
		//判断终止条件,若区间大于1,则继续排序
		if (key + 1 < end)
		{
			STPush(&st, key - 1);
			STPush(&st, begin);
		}
	} 
}

以上即快速排序的非递归实现,逻辑是比较巧妙的,希望大家能够理解

相关推荐
LuminousCPP1 小时前
数据结构 - 线性表第四篇:C 语言通讯录优化升级全记录(踩坑 + 思考)
c语言·开发语言·数据结构·经验分享·笔记·学习
AI算法沐枫1 小时前
深度学习python代码处理科研测序数据
数据结构·人工智能·python·深度学习·决策树·机器学习·线性回归
m0_629494733 小时前
LeetCode 热题 100-----26.环形链表 II
数据结构·算法·leetcode·链表
壹号用户3 小时前
用队列实现栈
数据结构·算法
浩浩测试一下4 小时前
汇编 标志位寄存器 (逆向分析 )
c语言·汇编·逆向·windows编程·标志寄存器
SuperByteMaster5 小时前
uart中断发送和接收处理
c语言
欧米欧5 小时前
C++进阶数据结构之搜索二叉树
开发语言·数据结构·c++
小江的记录本5 小时前
【Java基础】反射与注解:核心原理、自定义注解、注解解析方式(附《思维导图》+《面试高频考点清单》)
java·数据结构·python·mysql·spring·面试·maven
Trouvaille ~6 小时前
【Redis篇】初识 Redis:特性、应用场景与版本演进
数据结构·数据库·redis·分布式·缓存·中间件·持久化
社交怪人6 小时前
【浮点数相除的余】信息学奥赛一本通C语言解法(题号1029)
c语言·开发语言