快速排序(一)

news2024/11/27 6:15:39

目录

快速排序(hoare版本)

初级实现

问题改进 

中级实现

时空复杂度 

高级实现

三数取中 


快速排序(hoare版本)

历史背景:快速排序是Hoare于1962年提出的一种基于二叉树思想的交换排序方法
基本思想:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列

初级实现

实现步骤: 1、确定排序开始时key (每轮排序作为基准元素的元素下标) 、left (负责寻找大于基准元素的元素下标) 、right (负责寻找小于基准元素的元素下标) 三者的初始值:
int left = begin;  //数组首元素下标
int right = end;   //数组尾元素下标
int keyi = begin;  //即可以是首元素下标、也可以是尾元素下标,一般来说是首元素下标

2、right和left开始自己的寻找任务,当a[right] > a[keyi],right就--继续向左走,当a[left] < a[keyi],left就++继续向右走,当二者都在找的过程中在某一位置停下时(两个while循环均结束),交换此时的a[left]和a[right]:

void QuickSort(int* a, int begin, int end)
{

	int left = begin, right = end;
	int keyi = begin;

		// 右边找小
		while (a[right] > a[keyi])
		{
			--right;
		}

		// 左边找大
		while (a[left] < a[keyi])
		{
			++left;
		}

		Swap(&a[left], &a[right]);

}

3、当left与right相遇即left == right时,此时元素的值一定小于基准元素的值,所以交换当前元素与基准元素的位置(因为我们要做的就是将小于基准元素的数放在左边,大于的放在右边)

void QuickSort(int* a, int begin, int end)
{

	int left = begin, right = end;
	int keyi = begin;

	while (left < right)
	{
		// 右边找小
		while (a[right] > a[keyi])
		{
			--right;
		}

		// 左边找大
		while (a[left] < a[keyi])
		{
			++left;
		}

		Swap(&a[left], &a[right]);
	}

	Swap(&a[left], &a[keyi]);
}

关于“为什么相遇位置一定会比基准元素小”的解释:

        因为我们规定右边先走(当然你也可以让左基准元素是数组尾元素然后左边先走,这里就不再分析了),这样就会有两种相遇的情况:

①right遇到left,right没找到比基准元素小的,一直走,找到时停下,然后left向右走,当二者相遇即right ==left时停下(原因我们后面会将),所处位置的元素的值小于基准元素:

②left遇到right,right先走,找到小于基准元素的位置停下,left开始找比基准元素大的,没有找到,一直走,遇到right停下,相遇位置是right,前面说过此时的位置应该是小于基准元素的位置(“right先走,找到小于基准元素的位置停下”)

至此,我们快速排序的初级实现已经完成了,接下来就是处理我们遗留的一些问题了: 

问题改进 

1、产生原因:有时我们写的代码只适用于部分数据,但是换成其它数据时就会出错,为了保证我们代码的通用性,我们要进行多次的用例测试,比如我们将数组换为{6,1,2,5,4,6,9,7,10,8}:

        

        可以发现,之前的代码并不能让right和left相遇,又因为我们规定right先走,所以我们为了能让二者相遇,需要保证left永远不会超过right,故在a[right] > a[keyi]之前加上left < right,即left < right && a[right] >= a[keyi],left也是一样的道理:

void QuickSort(int* a, int begin, int end)
{

	int left = begin, right = end;
	int keyi = begin;

	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]);
}

2、产生原因:当我们将基准元素换到数组的中的某个位置时,它左侧的元素经过一系列检查与交换的操作后已经全部是小于基准元素的元素,右边的元素也已经全部是大于基准元素的元素,现在我们要做的就是将左右两边的元素均变为有序,当左右两边均有序时该数组就完全有序(原因自己想去😡)这就需要用到递归思想了(在前面我们说过hoare版本的快排是基于二叉树思想的),当我们尝试对上面的数组开始递归操作时,如果还是原来的代码就会出现下图所示的问题:

       

         可以发现, 原本我们是想通过右递归将右侧大于基准元素的几个元素变为有序,但可以看到的是只有当a[right] == 4时才会停下,此时就已经在左递归的范围内了,因此为了保证不会越界,我们还需要为a[right] > a[keyi]加上一个"=",即a[right] >= a[keyi],左递归也是一样的道理:

