八大算法排序@快速排序、递归版本一(C语言版本)

目录

  • 快速排序版本一
    • 概念
    • 算法思想
    • 快排步骤
    • 代码实现
    • 时间复杂度
    • 空间复杂度
    • 特性总结

快速排序版本一

概念

  快速排序(Quicksort)是一种高效的排序算法,它是由英国计算机科学家 Tony Hoare 在1960年提出的。快速排序是基于分治(Divide and Conquer)策略的算法,其基本思想是通过选择一个基准元素,将数组划分为两个子数组,使得左侧子数组的元素都小于基准元素,右侧子数组的元素都大于基准元素,然后对子数组进行递归排序。

快速排序的核心算法有多种实现方法,该文章介绍的版本是。借助双指针,递归的方式实现排序功能。




算法思想


注:为了方便理解,下文所说的指针,并不是概念上的指针,其实就是一个变量,只是为了更加形象的表达、讲解,所以用了指针这一形象的工具。

  原数组使用变量left 、 right 记录数组第一个和最后一个元素的下标。借助快排核心算法:
  在数组中选取一个元素,作为基准值。使用 “双指针” 分别从数组的左边和右边开始往中间挪动,要求
左边的 “指针” begin 从左到右寻找到比基准值大的数则停止,右边的 “指针” end 从右到左寻找到比基准值小的数则停止。 接着左右指针指向的数据互换。交换完成后,左边 ”指针” 继续往右边寻找,右边 “指针” 继续往左边寻找。
重复以上的动作,直到 begin“指针” 和 end “指针”相遇,交换begin 和 end所指的元素(自身交换),然后结束退出迭代过程。最后将一开始选定的基准值与左 “指针” begin指向的数据进行交换。
  最终的结果:最终与begin “指针” 所指的位置交换后的基准值,基准值左边的数都比基准值小,右边的数都比基准值大。假设数组排好升序的情况下,基准值所在的位置,便是升序下所在的位置。即根据以上的步骤,我们便完成数组中的一个元素(基准值)的排序。
  接着用变量div存储begin“指针”指向的下标,根据这个下标,将原数组一分为二,分别为[left , div-1] 、[div+1,right]。然后依次对这两个数组进行以上快排的核心算法,也就是递归的思想。通过每次递归一次完成一个“基准值”到达指定的位置。最终实现了整个数组的排序。

为了方便演示递归的流程,使用数组arr[] = { 6 , 2 , 3 , 9 , 4 } 进行升序过程的模拟实现。




原数组状态
一开始用变量left、right 存储待排序数组的第一个元素下标和最后一个元素的下标。div变量用来接收快排核心算法最终返回的begin存储的下标。
将数组的地址、待排序数组 left 和 right 传入核心算法函数,核心算法思路图解如下:

PartSort1

基准值一般选取待排序数组的最后一个元素的下标,如图选取元素4作为基准值
begin指向的下标0的元素6比4大,begin“指针”停止。
end指向的下标4的元素4没比基准值小,所以接着往左边找,直到end指向下标为2的元素3满足条件,end “指针” 停止。
接着begin 和 end 指向的两个元素交换。交换后重复迭代动作。
交换后的begin指针指向的元素从原来的6变成了3,不满足条件,所以接着往右边寻找,直到指向下标为2的元素6时才满足停止的条件,但是这时begin “指针” 和 end “指针” 相遇了,因此退出迭代,最终begin “指针” 指向的元素和基准值进行交换。
最终返回begin变量存储的下标值。


退出核心排序算法函数后,如下:

在这里插入图片描述
变量div接收核心排序算法函数返回的begin变量存储的下标值。这时,我们已经完成了对数组升序结果中,元素4的排序了。

接着将数组,以div所指的位置为分割点,划分为两个数组,分别为 [ left,div-1] 、[ div+1,right]。如下:

在这里插入图片描述

现在问题从,对数组arr[] = { 6 , 2 , 3 , 9 , 4 }的升序排序,转变为对数组 { 3 , 2 } 和 { 9 , 6 } 的升序排序了。但是对于这两个待排序的数组,排序的思路和上述的思路一致,借助 “双指针” 的方法,一步步递归下去,最终由一个个排好序的个体,返回到原数组时,形成有序的整体。




对于分割出来的数组的排序流程如下:

在这里插入图片描述

传入数组arr的地址,left 和 div-1给核心算法函数:

递归左数组

begin所在的元素比基准值2大,故停止不动;
end所在的元素没比基准值小,故向左移动,当指向下标0时,与begin “指针” 相遇,begin 和 end 所指的元素交换,而后结束、退出迭代过程。
最终begin指向的元素和基准值进行交换,然后返回begin存储的下标值。

退出核心算法函数,如下:

