【数据结构】快速排序(用递归)

news2024/12/26 14:12:12

大家好,我是苏貝,本篇博客带大家了解快速排序,如果你觉得我写的还不错的话,可以给我一个赞👍吗,感谢❤️
在这里插入图片描述


目录

  • 一. 基本思想
  • 二. 快速排序
    • 2.1 hoare版本
    • 2.2 挖坑法
    • 2.3 前后指针法
    • 2.4 快速排序优化
      • 三数取中法取key(hoare版本)
      • 小区间优化

一. 基本思想

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


二. 快速排序

2.1 hoare版本

在这里插入图片描述

思路:

1.让key==数组在特定区间的第一个元素的下标
2.定义变量left和right,它们分别初始化为数组在特定区间的首元素和最后一个元素的下标,right先向前走,找到比key小的位置后停下;left再走,找到比key大的位置后停下;交换下标为right和left数组元素的位置。这样大的值就被放在后面,小的值就被放在前面
3.等到left和right相遇时,将相遇位置a[left]和下标为key的元素a[key]交换位置(能保证a[left]<a[key],具体原因下面会讲)
4.1-3步完成后,相遇位置即a[key]左边全是<=它的,右边都是>=它的
5.递归a[key]的左右子树,让它们经历1-3步

在这里插入图片描述

上面这幅图展示了将一个元素排序的步骤,后面还要排序6的左边和右边,我们可以将它当成一个二叉树来处理。第二次我们先来排6的左子树
在这里插入图片描述

第三次我们再来排3的左子树

在这里插入图片描述

2的左右子树分别为一个元素和空,所以不需要再递归下去。这两种情况用代码实现为:

if (begin >= end)
		return;

再递归3的右子树和6的右子树,思路一样,就不再赘述了

相遇位置的值一定小于下标为key的数组元素的原因:
在这里插入图片描述

int PartSort1(int* a, int begin, int end)
{
	int key = begin;
	int left = begin;
	int right = end;

	//right先走,left后走,right找小,left找大
	while (left < right)
	{
		while (left < right && a[right] >= a[key])
			right--;
		while (left < right && a[left] <= a[key])
			left++;
		Swap(&a[left], &a[right]);
	}
	Swap(&a[key], &a[left]);
	return left;
}


void QuickSort(int* a, int begin, int end)
{
	if (begin >= end)
		return;

	int key = PartSort1(a, begin, end);
	QuickSort(a, begin, key - 1);
	QuickSort(a, key + 1, end);
}

2.2 挖坑法

在这里插入图片描述

思路:

在这里插入图片描述

挖坑法实际上与hoare方法差不多,只是可能更好理解一些。接下来也是递归6的左右子树,不多说了

int PartSort2(int* a, int begin, int end)
{
	int left = begin;
	int right = end;
	int hole = begin;
	int key = a[hole];

	while (left < right)
	{
		while (left < right && a[right] >= key)
			right--;
		a[hole] = a[right];
		hole = right;

		while (left < right && a[left] <= key)
			left++;
		a[hole] = a[left];
		hole = left;
	}
	a[hole] = key;
	return hole;
}


void QuickSort(int* a, int begin, int end)
{
	if (begin >= end)
		return;

	int key = PartSort2(a, begin, end);
	QuickSort(a, begin, key - 1);
	QuickSort(a, key + 1, end);
}

2.3 前后指针法

在这里插入图片描述

思路:

1.定义3个变量key,prev和cur来表示数组的下标,prev和key置为第一个元素的下标,cur置为第二个元素的下标
2.当a[cur]>=a[key]时,cur++
3.当a[cur]<a[key]时,prev++,交换下标为prev和cur的元素,再cur++
4.如果cur>end(最后一个元素的下标),交换下标为prev和key的元素
5.递归a[key]的左右子树,让它们经历1-4步

在这里插入图片描述

接下来也是递归6的左右子树,不多说了

int PartSort3(int* a, int begin, int end)
{
	int key = begin;
	int prev = begin;
	int cur = begin + 1;

	while (cur <= end)
	{
		if(a[cur]<a[key])
		{
			prev++;
			Swap(&a[prev], &a[cur]);
		}
		cur++;
	}
	Swap(&a[prev], &a[key]);
	return prev;
}



void QuickSort(int* a, int begin, int end)
{
	if (begin >= end)
		return;

	int key = PartSort3(a, begin, end);
	QuickSort(a, begin, key - 1);
	QuickSort(a, key + 1, end);

}

2.4 快速排序优化

1