void QuickSort(int* a, int begin, int end)
{
	int left = begin, right = end;
	int keyi = begin;

	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]);
}

中级实现

至此,我们开始进行递归操作,关于递归的过程如下图所示:

关于递归的代码也不再过多解释,自行理解即可: 

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

	int left = begin, right = end;
	int keyi = begin;

	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]);
	keyi = left;

	// [begin, keyi-1] keyi [keyi+1, end]
	QuickSort(a, begin, keyi - 1);
	QuickSort(a, keyi+1, end);
}

以上就是一个“较为”完整的快速排序的代码 

时空复杂度 

最坏时间复杂度:O(N^2)(当数组已经降序有序或升序有序时,此时基准元素一直位于首元素或尾元素,n个元素要进行n次快速排序才能将当前的顺序改变,n*n)

最好时间复杂度:O(N*logN)每次划分都能将数组均匀地分成两个接近子数组,N个元素要进行logN次的排序,N*logN

空间复杂度:O(logN)或O(N)(在递归过程中需要使用栈来保存函数调用信息,所以快速排序的空间复杂度取决于递归调用的层数。在最坏情况下,递归调用栈可能达到O(n)的空间复杂度,最好的空间复杂度为O(logn))

高级实现

        当面对有序队列时,快速排序的效率确实会降低。这是因为快速排序的分区操作通常选择一个基准元素,并将小于等于基准的元素放在左侧,大于基准的元素放在右侧。如果输入数据已经有序,那么每次分区后只能将一个元素移到正确位置上,而剩余部分仍然需要进行递归调用。为了应对这种情况,可以采取以下方法来提高快速排序在有序队列上的效率:

  1. 随机化选择基准:通过随机选择基准值可以降低出现最坏情况(即已经有序)的概率。这样可以增加快速排序处理无序数据时的性能。

  2. 三数取中法:使用三数取中法来选择合适的基准值。从待排序数组中选取头、尾和中间位置上的三个数,并将它们按照大小顺序排列。然后选取其中位数作为划分子数组(即作为枢纽),以避免最坏情况发生。

  3. 插入排序优化:当待排序子数组长度较小时(比如小于某个阈值),可以切换到插入排序算法进行处理。插入算法对局部有序数据表现良好,在长度较短的子数组上可以提高排序效率。

  4. 优化递归调用:通过限制递归深度或者使用尾递归优化等方法,减少对有序数据的不必要处理。

        这些方法可以在特定情况下提高快速排序算法在有序队列上的性能,但需要根据具体场景选择合适的策略。

三数取中 

注意事项:获取的是下标为begin、midi、end的三个元素中的中位数(非最多,非最小)

完整代码如下:

int GetMidi(int* a, int begin, int end)
{
	int midi = (begin + end) / 2;
	// begin midi end 三个数选中位数
	if (a[begin] < a[midi])
	{
		if (a[midi] < a[end])
			return midi;   //返回a[midi] < a[midi] < a[end]
                
		else if (a[begin] > a[end])
			return begin;  //返回a[end] < a[begin] < a[midi]

		else
			return end;    //返回a[begin] < a[end] < a[midi]
	}
	else // a[begin] > a[midi]
	{
		if (a[midi] > a[end])
			return midi;   //返回a[end] < a[mid] < a[begin]
		else if (a[begin] < a[end])
			return begin;  //返回a[midi] < a[begin] < a[end]
		else 
			return end;    //返回a[midi] < a[end] < a[begin]
	}
}

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

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

	int left = begin, right = end;
	int keyi = begin;

	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]);
	keyi = left;

	// [begin, keyi-1] keyi [keyi+1, end]
	QuickSort(a, begin, keyi - 1);
	QuickSort(a, keyi+1, end);
}

