快速排序(上)

news2025/1/13 10:00:01

快速排序

在这里插入图片描述

前言

快速排序算法是最流行的排序算法,且有充足的理由,因为在大多数情况下,快速排序都是最快的。所以学习快速排序算法十分有必要。当然,既然它这么好,也就不太容易理解。

正文

Hoare版快排

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

既然是二叉树结构,在这个重复过程中就少不了是递归:

递归到什么程度?递归到只有一个数据或者没有数据,就开始“归”。

在头文件中我们对快速排序的声明如下:

void QuickSort(int* arr, int left, int right);

说明了我们需要传的参数是待排序的数组,以及左和右两个下标控制要排序的区间。

快速排序算法的具体实现

首先,我们要对left和right控制的区间找基准值。因为找到了基准值后,我们就能控制下一次递归的区间。

怎么找基准值?我们上面说基准值要满足左侧数据都小于基准值,右侧数据都大于基准值,所以我们从这一点入手。

我们现在有一个数组,我们让基准值先为第一个数据,让left为第二个数据,right为最后一个数据。然后,让left从左到右找比基准值大的数据,让right从右往左找比基准值小的数据。

找到后,我们让left和right的值交换;交换完再重复刚才的过程。

然后我们发现left走到right的后面去了, 这时我们让keyi和right交换,最后keyi就来到了正确的位置。检查一下,基准值左侧都是比基准值要小的数据,基准值右侧都是比基准值要大的数据。

现在我们在代码中实现找基准值,我们写一个QuickSort的子方法,就叫做_QuickSort,参数还是一样的。

(注意,在写快排代码的过程中,等于的情况会比较难处理,需要单独讨论,我们可以先跳过,到后面再来仔细讨论)

我们可以先写出这样的代码,left比right小进入循环,right从右往左找比基准值小的,left从左往右找比基准值大的,找到了就交换,这样逻辑顺下来写的代码似乎没有问题,实则有一个错误

其实上面代码的运行逻辑是这样的:

在第一次循环后我们的left<right是满足的,所以再次进入循环,找到了新的left和right,但是此时的left已经比right大了,我们想要的是在此时将right与keyi交换,但是现在我们会执行left与right交换,然后终止循环。

所以我们不能直接交换,要在交换之前进行判断

(注意函数的返回值是int,我们要把找到的基准值下标返回)

int _QuickSort(int* arr, int left, int right)
{
	int keyi = left;
	++left;//left是从第二个数据开始的

	while (left < right)
	{
		while (arr[right] > arr[keyi])
		{
			right--;
		}
		//right找到了比基准值小或等于的数据,等于的情况怎么处理?
		while (arr[left] < arr[keyi])
		{
			left++;
		}
		//left找到了比基准值大或等于的数据,等于的情况怎么处理?
		if (left < right)//等于的情况怎么处理?
		{
			Swap(&arr[left], &arr[right]);
		}
	}
	//right与keyi交换
	Swap(&arr[keyi], &arr[right]);
	return right;
}

但是这是我们还没有仔细分析要不要取等的版本。

为了探讨等于情况,我们就需要更多的案例。

假如我们现在就必须要让right找到比keyi小的才停下来,也就是改为这样:

while (arr[right] >= arr[keyi])//这里改为了大于等于
{
	right--;
}

等于也还要往前走。

我们发现我们没有限制,所以right会一直走到越界,于是我们需要加上限制,让right顶多走到left前一个:

while (left<=right && arr[right] >= arr[keyi])//注意限制
{
	right--;
}

所以我们的left也不能一直往后走,要加上限制。

while (left < right)//我们先写为可以等于
{
	while (left<=right && arr[right] >= arr[keyi])//注意限制
	{
		right--;
	}
	
	while (left <= right && arr[left] <= arr[keyi])
	{
		left++;
	}
	
	if (left <= right)//我们先写为可以等于
	{
		Swap(&arr[left], &arr[right]);
	}
}

(注意在上述代码中我们让外层循环可以取等,让内层的if也可以取等,总之就是都让等,看看情况)