在这里插入图片描述
又实现了对于 [ left,div-1] 数组的排序。
注意:这里还需递归一次,但是可以通过边界判断直接返回。




同样的思路,对 [ div+1,right ] 所处的数组进行排序:

在这里插入图片描述

传入数组arr的地址,div+1 和 riht 下标范围,如下:

在这里插入图片描述

退出核心算法后:

在这里插入图片描述

后续,主要为返回递归,销毁调用的栈帧,回到一开始调用的函数。结束排序。

最后,实现了整个数组的升序排序效果。以上是对于“双指针版本” 的快速排序的大体流程讲解。
具体步骤如下。




快排步骤

基本步骤:

1、选择基准元素: 从数组中选择一个元素作为基准元素。选择基准元素的方法可以有多种,常见的包括选择第一个元素、最后一个元素或随机选择一个元素。

2、划分数组: 将数组中的其他元素按照与基准元素的大小关系分为两个子数组。比基准元素小的放在左侧,比基准元素大的放在右侧。相等的元素可以放在任一侧。

3、递归排序: 对左右两个子数组进行递归排序。递归的终止条件是子数组的大小为1或0,因为一个元素或没有元素的数组是已经有序的。

4、合并结果: 将排好序的左右子数组合并成最终的有序数组。




代码实现

void Swap(int* p1, int* p2)
{
	int tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}


