排序——快排(递归/非递归)

news2025/6/30 19:18:22

目录

定义

递归

三种方法

1.hoare法

 2.挖坑法

 3.双指针法

整体

优化1

优化2

非递归


定义

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

说人话就是将数组中任意一个元素作为基准值,将比基准值小的元素放在基准值的左边,将比基准值大的元素放在基准值的右边,经过不断的排序,实现将所有数据排序。



递归

三种方法

在我们完成整体的排序前,我们先考虑一下单趟是如何实现的(本篇文章皆考虑升序)

1.hoare法

顾名思义,就是快排提出人本身使用的方法

简单来说,就是从数组的两边,分别寻找比基准值大的数和比基准值小的数,在两侧都找到之后,进行交换。

而为了方便,我们常常使用左右两侧其中一个作为基准值(采用左侧)。而当我们采用左侧为基准值时,我们需要先在右侧寻找较小值,同理,在我们采用右侧为基准值时,我们需要在左侧寻找最小值,具体原因后面会提到。

例如下面的数组

 我们需要先进行不断得交换

而这时,右侧(8)先寻找较小值,可以找到为5,而左侧(3)寻找较大值时会与右侧相遇。

而在相遇后,由于我们是让右侧先走,因此我们可以发现,停下来的位置是比基准值小的,而后面的位置都是比基准值大的,因此,我们在这里将停下来的位置(left/right)与基准值的位置进行交换。

这样便完成了一趟排序。

int Partion1(int* a, int left, int right)
{
	int keyi = left;
	while (left < right)
	{
		while (left < right && a[right] >= a[keyi])
			--right;
		while (left < right && a[left] <= a[keyi])
			++left;
		Swap(&a[left], &a[right]);
	}
	Swap(&a[left], &a[keyi]);
	return left;
}

 2.挖坑法

首先,我们选择左侧为一个坑位

之后从右侧开始,寻找比坑位内数值小的数据,使之与坑位内的数值交换,同时坑位也进行交换。之后,在从左侧(原本坑位所在的位置)向右寻找比坑位内数值大的数据,与坑位交换,依次进行。

 

 而当左侧或右侧与坑位相遇时,一趟排序便已经完毕。

int Partion2 (int* a, int left, int right)
{
	int pivot = left;
	while (left < right)
	{
		while (left < right && a[right] >= a[pivot])
			right--;
		Swap(&a[right], &a[pivot]);
		pivot = right;
		while (left < right&& a[left] <= a[pivot])
				left++;
		Swap(&a[left], &a[pivot]);
		pivot = left;
	}
	return pivot;
}

 3.双指针法

 我们需要两个指针prev和cur,初始时,prev指向左侧,cur指向左侧的下一个。

 

cur需要不断得bi向右进行便利,每当遇到比基准值(左侧数据)小的值时,将prev向右移动一位,并与cur位置的值进行交换。直到cur抵达右侧。

 而在cur抵达右侧时,便只需要将左侧(基准值)与prev所在位置的值进行交换

 同时,我们也可以进行一些小小的优化,例如在prev与cur指向同一位置时不需要交换

最后便如下所示

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

整体

在上面,我们提到了单趟排序的三种方法,而在我们结束单趟排序后,需要对基准值所在位置的左右两个区间的数据进行排序,直到这些区间的大小为0。我们便可以依此来完成整个的快排

void QuickSort(int* a, int left, int right)
{
	if (left >= right)
		return;
	int keyi = Partion1(a, left, right);
	QuickSort(a, left, keyi - 1);
	QuickSort(a, keyi + 1, right);
}

优化1

在写完整体的代码之后,我们可以发现,当keyi越接近区间的中间时,递归的次数就越少,因此,我们应该想办法使他位于区间的中间,即让单趟排序中的基准值的数值接近所有数据的中间位置。

但我们无法做到选择出一个完全接近中间的值,这也会消耗大量的时间,所以我们选择左侧、右侧、中间三个位置中数值不大不小的作为基准值,并将其放置在右侧。

int GetMidIndex(int* a, int left, int right)
{
	int mid = left + (right - left) >> 1;
	if (a[left] > a[mid])
	{
		if (a[right] < a[mid])
			return mid;
		else if (a[left] > a[right])
			return right;
		else
			return left;
	}
	else
	{
		if (a[right] < a[left])
			return left;
		else if (a[mid] > a[right])
			return right;
		else
			return mid;
	}
}

int Partion1(int* a, int left, int right)
{
    int mini = GetMidIndex(a, left, right);
    Swap(&a[mini], &a[left]);
    int keyi = left;
    //......
}

同时,补充一个小知识点。我们可以看到,在我们寻找中间位置时,我们采用的是这样一个方法

int mid = left + (right - left) >> 1;

这是因为,计算机计算时本质都是加法,在使用除法时也要转换为加法来计算,而这种方法效率更高。


优化2

在区间比较小的时候,快排依旧需要多层递归来完成排序,这时效率就不及其他排序了,因此,我们可以在其中加以判断,当区间(right-left)小于一个值时(可以为10),就采用其他排序来完成,例如我们之前所提到的效率较高的希尔排序,这里就不多做赘述。