加上这个限制后,在我们举的这个例子中,right已经走到left前面去了,所以内层的第二个while循环我们是进不去了,退出循环,来到right与keyi交换的语句。

根据上面画的图,此时我们的keyi和right都在第一个6的位置。而我们找基准值的意义是要划分左子序列和右子序列。

一般情况,左子序列的区间为[left,keyi-1],右子序列为[keyi+1,right],但是此时我们基准值在第一个数位置,所以我们就没有左子序列了,剩下右边全是右子序列。

我们要再次递归划分子序列,而因为数据全是6,下一次划分的情况又是一样的,基准值又是第一个数。所以我们排完n个数据排n-1个数据,然后n-2个数据,所以我们要递归n次,而且每次都要排n-1 、n-2 、n-3个数据,所以时间复杂度就很差。而我们前面讲快排应该每次都以“二分”来排序所以才那么快。

那么,怎么解决这个问题或者说代码应该怎么优化呢?

	while (left <= right)//这里先写为可以等于
	{
		while (left<=right && arr[right] > arr[keyi])//改为>,也就是等于时无法进入循环往前走
		{
			right--;
		}
		
		while (left <= right && arr[left] < arr[keyi])//改为<
		{
			left++;
		}
		
		if (left <= right)//这里先写为可以等于
		{
			Swap(&arr[left++], &arr[right--]);//改为了要++和--
		}
	}

所以这是我们现在代码的执行情况,然后left此时<=right所以我们再次进入循环,接着我们重复这个过程。

如图。

可以看到我们这么做的原因就是为了尽可能让基准值来到中间的位置,从而划分小的子序列。

现在我们再看一个场景:相遇值大于keyi值