~over~

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

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

相关文章

强制性产品认证车辆一致性证书二维码解析

目录 说明 界面 下载 强制性产品认证车辆一致性证书二维码解析 说明 二维码扫描出的信息为&#xff1a; qW0qS6aFjU50pMOqis0WupBnM21DnMxy0dGFN/2Mc9gENXhKh0qEBxFgfXSLoR qW0qS6aFjU50pMOqis0WupBnM21DnMxy0dGFN/2Mc9gENXhKh0qEBxFgfXSLoR 解析后的信息为&#xff1a…

20来岁,大专毕业,学软件测试可行吗?

转行软件测试找不到工作&#xff01; 转行软件测试找不到工作&#xff01; 转行软件测试找不到工作&#xff01; 重要的事情说三遍&#xff01;千万别听培训班咨询老师给你画饼 &#xff1b;我就是某某软件测试培训班出来的&#xff0c;大专&#xff0c;其他专业毕业&#x…

初级数据结构(六)——堆

文中代码源文件已上传&#xff1a;数据结构源码 <-上一篇 初级数据结构&#xff08;五&#xff09;——树和二叉树的概念 | NULL 下一篇-> 1、堆的特性 1.1、定义 堆结构属于完全二叉树的范畴&#xff0c;除了满足完全二叉树的限制之外&#xff0c;还满…

小程序商城活动页面怎么生成二维码

背景 小程序商城某些页面需要做成活动推广页&#xff0c;或需要某一个页面做成二维码进行推广。比如某些非公开的商品做成一个活动&#xff0c;发送指定部分用户&#xff0c;这个活动页面可以做成二维码。 前提 小程序已经上线 步骤 登录微信小程序官网&#xff0c;选择工具…

数据库交付运维高级工程师-腾讯云TDSQL

数据库交付运维高级工程师-腾讯云TDSQL上机指导&#xff0c;付费指导&#xff0c;暂定99

Element 介绍

Element 介绍 Vue 快速入门 Vue 常见组件 表格 分页组件 其他自己去看吧 链接: 其他组件

【idea】解决sprintboot项目创建遇到的问题

目录 一、报错Plugin ‘org.springframework.boot:spring-boot-maven-plugin:‘ not found 二、报错java: 错误: 无效的源发行版&#xff1a;17 三、java: 无法访问org.springframework.web.bind.annotation.CrossOrigin 四、整合mybatis的时候&#xff0c;报java.lang.Ill…

文件函数的简单介绍

1. 向文件中写入一个字符 fputc int_Ch指的是输入文件中的字符 &#xff08;int&#xff09;的原因是以ascll码值的型式输入 #include <stdio.h> #include <errno.h> #include <string.h> int main() { FILE* pf fopen("test.txt","…

再怎么“顽固”的应用程序,也很难经得住以下的卸载方法

卸载程序是我们经常尝试的事情。这可能是因为我们不再需要程序,该程序可能会导致问题等。有时,如果你试图卸载某个程序,你会收到一个错误,但卸载没有发生。在这种情况下,你可以选择强制卸载。在本教程中,我将向你展示如何在Windows 10/11计算机上强制卸载程序。 ​控制面…

西南科技大学数字电子技术实验五(用计数器设计简单秒表)FPGA部分