三数取中法取key(hoare版本)

当数组本来就是有序数组(如升序)时,hoare版本的排序后的key相当于只有右子树,没有左子树。这样在数组元素较多时(如10000个),在debug条件下可能会因为栈溢出而报错(在release条件下不会,因为release对递归建立栈帧的优化已经足够好了)

下面是测试排序性能的函数,将用希尔排序排序好的数组a1再用快速排序,发现会报错,原因:栈溢出

void TestOP()
{
	srand(time(0));
	const int N = 10000;
	int* a1 = (int*)malloc(sizeof(int) * N);

	for (int i = 0; i < N; ++i)
	{
		a1[i] = rand();
	}

	int begin1 = clock();
	ShellSort(a1, N);
	int end1 = clock();

	int begin2 = clock();
	QuickSort(a1, 0, N-1);
	int end2 = clock();

	printf("InsertSort:%d\n", end1 - begin1);
	printf("ShellSort:%d\n", end2 - begin2);

	free(a1);
}

在这里插入图片描述

那下面我们来优化hoare版本的快速排序。我们不再取数组第一个元素为key,而是取a[begin],a[end]和a[midi](midi是中间元素的下标)三者中的中位数。如何实现呢?先找到中位数的下标,再交换第一个元素和中位数的位置,再让key==begin,此时a[key]就不会是最小的元素了

int GetMidi(int* a, int begin, int end)
{
	int midi = (begin + end) / 2;
	if (a[begin] > a[midi])
	{
		if (a[midi] > a[end])
			return midi;
		else if (a[end] > a[begin])
			return begin;
		else
			return end;
	}
	else
	{
		if (a[end] > a[midi])
			return midi;
		else if (a[begin] > a[end])
			return begin;
		else
			return end;
	}
}


int PartSort1(int* a, int begin, int end)
{
	int midi = GetMidi(a, begin, end);
	Swap(&a[begin], &a[midi]);

	int key = begin;
	int left = begin;
	int right = end;

	//right先走,left后走,right找小,left找大
	while (left < right)
	{
		while (left < right && a[right] >= a[key])
			right--;
		while (left < right && a[left] <= a[key])
			left++;
		Swap(&a[left], &a[right]);
	}
	Swap(&a[key], &a[left]);
	return left;
}

2

小区间优化

假设每次的key的最终位置都是中间位置,那么为了将最后7个数排好序,总共要递归7次,这种代价还是有些大的,所以我们选择当要排列的数组的元素比较少时,采用直接插入排序
在这里插入图片描述
那当要排列的数组的元素小于多少时用直接插入排序呢?我们一般选择在树的倒数前3排开始,因为这3排(如果是满二叉树),那么会占据整个树的将近90%的元素。因为最后一层的元素个数为2^(h-1),总元素个数为2 ^h-1,所以将近占了一半的元素,倒数第二排是倒数第一排的一半,所以是25%……基于这种原因,我们最后选择当要排列的数组的元素小于10时用直接插入排序,当然,你也可以选择其他的值
在这里插入图片描述

代码如下,你能发现有什么问题吗?

void QuickSort1(int* a, int begin, int end)
{
	if (begin >= end)
		return;

	if (end - begin + 1 < 10)
		InsertSort(a, end - begin + 1);
	else
	{
		int key = PartSort1(a, begin, end);
		QuickSort1(a, begin, key - 1);
		QuickSort1(a, key + 1, end);
	}
}

直接插入排序本来是从begin的位置开始到往后的end - begin + 1个元素结束,但是上面的代码是从下标为0的位置开始,所以将它改正

void QuickSort1(int* a, int begin, int end)
{
	if (begin >= end)
		return;

	if (end - begin + 1 < 10)
		InsertSort(a + begin, end - begin + 1);
	else
	{
		int key = PartSort1(a, begin, end);
		QuickSort1(a, begin, key - 1);
		QuickSort1(a, key + 1, end);
	}
}

void QuickSort2(int* a, int begin, int end)
{
	if (begin >= end)
		return;

	int key = PartSort1(a, begin, end);
	QuickSort2(a, begin, key - 1);		
	QuickSort2(a, key + 1, end);
}

再使用测试排序性能的函数看看优化后与优化前的差别