(这个场景解释了为什么外循环是while (left <= right)而不是while (left < right),也就是说为什么left与right相遇时还要再进入循环。

可以看到,如果我们写的是while (left < right),那么无法再进入循环,也就会执行

//right与keyi交换
Swap(&arr[keyi], &arr[right]);
return right;

那么我们就让9到了第一个位置,而这不是我们想要的。所以我们要有等号,也就是相遇时也要进入循环让left再向左走一次,让right再向右走一次,然后再退出循环让right与keyi交换,所以内层left与right比较的代码也要能取等。

所以,两个内循环的第二个条件不能取等是为了防止基准值找到第一个数然后最终代码效率低下;left与right比较要取等是因为要防止把更大的数换到第一个位置。

现在我们的子程序,也就是找基准值的程序写完了。

我们还可以发现,基准值待的位置就是它该待的地方,所以下一次找基准值它的位置不用发生改变。

所以也可以这么说,找基准值的目的就是把基准值放到它该待的位置。

然后根据递归的思路,我们就可以写出快排的代码:

//快速排序
void QuickSort(int* arr, int left, int right)
{
	if (left >= right)//这种情况没有子序列,不要递归
		return;

	//找基准值
	int keyi = _QuickSort(arr, left, right);

	QuickSort(arr, left, keyi - 1);
	QuickSort(arr, keyi+1, right);

}

然后我们测试一下。

int main()
{
	int a[] = { 5, 3, 9, 6, 2, 4, 7, 1, 8 };
	int n = sizeof(a) / sizeof(int);

    printf("排序前: ");
	PrintArr(a, n);

	QuickSort(a, 0, n-1);

	printf("排序后: ");
	PrintArr(a, n);
    
	return 0;
}

测试代码:排序性能对比

在前面几种排序算法(前几篇博客)的探讨中我们都用到了10万个随机数的排序时间来检测排序速度,现在我们也再次使用这个方法(具体写法参考前面的文章)来检测一下快排的速度:

可以看到快排确实非常快。

现在我们改为100万个随机数据的排序,不看冒泡排序和直接插入了,来比较比较希尔排序、堆排序和快排的表现:

可以看到快排显著的优势。

快排的时间复杂度

我们知道快排是二叉树结构的交换排序方法,我们要递归logn次,空间复杂度为O(logn), 时间复杂度为O(nlogn)。可以看到空间复杂度和时间复杂度都很低。

其实快排还有其他版本,放到下篇文章再说=_=

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

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

相关文章

专业知识 | 操作系统与网络 | 3. Linux 环境基础开发工具使用

知足知不足&#xff0c;有为有不为&#xff01;——《心安即是归处》 目录 专业知识 | 操作系统与网络 | 3. Linux 环境基础开发工具使用 专业知识 | 操作系统与网络 | 3. Linux 环境基础开发工具使用

SpringDataJPA(三):多表操作,复杂查询

一、Specifications动态查询 有时我们在查询某个实体的时候&#xff0c;给定的条件是不固定的&#xff0c;这时就需要动态构建相应的查询语句&#xff0c;在Spring Data JPA中可以通过JpaSpecificationExecutor接口查询。相比JPQL,其优势是类型安全,更加的面向对象。 import …

Spring Boot 整合 Dubbo3 + Nacos 2.4.0

准备工作&#xff1a;Nacos 一、前置工作 安装Nacos&#xff0c;参考&#xff1a;Nacos 快速开始 此次安装 Nacos 最新版本&#xff1a;2.4.0 单机版 安装教程&#xff1a;Linux 安装 nacos 2.4.0-CSDN博客 二、创建配置文件 创建命名空间 新增配置文件 dubbo:application:i…

关于uniapp的vue2.x版本的路由守卫拦截方案

使用uni-read-pages和uni-simple-router实现&#xff0c;方案思路如下 首先在packge.json把下面两个依赖npm install 一下&#xff0c;我目前是这两个版本&#xff0c;别的版本号没有测试过 "dependencies": {"uni-read-pages": "^1.0.5",&quo…

matplotLib在图中标出最后一个点的值

import matplotlib.pyplot as plt import numpy as np# 生成100个随机数据 data np.random.rand(100)# 绘制数据 plt.plot(data, labelData Points)# 获取最后一个数据点的位置和值 last_x len(data) - 1 last_y data[-1]# 用红圈标出最后一个点 plt.plot(last_x, last_y, r…

《动手做科研》09. 万事具备,只欠行动

地址链接:《动手做科研》09. 万事具备&#xff0c;只欠行动 欢迎加入我的知识星球&#xff0c;定期分享AI论文干货知识&#xff01; 导读: 当你坚持学习到这一步&#xff0c;并且之前的内容都有跟着操作&#xff0c;那么恭喜你&#xff0c;你已经在脑力层面消化了最难的知识&am…

AI表情神同步!LivePortrait安装配置,一键包,使用教程

快手在AI视频这领域还真有点东西&#xff0c;视频生成工具“可灵”让大家玩得不亦乐乎。 现在又开源了一款超好玩的表情同步&#xff08;表情控制&#xff09;项目。 一看这图片&#xff0c;就知道是小视频平台出的&#xff0c;充满了娱乐性。发布没几天就已经有8000Star。 项…

7月Langchain-Chatchat 0.3.1最新 win系统-安装教程,踩坑2小时,5分钟拿去!

Win11安装 langchain-chatchat 0.3.1最新版 1. 虚拟环境安装和python包安装 conda create -n chat310 python3.10#这里很重要 需要先安装cuda版本的torch pip install torch2.3.1 torchvision0.18.1 torchaudio2.3.1 --index-url https://download.pytorch.org/whl/cu121pip…

六种方法实现Python文件之间的互动!

一、exec() 首先&#xff0c;我们生成一个名为**“file1.py”的python文件**&#xff1a; # file1.py print("Hello from file1!")然后&#xff0c;再生成一个名为“main.py”的python文件&#xff1a; # main.py filename file1.py with open(filename) a…

vlunstack-1(横向,phpmyadmin拿shell,xycms拿shell,cs和msf联动)

Vulnstack-1 内网环境搭建 环境配置&#xff1a; 靶机介绍 包括win7的web端&#xff0c;还有win2003为域成员&#xff0c;域控为win2008 win7内网ip&#xff1a;192.168.157.153 外网ip&#xff1a;192.168.52.143 域成员 win2003 ip 192.168.52.141 域控 win2008 ip 192.168…

基于WEB的仓库管理系统的设计与实现

点击下载源码 基于WEB的仓库管理系统的设计与实现 摘 要 仓库物品的管理是与我们的日常生活息息相关的一个重大问题。随着我国经济飞速的发展&#xff0c;改革开放的不断深入&#xff0c;企业要想在激烈的市场竞争中立于不败之地&#xff0c;要想继续的发展与生存&#xff0…

IDEA报错无效的目标发行版:17

问题描述&#xff1a;由于要接手另外一个项目&#xff0c;之前项目用的jdk17&#xff0c;新项目用的jdk8。需要切换jdk&#xff0c;idea切换之后启动报错 检查之后发现是没切换完全&#xff0c;一共需要切换如下几个地方&#xff1a; 切换前提&#xff1a;电脑上安装了多个版本…

八戒会修特斯拉 气囊电脑 rcm故障代码RCM2_a442_presFrntRDoorOpen

--------------------------------------------------------------------------------------------------------------------------------- -------------------------------------- 作者&#xff1a; 八戒会修特斯拉 -------------------------…

手机在网状态接口如何对接?(一)

一、什么是手机在网状态&#xff1f; 传入手机号码&#xff0c;查询该手机号的在网状态&#xff0c;返回内容有正常使用、停机、在网但不可用、不在网&#xff08;销号/未启用/异常&#xff09;、预销户等多种状态。 二、手机在网状态使用场景&#xff1f; 1.信贷审核&#…

MySQL是怎样运行的——第2章 启动选项和系统变量

文章目录 2.1 在命令行上使用选项2.1.1 选项的长形式和短形式 2.2 配置文件中使用选项2.2.1 配置文件的路径2.2.2 配置文件的内容2.2.3 配置文件的优先级 2.3 命令行和配置文件中启动选项的区别2.4 系统变量2.4.1 简介2.4.2 查看系统变量2.4.3 设置系统变量2.4.4 启动选项和系统…

Java原生序列化与反序列化、URLDNS

配套课件地址&#xff1a;https://blog.csdn.net/mocas_wang/article/details/10762101 1. 概述 1.1 序列化与反序列化 序列化是指把Java代码转化为字节序列的过程&#xff1b;而反序列化时指把字节序列恢复为Java对象的过程。序列化分为两大部分&#xff1a;序列化和反序列化…

mindspore框架实现ckpt模型导出ONNX格式

mindspore框架保存及加载模型 详细流程&#xff1a;昇思-保存及加载模型 关键步骤 关键代码 from mindspore import export, load_checkpoint, load_param_into_net from mindspore import Tensor import numpy as np from MobileNet2GarbageCls.MobileNetv2 import *# 有…

第二证券:商业航天概念再活跃,航天晨光5连板,航新科技等涨停

商业航天概念1日盘中再度活跃&#xff0c;到发稿&#xff0c;航新科技、春晖智控“20cm”涨停&#xff0c;航天雄图涨超10%&#xff0c;航天长峰、航天晨光、星网宇达、航天科技、航天展开等均涨停&#xff0c;航宇微涨近10%。 值得注意的是&#xff0c;航天晨光已接连5个交易…

如何对同一个项目,不同分支,开两个IDEA窗口?

问题&#xff1a;有次我想参考&#xff08;fu zhi&#xff09;某个分支的代码&#xff0c;来写代码&#xff0c;但是打开双击项目的pom文件&#xff0c;会自动打开现在的IDEA窗口&#xff0c;如下&#xff1a; 解决&#xff1a;后面我用Open的方式打开&#xff0c;也是一样的。…

免费【2024】springboot 大棚蔬菜管理系统的设计与实现

博主介绍&#xff1a;✌CSDN新星计划导师、Java领域优质创作者、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和学生毕业项目实战,高校老师/讲师/同行前辈交流✌ 技术范围&#xff1a;SpringBoot、Vue、SSM、HTML、Jsp、PHP、Nodejs、Python、爬虫、数据可视化…