// 三数取中法,排除 key 取到最大 和 最小的数值
// 如数组: 0 1 2 3 4 5 6 7 8 9 时  中间值为4,随后将 4 和 9 互换,再进行快排,即可将最坏的情况变为最优情况
int GetMidIndex(int* a, int begin, int end)
{
	int mid = (begin + end) / 2;

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


// 时间复杂度:O(N)
// [begin,end]    1、左右指针法
int PartSort1(int* a, int begin, int end)
{
	int midIndex = GetMidIndex(a, begin, end);
	Swap(&a[midIndex], &a[end]);  // 让最坏的情况不再会出现

	int keyindex = end;

	while (begin < end)
	{
		// 注意:此处 要给个‘=’,若不然当左右第一找到的数都是a[keyindex] 会一直死循环
		while (begin < end && a[begin] <= a[keyindex])
		{
			begin++;
		}

		while (begin < end && a[end] >= a[keyindex])
		{
			end--;
		}

		Swap(&a[begin], &a[end]);
	}

	Swap(&a[begin], &a[keyindex]);

	return begin;
}

/*
 极端情况下,快速排序最坏的情况下,
 每次选到的key都是最大的或者最小的  (有序或者接近有序)
  时间复杂度:O(N^2)
 最好情况下:
 时间复杂度:(类似于将N个数分散到二叉树中,高度为logN,每一层时间复杂度为 N )
			  所以时间复杂度为:O( N*logN )

 空间复杂度:O(logN)
*/
// 快速排序 单趟排序
void QuickSort(int* a, int left, int right)
{
	assert(a);
	if (left >= right)	return;

	if ((right - left + 1) > 10)
	{
		int div = PartSort1(a, left, right);

		//PrintArr(a + left, right - left + 1);
		//printf("[%d,%d]%d[%d,%d]\r\n", left, div - 1, div, div + 1, right);
			
		QuickSort(a, left, div - 1);
		QuickSort(a, div + 1, right);
	}
	else
	{
		// 小于等于10以内的数组,不再使用递归排序
		// 小区间使用插入排序去排,不再使用快速排序的递归排
		// 减少整体的递归次数
		InsertSort(a + left, right - left + 1);
	}
}




以上是关于快速排序,核心算法版本一“双指针”的演示流程和代码。可以结合这图形和代码自己演示一遍,更有利于理解。难点主要是递归的思想,需要注意是:
1、递归过程中核心思想是相同的,
2、注意递归返回的判定条件,防止出现栈溢出问题。



上述代码还有一点没讲到:
  三树取中法。主要是防止,每次的基准值取到的是数组中最大/最小的元素。如升序排序时,基准值每次取到的都是最大值的话,左 “指针” begin就得一直从左找到右,begin和end相遇了才退出;若基准值每次取到的都是最小值的话,那么右 “指针” 又得一直从右找到左,知道end和begin相遇才退出。这样就会大大的拉低程序的效率。
至此,我们发现如果原数组一开始便是有序的,比如 arr = { 2 , 3 , 4 , 6 , 9 },如果没有使用三树取中法的话,那么将出现最坏的情况,快排效率为O(N^2);
但是如果使用了三树取中法,取左右两边和中间的三个数中的既不是最大也不是最小的数,作为基准值。如{ 2 , 3 , 4 , 6 , 9 }这种情况,将 4 和 9 互换,4作为基准值,那么原先最坏的情况,将变成最好的情况,时间复杂度为O(N^logN)。


  除此之外,对于数组较小的排序,可以采用其他的排序方法,进行排序。因为快速排序得使用到递归,在性能上会造成栈帧上的开销。因此对于较小的数组排序时可以借助其他排序,上述代码使用的是插入排序。




时间复杂度

O( N*logN )

计算如下:
在这里插入图片描述
快排有点类似于将数组放到一个二叉树上,
在第一层选取1个元素排序、
在第二层选取2个元素排序、
在第三层选取4个元素排序、

在第n蹭选取2^(n-1)个元素排序。

每一层,都需要遍历一遍待排序的数组,大体上归纳统计,每一层时间复杂度为O(N)。
而整个“二叉树” 的高度为 logN。
所以时间复杂度为:O(N*logN)。
以上画的是理想下,每次都是均匀划分的。




空间复杂度

O(logN)

递归需要调取栈帧,递归的深度是logN,每次递归使用的空间是常数量O(1)。
所以空间上开销为:1*logN。
即空间复杂度为:O(logN)。




特性总结

1、快速排序整体的综合性能和使用场景都是比较好的,所以才敢叫快速排序
2、时间复杂度:O(N*logN)
3、 空间复杂度:O(logN)
4、稳定性:不稳定

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

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

相关文章

STM32深入系列02——BootLoader分析与实现

文章目录 1. STM32程序升级方法1.1 ST-Link / J-link下载1.2 ISP&#xff08;In System Programing&#xff09;1.3 IAP&#xff08;In Applicating Programing&#xff09;1.3.1 正常程序运行流程1.3.2 有IAP时程序运行流程 2. STM32 Bootloader实现2.1 方式一&#xff1a;Boo…

西电期末1026.删除特定字符后排序输出

一.题目 二.分析与思路 题目名字很有意思&#xff0c;先删除后排序&#xff0c;难死了&#xff0c;还是先排序后删除简单吧&#xff1f;注意字符串里有空格&#xff0c;前面提到过了&#xff1a;只能用fgets!! 三.代码实现 #include<bits/stdc.h>//万能头 #define MAX…

SpringBoot-项目引入Redis依赖

在使用Spring Boot开发应用时&#xff0c;可以使用Redis来实现缓存、分布式锁等功能。在编写业务逻辑代码时&#xff0c;可以通过注入RedisTemplate或StringRedisTemplate对象来操作Redis&#xff0c;如存取数据、设置过期时间、删除数据等。同时&#xff0c;还可以使用Redis的…

西电期末1027.判断同构数

一.题目 二.分析与思路 不用把他转成字符串再转成数字之类的&#xff0c;用数学解决就好&#xff01;找出一个数的最后位就是将其对求余啊&#xff0c;找一个数有几位以前也有过啊&#xff0c;那不就过了嘛&#xff01; 三.代码实现 #include<bits/stdc.h>//万能头 in…

Windows.OpenSSL生成ssl证书配置到nginx

一、下载OpenSSL程序安装 到E:\soft\OpenSSL-Win64 二、打开一个CMD控制台窗口&#xff0c;设置好openssl.cnf路径 E: cd E:\soft\OpenSSL-Win64\bin set OPENSSL_CONFE:\soft\OpenSSL-Win64\bin\openssl.cnf 三、在当前目录 E:\soft\OpenSSL-Win64\bin 里创建两个子目录 m…

MySQL 8.0 InnoDB Tablespaces之Temporary Tablespaces(临时表空间)

文章目录 MySQL 8.0 InnoDB Tablespaces之Temporary Tablespaces&#xff08;临时表空间&#xff09;会话临时表空间会话临时表空间的磁盘分配和回收会话临时表空间的创建创建临时表和查看临时表信息会话临时表空间相关的设置参数innodb_temp_tablespaces_dir 全局临时表空间查…

CentOS安装gcc及g++

目录 一、在线安装 二、离线安装 1、收集对应.rpm文件 2、rpm文件上传 3、在该目录下执行安装命令 3、测试 一、在线安装 联网安装比较简单只需使用如下命令即可 yum install gcc yum install gcc-c 二、离线安装 1、收集对应.rpm文件 &#xff08;1&#xff09;、解压…

OS_lab——保护模式之GDT、 Descriptor、Selector、GDTR 及其之间关系

1. 保护模式的相关数据结构 保护模式必要的数据结构定义 • GDT&#xff1a;即为 Global Descriptor Table&#xff08;全局描述符表&#xff09;&#xff0c;又称段描述符表&#xff0c; 为保护模式下的一个数据结构。其中包含多个 descriptor&#xff0c;定义了段的起始地…

pyinstaller生成的exe文件启动时间漫长的原因

加-F慢的原因是&#xff0c;pyinstaller把所有资源文件包括python解释器的依赖文件和库都打包到exe一个文件中&#xff0c;用户打开时&#xff0c;pyinstaller需要先执行一边解压操作&#xff0c;把依赖文件全部解压出来。慢就慢在这里。 如果不加-F&#xff0c;你会发现那些文…

STL标准库与泛型编程(侯捷)笔记3

STL标准库与泛型编程&#xff08;侯捷&#xff09; 本文是学习笔记&#xff0c;仅供个人学习使用。如有侵权&#xff0c;请联系删除。 参考链接 Youbute: 侯捷-STL标准库与泛型编程 B站: 侯捷 - STL Github:STL源码剖析中源码 https://github.com/SilverMaple/STLSourceCo…

常用服务器管理面板整理汇总

服务器管理面板是用于管理和控制服务器的软件&#xff0c;可以帮助管理员更轻松地进行服务器管理和维护。以下是几种常用的服务器管理面板&#xff1a; 1、宝塔面板【官网直达】 宝塔面板是一款服务器运维管理软件&#xff0c;支持Windows和Linux等操作系统&#xff0c;提供了…

《动手学深度学习》学习笔记 第6章 卷积神经网络

本系列为《动手学深度学习》学习笔记 书籍链接&#xff1a;动手学深度学习 笔记是从第四章开始&#xff0c;前面三章为基础知道&#xff0c;有需要的可以自己去看看 关于本系列笔记&#xff1a; 书里为了让读者更好的理解&#xff0c;有大篇幅的描述性的文字&#xff0c;内容很…

TypeScript 从入门到进阶之基础篇(六) 类型(断言 、推论、别名)| 联合类型 | 交叉类型

系列文章目录 TypeScript 从入门到进阶系列 TypeScript 从入门到进阶之基础篇(一) ts基础类型篇TypeScript 从入门到进阶之基础篇(二) ts进阶类型篇TypeScript 从入门到进阶之基础篇(三) 元组类型篇TypeScript 从入门到进阶之基础篇(四) symbol类型篇TypeScript 从入门到进阶…

鱼类识别Python+深度学习人工智能+TensorFlow+卷积神经网络算法

一、介绍 鱼类识别系统。使用Python作为主要编程语言开发&#xff0c;通过收集常见的30种鱼类&#xff08;‘墨鱼’, ‘多宝鱼’, ‘带鱼’, ‘石斑鱼’, ‘秋刀鱼’, ‘章鱼’, ‘红鱼’, ‘罗非鱼’, ‘胖头鱼’, ‘草鱼’, ‘银鱼’, ‘青鱼’, ‘马头鱼’, ‘鱿鱼’, ‘鲇…

如何批量自定义视频画面尺寸

在视频制作和编辑过程中&#xff0c;对于视频画面尺寸的调整是一项常见的需求。有时候&#xff0c;为了适应不同的播放平台或满足特定的展示需求&#xff0c;我们需要对视频尺寸进行批量调整。那么&#xff0c;如何实现批量自定义视频画面尺寸呢&#xff1f;本文将为您揭示这一…

github action

https://www.bilibili.com/video/BV1PM411B7um/?spm_id_frompageDriver&vd_sourcefd0f4be6d0a5aaa0a79d89604df3154a workflow pipeline

PyTorch 进阶指南,这个宝典太棒了

最新写了很多关于 Pytorch 的文章&#xff0c;主要针对刚刚接触 Pytorch 的同学&#xff0c;文章我给大家列出来了&#xff0c;喜欢可以从0开始学习&#xff1a; 小白学 PyTorch 系列&#xff1a;这一次&#xff0c;我准备了 20节 PyTorch 中文课程小白学 PyTorch 系列&#x…

【深度deepin】深度安装,jdk,tomcat,Nginx安装

目录 一 深度 1.1 介绍 1.2 与别的操作系统的优点 二 下载镜像文件及VM安装deepin 三 jdk&#xff0c;tomcat&#xff0c;Nginx安装 3.1 JDK安装 3.2 安装tomcat 3.3 安装nginx 一 深度 1.1 介绍 由深度科技社区开发的开源操作系统&#xff0c;基于Linux内核&#xf…

学完 Pinia 再也不想用 vuex 真香啊!!!!

&#x1f495;Pinia 注册 ✔ vue3 与 Pinia 注册 import { createApp } from vue import { createPinia } from pinia import App from ./App.vueconst app createApp()app.use(createPinia()) app.mount(#app)✔ vue2 与 Pinia 注册 import Vue from vue import App from …

java推荐系统:好友推荐思路

1.表的设计 表里面就两个字段&#xff0c;一个字段是用户id&#xff0c;另外一个字段是好友id&#xff0c;假如A跟B互为好友&#xff0c;那在数据库里面就会有两条数据 2.推荐好友思路 上面的图的意思是&#xff1a;h跟a的互为好友&#xff0c;a跟b&#xff0c;c&am…