非递归

在写完递归后,我们可以尝试一下非递归的写法。

首先,由于每一层递归都有所不同,我们难以轻易的写出来。

而每一层递归都需要有一个区间,我们这里采用栈来进行存储。

首先我们将整个区间存储进去

ST stack;
	StackInit(&stack);
	StackPush(&stack, left);
	StackPush(&stack, right);

存储过后,我们还是采用上面的三种方法进行排序,而排序的区间改为从堆中删除的前后两个坐标

int end = StackTop(&stack);
StackPop(&stack);
int begin = StackTop(&stack);
StackPop(&stack);
int keyi = Partion3(a, begin, end);

在递归中,我们在进行完一趟排序后,对 (left, keyi - 1)、(keyi + 1, right)两个区间进行下一步排序,而在这里,我们将这两个区间再次存储在栈中

StackPush(&stack, begin);
StackPush(&stack, keyi - 1);
StackPush(&stack, keyi + 1);
StackPush(&stack, end);

之后通过循环进行再次的排序和存储。

而在两个区间的大小为0时,我们便不需要再进行这两个区间的存储

if (begin < keyi - 1)
{
	StackPush(&stack, begin);
	StackPush(&stack, keyi - 1);
}
if (keyi + 1 < end)
{
	StackPush(&stack, keyi + 1);
	StackPush(&stack, end);
}

因此,循环的终止条件也就应该为栈中不存在数据

while (stack.top)

如此,我们便能完成整个排序的编写

void QuickSortNonR(int* a, int left, int right)
{
	ST stack;
	StackInit(&stack);
	StackPush(&stack, left);
	StackPush(&stack, right);
	while (stack.top)
	{
		int end = StackTop(&stack);
		StackPop(&stack);
		int begin = StackTop(&stack);
		StackPop(&stack);
		int keyi = Partion3(a, begin, end);
		if (begin < keyi - 1)
		{
			StackPush(&stack, begin);
			StackPush(&stack, keyi - 1);
		}
		if (keyi + 1 < end)
		{
			StackPush(&stack, keyi + 1);
			StackPush(&stack, end);
		}
	}
	StackDestroy(&stack);
}

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

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

相关文章

快速掌握e语言,以js语言对比,快速了解掌握。

易语言&#xff0c;怎么调试输出&#xff0c;查看内容 在js语言里&#xff0c;弹窗是 alert()在易语言里&#xff0c;弹窗是 信息框 (“弹出显示内容”, 0, “标题”, ); 在js语言里&#xff0c;调试输出是 console.log()在易语言里&#xff0c;调试输出是 调试输出 (“输出内…

开发过程中使用,可以早点下班的coding小技巧

前言 在实际开发过程中,通过时间的沉淀,一些老人常常能写出一些让小白大吃一惊“骚操作”,那些“骚操作”通常简单的离谱,却能做很多事,属实是让很多新人摸不着头脑。 做一件事时间长了,技巧也就有了。 下面来个情景小剧场: 初入职场小鱼仔:这傻逼是不是写错了,~~ s…

基于凸几何和K均值的高光谱端元提取算法(Matlab代码实现)

&#x1f468;‍&#x1f393;个人主页&#xff1a;研学社的博客 &#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜…

Sentinel统一异常处理

五.统一异常处理—BlockException 在上述规则测试中&#xff0c;当违反规则时&#xff0c;出来的异常信息页面不够友好和统一&#xff0c;我们可以通过设置统一的异常处理类&#xff0c;针对不同规则显示不同异常信息。 创建一个配置类&#xff0c;实现BlockExceptionHandler…

numpy数组,numpy索引,numpy中nan和常用方法

一&#xff1a;【numpy数组】 1.1为什么要学习numpy 1.快速 2.方便 3.科学计算的基础库 1.2什么是numpy 一个python中做科学计算的基础库&#xff0c;重在数值计算&#xff0c;也是大部分python科学计算库的基础库&#xff0c;多用于在大型&#xff0c;多维数组上执行数组运…

常用的键盘事件

1、键盘事件 键盘事件触发条件onkeyup某个键盘按键被松开时触发onkeydown某个键盘按键被按下时触发onkeypress某个键盘按键被按下时触发&#xff08;但它不识别功能键&#xff0c;比如ctrl、shift等&#xff09; 注意&#xff1a; 如果使用addEventListener不需要加ononkeypr…

Go 堆数据结构使用

说到 container/heap 下的堆数据结构&#xff0c;让我们不需要从零开始实现这个数据结构。如果只是日常工作&#xff0c;其实还挺难用到堆的&#xff0c;更多的还是在写算法题的时候会用到。 基本概念 堆分为大顶堆和小顶堆&#xff0c;区分这两种类型方便我们处理问题。大顶…

Docker安装Zookeeper教程(超详细)

生命无罪&#xff0c;健康万岁&#xff0c;我是laity。 我曾七次鄙视自己的灵魂&#xff1a; 第一次&#xff0c;当它本可进取时&#xff0c;却故作谦卑&#xff1b; 第二次&#xff0c;当它在空虚时&#xff0c;用爱欲来填充&#xff1b; 第三次&#xff0c;在困难和容易之…