一、实验目的 1.进一步理解用中规模集成计数器构成任意进制计数器的原理。 2.了解计数器的简单应用。 3.进一步学习与非门和译码显示器的使用方法。 4.学会用FPGA实现本实验内容。 二、实验原理 简单秒表 可暂停、复位秒表 三、程序清单(每条语句必须包括注释或在开发…

运维开发实践 - 服务网关 - apisix部署

1. Apache Apisix Apache Apisix 是一个动态&#xff0c;实时&#xff0c;高性能的云原生API网关&#xff0c;提供负载均衡&#xff0c;动态上游&#xff0c;灰度发布&#xff0c;服务熔断&#xff0c;身份认证&#xff0c;可观测性等丰富的流量管理功能&#xff1b; 2. 如…

0x17 二叉堆

0x17 二叉堆 二叉堆是一种支持插入、删除、查询最值的数据结构。它其实是一种满足“堆性质”的完全二叉树&#xff0c;树上的每一个节点带有一个权值。若树中的任意一个节点的权值都小于等于其父节点的权值&#xff0c;则称该二叉树满足“大根堆性质”&#xff0c;称其为“大根…

olap/clickhouse-编译器优化与向量化

本文主要结合15721和clickhouse源码来聊聊向量化&#xff0c;正好我最近也在用Eigen做算子加速&#xff0c;了解下还是有好处的。 提示编译器 提示编译器而不是复杂化简单的代码 什么时候使用汇编&#xff0c;什么时候使用SIMD&#xff1f;下面有几个基本原则&#xff1a; …

一种缩小数据之间差距的算法

先上代码&#xff1a; /** * 缩小数据之间的差距&#xff0c;但是大小关系不变的方法* param {Array} features */function minMaxData(data) {for (let i 0; i < data.length; i) {const f data[i];const x f[1];const yf[2];//此处5根据实际情况设置const y2 Math.pow(…

VLAN 详解一(VLAN 基本原理及 VLAN 划分原则)

VLAN 详解一&#xff08;VLAN 基本原理及 VLAN 划分原则&#xff09; 在早期的交换网络中&#xff0c;网络中只有 PC、终端和交换机&#xff0c;当某台主机发送一个广播帧或未知单播帧时&#xff0c;该数据帧会被泛洪&#xff0c;甚至传递到整个广播域。而广播域越大&#xff…

myBatis-plus自动填充插件

在 MyBatis-Plus 3.x 中&#xff0c;自动填充的插件方式发生了变化。现在推荐使用 MetaObjectHandler 接口的实现类来定义字段的填充逻辑。以下是使用 MyBatis-Plus 3.x 自动填充的基本步骤&#xff1a; 1.基本配置 1.1添加 Maven 依赖&#xff1a; 确保你的 Maven 依赖中使…

数据结构之<图>的介绍

图&#xff08;Graph&#xff09;的概念&#xff1a; 在数据结构中&#xff0c;图是由节点&#xff08;顶点&#xff09;和边组成的非线性数据结构。图用于表示不同对象之间的关系&#xff0c;其中节点表示对象&#xff0c;边表示对象之间的连接或关系。 1.图的基本组成元素&a…

Jenkins----基于 CentOS 或 Docker 安装部署Jenkins并完成基础配置

查看原文 文章目录 基于 CentOS7 系统部署 Jenkins 环境基于 Docker 安装部署 Jenkins环境配置 Jenkins 中文模式配置用户名密码形式的 Jenkins 凭据配置 ssh 私钥形式的 Jenkins 凭据配置 Jenkins 执行任务的节点 基于 CentOS7 系统部署 Jenkins 环境 &#xff08;1&#xff…

广东高院严惩“套路贷”犯罪,保护校园安全

近日&#xff0c;广东高院发布了一批依法严惩“套路贷”犯罪的典型案例&#xff0c;其中一起涉及在校学生的“套路贷”案件引起了广泛关注。 这起案件中&#xff0c;张某等人针对在校大学生开展无抵押高息短期借款“套路贷”业务&#xff0c;通过频繁威胁恐吓、借新还旧、转单…

【计算机视觉--解耦视频分割跟踪任何物体】

UIUC&Adobe开源|无需监督&#xff0c;使用解耦视频分割跟踪任何物体&#xff01;视频分割的训练数据往往昂贵且需要大量的标注工作。这限制了将端到端算法扩展到新的视频分割任务&#xff0c;特别是在大词汇量的情况下。为了在不为每个个别任务训练视频数据的情况下实现“跟…