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

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


目录

  • 一. 基本思想
  • 二. 快速排序
    • 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分,是不是也不容易呢?


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

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

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

相关文章

弥合认知差距,扫除IT服务交付中的“拦路虎”

文章目录 编辑推荐内容简介作者简介作者简介&#xff1a;译者简介&#xff1a; 精彩书评目录 按需交付服务从来都不容易。成功的交付是以一种符合客户预期的一致性、可靠性、安全性、隐私性和成本效益的方式交付客户所需的服务。无论服务提供商提供的是 IT 服务&#xff0c;还是…

【C语言】——指针四:字符指针与函数指针变量

【C语言】——指针四&#xff1a;字符指针与函数指针变量 一、字符指针二、函数指针变量2.1、 函数指针变量的创建2.2、两段有趣的代码 三、typedef关键字3.1、typedef的使用3.2、typedef与define比较 四、函数指针数组 一、字符指针 在前面的学习中&#xff0c;我们知道有一种…

力扣|两数相加|链表

给你两个 非空 的链表&#xff0c;表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的&#xff0c;并且每个节点只能存储 一位 数字。 请你将两个数相加&#xff0c;并以相同形式返回一个表示和的链表。 你可以假设除了数字 0 之外&#xff0c;这两个数都不会以 0 …

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

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

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

大家好&#xff0c;我是苏貝&#xff0c;本篇博客带大家了解归并排序&#xff0c;如果你觉得我写的还不错的话&#xff0c;可以给我一个赞&#x1f44d;吗&#xff0c;感谢❤️ 目录 一. 基本思想二. 归并排序代码 一. 基本思想 在讲归并排序之前&#xff0c;问问自己&#x…

Nexus3 Docker 私有仓库

Nexus3 Docker 私有仓库 安装并部署 Nexus3 $ docker search nexus3$ docker pull sonatype/nexus3$ mkdir /home/tester/data/docker/nexus3/sonatype-work $ sudo chown -R 200 /home/tester/data/docker/nexus3/sonatype-work$ docker run -d --namenexus3 \ --restartalw…

【prometheus-operator】k8s监控redis

1、准备exporter https://github.com/oliver006/redis_exporter oliver006-redis_exporter-amd64.tar # 安装镜像 docker load -i oliver006-redis_exporter-amd64.tar # 上传镜像 docker tag oliver006/redis_exporter ip/monitor/redis_exporter:latest docker push ip/mo…

算法---前缀和练习-2(和为k的子数组)

和为k的子数组 1. 题目解析2. 讲解算法原理3. 编写代码 1. 题目解析 题目地址&#xff1a;点这里 2. 讲解算法原理 创建一个无序映射&#xff08;哈希表&#xff09; hash&#xff0c;用于统计前缀和的出现次数。初始时&#xff0c;将前缀和为 0 的次数设为 1&#xff0c;表示…

抖音IP属地怎么更改

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

[Spring Cloud] 简单搭建与请求转发

文章目录 简介相关链接搭建选择Spring Boot版本选择组件版本创建nacos配置创建网关gateway配置文件image.png创建微服务Spring Boot配置文件 gateway项目配置gateway项目文件pom.xmlApplication.javabootstrap.ymllogback-spring.xml 启动gateway 微服务配置微服务项目文件pom.…

使用pandas进行数据清洗

采集到原始的数据中会存在一些噪点数据&#xff0c;噪点数据是对分析无意义或者对分析起到偏执作用的数据。如何清洗&#xff1a; 清洗空值/缺失值清洗重复值清洗异常值 import pandas as pd from pandas import DataFrame,Series import numpy as np pandas处理空值操作 i…

ppp实验

拓扑图 实验步骤 配置IP地址及创建mp逻辑口 [R1]int ser 3/0/0 [R1-Serial3/0/0]ip add 192.168.1.1 24 [R1-Serial3/0/0] [R2]int se3/0/0 [R2-Serial3/0/0]ip add 192.168.1.2 24 [R2-Serial3/0/0]int mp [R2-Serial3/0/0]int mp-g [R2-Serial3/0/0]int mp-group 0…

中国气象局发布大地磁暴预警:空间站轨道或受影响

什么是地磁暴&#xff1f; 地磁暴作为最典型的太阳爆发活动&#xff0c;一次地磁暴是一次日冕物质抛射过程&#xff0c;能将数以亿吨计的太阳物质以数百千米每秒的高速抛离太阳表面。 不光是巨大质量与速度汇聚成的动能&#xff0c;它们还携带着太阳强大的磁场能&#xff0c;一…

前端基础篇-前端工程化 Vue 项目开发流程(环境准备、Element 组件库、Vue 路由、项目打包部署)

&#x1f525;博客主页&#xff1a; 【小扳_-CSDN博客】 ❤感谢大家点赞&#x1f44d;收藏⭐评论✍ 文章目录 1.0 环境准备 1.1 安装 NodeJs 1.2 验证 NodeJs 环境变量 1.3 配置 npm 的全局安装路径 1.4 切换 npm 的淘宝镜像( npm 使用国内淘宝镜像的方法(最新) ) 1.5 查看镜像…

406. 根据身高重建队列(力扣LeetCode)

文章目录 406. 根据身高重建队列题目描述贪心算法代码 406. 根据身高重建队列 题目描述 假设有打乱顺序的一群人站成一个队列&#xff0c;数组 people 表示队列中一些人的属性&#xff08;不一定按顺序&#xff09;。每个 people[i] [hi, ki] 表示第 i 个人的身高为 hi &…

tabs自定义样式

使用el-tabs 去修改样式的话比较麻烦&#xff0c;索性直接用div来制作。 <div class"contain"><div class"tab_wrap"><div :class"[skew, first, active 1 ? isActive: ]" click"tabClick(1)"><span class&quo…

HTTP --- 下

目录 1. HTTP请求方式 1.1. HTML 表单 1.2. GET && POST方法 1.2.1. 用 GET 方法提交表单数据 1.2.2. 用 POST 方法提交表单数据 1.2.3. 总结 1.3. 其他方法 2. HTTP的状态码 2.1. 重定向 2.1.1. 临时重定向 && 永久重定向 2.1.2. 302 &&…

3 Spring之DI详解

5&#xff0c;DI相关内容 前面我们已经完成了bean相关操作的讲解&#xff0c;接下来就进入第二个大的模块DI依赖注入&#xff0c;首先来介绍下Spring中有哪些注入方式? 我们先来思考 向一个类中传递数据的方式有几种? 普通方法(set方法)构造方法 依赖注入描述了在容器中建…

19.严丝合缝的文明——模板方法模式详解

“项目评审的节点又快到了&#xff0c;PPT你写了没&#xff1f;” “Oops&#xff0c;忘了&#xff0c;有模板没&#xff1f;给我一份” 概述 模板&#xff0c;一个频繁出现在办公室各类角色口中的词&#xff0c;它通常意味着统一、高效、经验和优质。各项汇报因为PPT的模板变…

trinus 3d打印机安装调试到成功打印3-没有热床模型脱落底床不粘模型翘边错位

由于没有自带热肠&#xff0c;改装的话需要额外购买配套的热床。但是如果手头没有的话&#xff0c;那只能使用原厂赠送的两张美文纸。美纹纸很容易用破&#xff0c;尤其是喷头可能会划破。另外拆模型的时候会引起气泡。 于是翘边和模型脱落就成了家常便饭。 这些问题的根源都在…