void TestOP()
{
	srand(time(0));
	const int N = 10000;
	int* a1 = (int*)malloc(sizeof(int) * N);
	int* a2 = (int*)malloc(sizeof(int) * N);

	for (int i = 0; i < N; ++i)
	{
		a1[i] = rand();
		a2[i] = a1[i];
	}

	int begin1 = clock();
	QuickSort1(a1, 0, N - 1);
	int end1 = clock();

	int begin2 = clock();
	QuickSort2(a2, 0, N - 1);
	int end2 = clock();
	
	printf("QuickSort1(优化后):%d\n", end1 - begin1);
	printf("QuickSort2:%d\n", end2 - begin2);

	free(a1);
	free(a2);
}

在debug条件下的结果:

我们发现,优化后只是比没有优化快了2毫秒,好像并没有很优秀。这也是正常的,毕竟快速排序本身就是一个较好的排序,相当于你本身就靠了93分,再让你提高5分,是不是也不容易呢?


好了,那么本篇博客就到此结束了,如果你觉得本篇博客对你有些帮助,可以给个大大的赞👍吗,感谢看到这里,我们下篇博客见❤️

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

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

相关文章

数据结构:堆和二叉树遍历

堆的特征 1.堆是一个完全二叉树 2.堆分为大堆和小堆。大堆&#xff1a;左右节点都小于根节点 小堆&#xff1a;左右节点都大于根节点 堆的应用&#xff1a;堆排序&#xff0c;topk问题 堆排序 堆排序的思路&#xff1a; 1.升序排序&#xff0c;建小堆。堆顶就是这个堆最小…

设计模式-访问者(Visitor)模式详解和应用

文章目录 前言访问者模式介绍结构包含的角色应用场景代码示例访问者模式的扩展访问者模式优缺点总结 前言 最近在做一个根据数学表达式生成java执行代码的功能&#xff0c;其中用到了访问者模式。使我对访问者模式有了更深入的理解。故写下此篇文章分享出来&#xff0c;不足之…

ios逆向某易新闻 md5+aes

本期的案例比较简单&#xff0c;也许是ios逆向算法本来就比较简单的原因&#xff0c;所以前面我就多扯一些爬虫和逆向的东西。之前写的文章都是js逆向和android逆向的案例&#xff0c;这也是首篇ios的案例&#xff0c;所以会从入门开始讲起。 3大逆向对比 首先爬虫工程师大部…

Objective-C—Class底层结构探索,真心分享给你!!!

isa 走位图 在讲 OC->Class 底层类结构之前&#xff0c;先看下下面这张图&#xff1a; 通过isa走位图 得出的结论是&#xff1a; 1&#xff0c;类&#xff0c;父类&#xff0c;元类都包含了 isa, superclass 2&#xff0c;对象isa指向类对象&#xff0c;类对象的isa指向了元…

C语言操作符和数据类型的存储详解

CSDN成就一亿技术人 目录​​​​​​​ 一.操作符 一.算数操作符&#xff1a; 二.位移操作符&#xff1a; 三.位操作符&#xff1a; 四.赋值操作符&#xff1a; 五.单目操作符&#xff1a; 六.关系操作符&#xff1a; 七.逻辑操作符&#xff1a; 八.条件操作符&…

Java学习笔记 | JavaSE基础语法05 | 方法

文章目录 0.前言1. 方法概述2. 方法的定义和调用2.1 无参数方法定义和调用2.2 带参数方法定义和调用1 带参数方法定义和调用2 形参和实参3 带参数方法练习 2.3 带返回值方法的定义和调用1 带返回值方法定义和调用2 带返回值方法练习13 带返回值方法练习24 带返回值方法练习3 3.…

V R元宇宙平台的未来方向|V R主题馆加 盟|游戏体验馆

未来&#xff0c;VR元宇宙平台可能会呈现出以下发展趋势和可能性&#xff1a; 全面融合现实与虚拟世界&#xff1a; VR元宇宙平台将更加无缝地融合现实世界和虚拟世界&#xff0c;用户可以在虚拟环境中进行各种活动&#xff0c;与现实世界进行互动&#xff0c;并且体验到更加逼…

C程序编译、链接与项目构建

C程序编译、链接与项目构建 摘要C编译环境静、动态库介绍gcc与g和程序编译、链接Visual Studio创建和链接库动态库的显示调用 Make介绍安装使用 CMake介绍安装使用构建方式内部构建外部构建构建使用静/动态库常用[系统]变量常用指令CMake模块 Make与CMake的联系与区别 摘要 本…

代码随想录|Day26|贪心01|455.分发饼干、376.摆动序列、53.最大子数组和