第六章:关系数据理论

一、问题的提出、范式 1、【多选题】下列说法中正确的是&#xff1a; 正确答案&#xff1a; ABCD 2、【多选题】关系模式R&#xff08;项目序号&#xff0c;项目代码&#xff0c;项目名称&#xff09;&#xff0c;项目序号是码。一个项目代码只有一个项目名称。下列说法不正确…

文献检索报告

文献检索第一篇检索作业总结第一章检索任务1.1检索课题1.2确定选题所属学科1.3中英文检索词第二章检索策略与结果2.1检索中文期刊文献2.1.1 CNKI中国期刊全文数据库2.1.2 维普期刊全文数据库2.1.3 万方期刊数据库2.1.4 超星期刊全文2.2检索中文学位论文2.2.1 CNKI博硕学位论文数…

virtio-net发包流程分析

virtio-net发包流程分析 virtio-net发包流程前端驱动部分 总流程 start_xmit|---->free_old_xmit_skbs /* 释放backend处理过的desc */|---->xmit_skb /* 调用xmit_skb函数将网络包写入virtqueue */| |---->sg_set_buf /* 数据包头部填入scatterlist */| | |---->…

手撕红黑树、三种情况就可玩转红黑

&#x1f9f8;&#x1f9f8;&#x1f9f8;各位大佬大家好&#xff0c;我是猪皮兄弟&#x1f9f8;&#x1f9f8;&#x1f9f8; 文章目录一、红黑树概念二、红黑树性质三、红黑树 插入①变色&#xff08;c红 p红 g黑 u存在且红&#xff09;②旋转&#xff08;c红 p红 g黑 u存在且…

熟人服务器被黑,五种实战方法强化linux服务器安全性!

公司护网行动,五种实战方法,下面直接上实操: 1.修改ssh端口为59527,并开放防火墙端口 修改ssh配置文件 /etc/ssh/sshd_config,将端口号修改为59527.同时保留ssh默认的22端口,为了防止修改端口号失败以后,远程登录不上服务器 2.修改firewall配置 默认情况下,防火墙是…

JVM——垃圾回收

垃圾回收 1、如何判断对象可以回收? 一、引用计数法 当一个对象被其他变量引用时&#xff0c;使其计数1&#xff08;若被引用两次&#xff0c;计数为2&#xff09;&#xff0c;若某个变量不在引用它时&#xff0c;使其计数-1&#xff1b;当这个对象引用计数变为0时意味着不…

吴恩达【神经网络和深度学习】Week1——深度学习概述

文章目录1、What is a neural network?2、Supervised Learning with Neural Networks2.1、Examples2.2、The classification of data3、Why is Deep Learning taking off&#xff1f;4、Quiz课程笔记整理按照所讲章节的标题来完成1、What is a neural network? 以房价预测模型…

基于HTML5 技术的开放自动化HMI

人机交互接口&#xff08;HMI&#xff09;是自动化系统中不可或缺的一部分。传统的做法是提供一个HMI 显示屏&#xff0c;并且通过组态软件来配置显示屏的功能&#xff0c;通过modbus 或者以太网与PLC 连接。 现在&#xff0c;事情变得复杂了许多&#xff0c;用户不仅需要通过专…

干货 | 关于PCB中的“平衡铜”,一文全部说明白

平衡铜是PCB设计的一个重要环节&#xff0c;对PCB上闲置的空间用铜箔进行填充&#xff0c;一般将其设置为地平面。 平衡铜的意义在于&#xff1a; 对信号来说&#xff0c;提供更好的返回路径&#xff0c;提高抗干扰能力&#xff1b;对电源来说&#xff0c;降低阻抗&#xff0c;…

Android 13 源码获取与构建

文章目录1. 环境准备1.1 基本信息1.2 系统初始化1.2.1 更新 Ubuntu 软件包1.2.2 安装 git 工具1.2.3 安装依赖包(Ubuntu 18.04)1.2.4 修改默认python版本1.2.5 安装 repo 工具2. 源码下载完成2.1 创建源码目录2.2 初始化源码仓库2.3 开始下载源码2.4 Android 13 源码目录3. 构建…

m可见光通信的空间调制(sm)误码率matlab仿真

目录 1.算法描述 2.仿真效果预览 3.MATLAB核心程序 4.完整MATLAB 1.算法描述 可见光通信技术&#xff08;Visible Light Communication&#xff0c;VLC&#xff09;是指利用可见光波段的光作为信息载体&#xff0c;在空气中直接传输光信号的通信方式。可见光通信技术绿色低…

virtio vring原理

vring原理 在 virtio 设备上进行批量数据传输的机制被称为 virtqueue 。每个设备可以拥有零个或多个 virtqueue &#xff0c;当 Driver 想要向设备发送数据时&#xff0c;它会填充 Descriptor Table 中的一项&#xff08;或将几项链接在一起&#xff09;&#xff0c;并将描述符…