455.分发饼干 大尺寸的饼干既可以满足胃口大的孩子也可以满足胃口小的孩子。 局部最优&#xff1a;尽量确保每块饼干被充分利用 全局最优&#xff1a;手上的饼干可以满足尽可能多的孩子 思路&#xff1a;大饼干 尽量分给 大胃口孩子 将小孩和饼干数组排序&#xff0c;我们从大到…

40+重量级DFLab合成模型含各种神丹底丹万能模型合集分享

之前玩DFL软件积累下来的资源&#xff0c;部分模型非常稀缺&#xff0c;之前买的都很贵&#xff0c;现在不玩了&#xff0c;分享给有缘人&#xff0c;懂货的自然懂。必须懂得怎么用再下载&#xff0c;否则对你没有任何价值。点击下载 所见即所得。其中包含几个重量级稀缺资源&…

新款理想L7一边增配一边减配,难怪大家都去买华为问界

文 | AUTO芯球 作者 | 雷歌 我真是要被理想汽车笑死了&#xff0c;真不愧是“定语榜单之王”。 几年前理想汽车搞了一个“中国市场新势力品牌销量周榜” 两个定语&#xff0c;将比亚迪&#xff0c;特斯拉排除在外&#xff0c;自己在自己打造的榜单里做了一年多的冠军宝座。…

试题E(求阶乘)

解题思路&#xff1a; 写不出来&#xff0c;看的题解。要想凑个10&#xff0c;就必须要有一个2和5&#xff0c;但是明显在一个阶乘里&#xff0c;因子为2的数量一定多余5的数量&#xff0c;所以计算5的数量。 解题代码&#xff1a; import java.util.Scanner; ​ public clas…

RabbitMQ 01

01.定义 02.功能

何为布控球?布控球的分类对比

主要的分类有&#xff1a; 根据内部的主控板卡的系统分类&#xff0c;典型的是基于海思芯片的嵌入式LINUX系统的&#xff0c;一般出国标GB28181&#xff0c;另外一种是剑走偏锋的安卓系统的&#xff0c;需要把球机的输出YUV转换为UVC接入安卓主板&#xff0c;作为外接USB摄像头…

Matplotlib数据可视化实战-2绘制折线图(1)

函数plot是matplotlib.pyplot模块中的一个重要函数&#xff0c;用于在图形中绘制折线图。其基本用法是plot(x, y, [fmt], **kwargs)&#xff0c;其中x和y分别代表X轴和Y轴上的数据点&#xff0c;通常是以列表、数组或一维序列的形式给出。通常用的参数有&#xff1a; 基本参数…

大学教材《C语言程序设计》(浙大版)课后习题解析 | 第一、二章

概述 本文主要提供《C语言程序设计》(浙大版) 第一、二章课后习题解析&#xff0c;以方便同学们完成题目后作为参考对照。后续将写出三、四章节课后习题解析&#xff0c;如想了解更多&#xff0c;请持续关注该专栏。 专栏直达链接&#xff1a;《C语言程序设计》(浙大版)_孟俊宇…

《深入浅出LLM 》(二):大模型基石知识

&#x1f389;AI学习星球推荐&#xff1a; GoAI的学习社区 知识星球是一个致力于提供《机器学习 | 深度学习 | CV | NLP | 大模型 | 多模态 | AIGC 》各个最新AI方向综述、论文等成体系的学习资料&#xff0c;配有全面而有深度的专栏内容&#xff0c;包括不限于 前沿论文解读、…

抖音IP属地怎么更改

抖音是一个非常受欢迎的短视频平台&#xff0c;吸引了无数用户在上面分享自己的生活和才艺。然而&#xff0c;随着快手的火爆&#xff0c;一些用户开始担心自己的IP地址会被他人获取&#xff0c;引起个人隐私风险。那么&#xff0c;抖音用户又该如何更改到别的地方呢&#xff1…

ArcGIS添加天地图底图服务

目录 一、注册天地图官网、申请Key 二、ArcGis配置和使用 1、配置 2、使用 三、其他方法 一、注册天地图官网、申请Key 进入官网&#xff0c;并注册账号。 地址&#xff1a;国家地理信息公共服务平台 天地图 (tianditu.gov.cn) 点击地图API&#xff0c;申请Key。 注意&am…

python闭包详解(实例)

“闭包”这个词语相信大多数学过编程的同学并不陌生&#xff0c;但是有时候理解起来还是有一定难度。 先看定义&#xff1a;闭包是由函数和与其相关的引用环境组合而成的实体。比如参考资源中就有这样的的定义&#xff1a;在实现深约束时&#xff0c;需要创建一个能显式表示引…