[C/C++]排序算法 快速排序 (递归与非递归)

news2024/11/16 15:34:16

目录

🚩概念:

🚩实现:

⚡1.hoare

⚡2.挖坑法

⚡3.双指针法

🚩快速排序递归实现

🚩快速排序非递归实现


🚩概念:

          通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据比另一部分的所有数据要小,再按这种方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,使整个数据变成有序序列。 

🌟排序过程:

1.在数组中确定一个关键值

2.将小于关键值的数排到其左边,将大于关键值的数排到其右边,此时关键数在数组中的位置就排好了

3.在左边的数据和右边的数据分别找一个关键值,通过排序使小于关键值的数排到其左边,大于关键值的数排到其右边...

4.重复上述操作,可以通过递归与非递归实现


快速排序的关键是写好步骤二的单趟排序,实现这个功能有三种版本

  1. hoare
  2. 挖坑法
  3. 双指针法

🚩实现:

⚡1.hoare

        将数组第一个元素定位关键值,定义begin和end指针,先让end从后往前找到比关键值小的数,begin从前往后找比关键值大的数,然后交换两数,直到 begin==end,再让关键值和begin所指的元素交换,最后返回关键值所在位置,便于后续进行递归或非递归操作(一定要先从后往前找小,原因下文解释)

动态展示:

void swap(int* a, int* b)
{
	int tmp = *a;
	*a = *b;
	*b = tmp;
}

//hoare
int PartSort1(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]);
	return left;
}

2.挖坑法

        首先将关键值定为数组第一个元素,并将坑位定为begin,先让end从后往前找到比关键值小的数,将这个数放到坑位,并更新坑位,再让begin从前往后找比关键值大的数,将这个数放到坑位,并更新坑位,直到 begin==end,再让关键值和坑位的元素交换,最后返回关键值所在位置

//挖坑法
int PartSort2(int* a, int begin, int end)
{
	int mid = GetMid(a, begin, end);
	swap(&a[begin], &a[mid]);

	int key = a[begin];
	int hole = begin;
	while (begin < end)
	{
		//右边找小,填入坑内,更新坑位
		while (begin<end && a[end]>=key)
		{
			--end;
		}
		a[hole] = a[end];
		hole = end;
		//左边找大,填入坑内,更新坑位
		while (begin<end && a[begin]<=key)
		{
			++begin;
		}
		a[hole] = a[begin];
		hole = begin;
	}
	a[hole] = key;
	return hole;
}

⚡3.双指针法

        将数组第一个元素定为关键值,定义两个指针prev和cur,先让prev指向数组的第一个元素,cur指向prev的下一个元素,cur的作用是找比关键值小的元素,若cur所指元素不小于关键值则cur++,直到cur所值元素小于关键值,此时,prev和cur之间的元素都是大于关键值的元素,若prev+1不是cur的话就可以让prev++所指元素与cur所指元素交换了,直到cur指向数组的最后一个元素

这里可能会有人出现问题:

1.为什么要判断 prev++!=cur

[解释]:如果prev+1为cur的话,那交换prev++和cur所指元素那就是一个元素之间的交换了,没有意义

2.为什么要交换prev++所指元素

[解释]:因为prev和cur之间的元素都为大于关键值的元素,prev++就可以让prev指向大于关键值的元素,而cur所指的是小于关键值的元素,这样一交换小的数就去前面了,大的数就去后面了

//双指针法
int PartSort3(int* a, int begin, int end)
{
	int mid = GetMid(a, begin, end);
	swap(&a[begin], &a[mid]);

	int key = begin;
	int prev = begin;
	int cur = prev + 1;
	while (cur <= end)
	{
		if (a[cur] < a[key] && ++prev != cur)
		{
			swap(&a[prev], &a[cur]);
		}
		cur++;
	}
	swap(&a[prev], &a[key]);
	return prev;
}

🚩快速排序递归实现

小优化:

        上述三个方法都是快速排序的单趟排序,但是上述排序还有一个小缺陷,因为三个方法都是固定第一个元素为关键值的,如果数组为有序的,那么从后往前找小就要遍历整个数组,效率会很小,所以通常会再写一个找中间值的函数:在数组开头结尾和中间三个数中找出一个大小在中间的数,并让这个数和数组第一个数交换,这样就会减少上述情况的发生

int GetMid(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[end] > a[begin])
			return end;
		else
			return begin;
	}
	else
	{
		if (a[begin] > a[end])
			return begin;
		else if (a[end] > a[mid])
			return mid;
		else
			return end;
	}
}


void swap(int* a, int* b)
{
	int tmp = *a;
	*a = *b;
	*b = tmp;
}

//hoare
int PartSort1(int* a, int begin, int end)
{
	int mid = GetMid(a, begin, end);
	swap(&a[begin], &a[mid]);

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

//挖坑法
int PartSort2(int* a, int begin, int end)
{
	int mid = GetMid(a, begin, end);
	swap(&a[begin], &a[mid]);

	int key = a[begin];
	int hole = begin;
	while (begin < end)
	{
		//右边找小,填入坑内,更新坑位
		while (begin<end && a[end]>=key)
		{
			--end;
		}
		a[hole] = a[end];
		hole = end;
		//左边找大,填入坑内,更新坑位
		while (begin<end && a[begin]<=key)
		{
			++begin;
		}
		a[hole] = a[begin];
		hole = begin;
	}
	a[hole] = key;
	return hole;
}
//双指针法
int PartSort3(int* a, int begin, int end)
{
	int mid = GetMid(a, begin, end);
	swap(&a[begin], &a[mid]);

	int key = begin;
	int prev = begin;
	int cur = prev + 1;
	while (cur <= end)
	{
		if (a[cur] < a[key] && ++prev != cur)
		{
			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 keyi = PartSort3(a, begin, end);
	QuickSort(a, begin, keyi - 1);
	QuickSort(a, keyi + 1, end);
}

补充:

为什么开始一定要右边找小,并且为什么left和right相遇那个点一定小于关键值呢?

[解释]:

        L遇到R有两种情况

  • R遇到L: R从右往左找小,一直没找到小,直到遇到L,而L的点的值小于关键值(因为此时L的值是和上一轮R找的小的值)
  • L遇到R:R先从右找小,找到小停下来,L从左往右找打大没有找到遇到R,相遇点的值小于关键值

🚩快速排序非递归实现

        快速排序的递归实现,无非就是通过调用函数对数组的不同区间进行排序,而非递归我们也可以用栈实现,不是递归胜似递归.

实现思路:
1.创建一个栈,将数组的右边界下标和左边界下标依次入栈

2.循环弹出数组的左右边界下标,并对该区间进行单趟排序,确定关键值的下标,分为左右两个区间

3.若左区间元素个数大于一个,将左区间右边界下标和左边界下标依次入栈,右区间同理

4.重复操作步骤2 3直到栈为空


例如待排序数组为:

第一步:将右边界下标和左边界下标7和0入栈

第二步:将边界值弹出,将0给到begin,7给到end,进行单趟排序(单趟排序采用挖坑法)

排序完后,key将数组分为了左右两个区间,让右区间边界7和5入栈,左边界3和0入栈

第三步:取出0  3并对此区间单趟排序

此时,关键值又把区间分为了两个区间,右区间只有一个值,没必要入栈排序,只需要将左区间边界1 0入栈

再弹出0 1,对此区间单趟排序,此时左区间无元素,有区间只有一个元素,这样数组左边就全部排序完成了,再次出栈的话就该排序5和7的区间了,和左边类似

void QuickSortNonR(int* a, int begin, int end)
{
	ST s;
	STInit(&s);
	STPush(&s,end);
	STPush(&s,begin);

	while (!STEmpty(&s))
	{
		int left = STTop(&s);
		STPop(&s);
		int right = STTop(&s);
		STPop(&s);

		int key = PartSort1(a, left, right);

		if (left < key - 1)
		{
			STPush(&s, key - 1);
			STPush(&s, left);
		}

		if (right > key + 1)
		{
			STPush(&s, right);
			STPush(&s, key+1);
		}
	}
	STDestroy(&s);
}

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

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

相关文章

华为OD机试真题-手机App防沉迷系统-2023年OD统一考试(C卷)

题目描述&#xff1a; 智能手机方便了我们生活的同时&#xff0c;也侵占了我们不少的时间。“手机App防沉迷系统”能够让我们每天合理的规划手机App使用时间&#xff0c;在正确的时间做正确的事。 它的大概原理是这样的&#xff1a; 1、在一天24小时内&#xff0c;可注册每个Ap…

什么洗地机值得推荐?2024入门级智能洗地机

对于当代社畜&#xff0c;每天下班后回家已经疲惫不堪&#xff0c;但家务活仍然等待着处理。虽然外卖可以解决洗碗的问题&#xff0c;但地面的清洁却是无法回避的任务。传统的扫地、拖地流程让人感到腰酸背痛&#xff0c;因此人们纷纷寻找能够快速清洁地面的工具&#xff0c;如…

Shell命令与Linux操作系统:深入理解其原理和功能(2/2)

在当今数字化时代&#xff0c;操作系统的安全性和稳定性对于个人用户和企业都至关重要。Linux&#xff0c;作为一个广泛使用的操作系统&#xff0c;其强大的文件权限系统是保护系统安全的核心机制之一。无论是在服务器管理、软件开发还是日常使用中&#xff0c;有效地管理和理解…

scons_交叉编译arm64_sysroot基于根文件rootfs编译方法

文章目录 1.问题现象2.环境变量3.实例1:交编译arm64 hello.c解决方法1: 指定rootfs下的include头文件解决方法2: 下载开源arm64-linux-gnu-gcc小结 4.交叉编译依赖第3方库1.前言2.小知识: gcc默认搜索与支持的库3.实例: 交叉编译依赖ROS的程序gcc/g 编译流程gcc/g 链接流程 5.遇…

阿里云双11活动:如何通过客户端连接linux服务器?配置入门详解

前言 最近双11活动&#xff0c;发现阿里云有服务器活动&#xff0c;就买了一个&#xff0c;今天主要给大家介绍下&#xff0c;如何通过通过客户端进行连接linux服务器后&#xff0c;进行简单的操作。 配置服务器信息 打开浏览器登录到阿里云服务器页面上&#xff0c;找到个人…

uni-app/vue封装etc车牌照输入,获取键盘按键键值

先看下效果如下&#xff1a; 动态图如下 uniapp的keyup获取不到keyCode和compositionstart&#xff0c;compositionend&#xff0c;所以需要监听input节点的keyup事件&#xff0c; 思路以及代码如下&#xff1a; 1.将每一个字符用文本框输入&#xff0c;代码如下 <view …

ClickHouse基础知识(二):ClickHouse 安装教程

1. 准备工作 1.1 确定防火墙处于关闭状态 1.2 CentOS 取消打开文件数限制 &#xff08;1&#xff09;在 hadoop101 的 /etc/security/limits.conf 文件的末尾加入以下内容 sudo vim /etc/security/limits.conf&#xff08;2&#xff09;在 hadoop101 的/etc/security/limits.…

64.乐理基础-打拍子-前八后十六、前十六后八拍子

前置内容&#xff1a;63.乐理基础-打拍子-四十六-CSDN博客 前八后十六指的是前半拍是一个八分音符&#xff0c;后半怕是两个十六分音符的节奏型&#xff0c;如图1。 前十六后八刚好就与前八后十六反着&#xff0c;如图3。 图1&#xff1a;在以四分音符为一拍的时候这三个音符加…

Android下载gradle失败解决方法

1、在gradle-wrapper.properties文件中查看自己需要下载gradle什么版本的包和zip路径&#xff08;wrapper/dists&#xff09;。 2、在setting中查看Gradle的保存路径&#xff0c;如下图&#xff1a;C:/Users/Administrator/.gradle&#xff0c;加上第一步的zip路径得到下载grad…

chcp 65001

chcp 65001 DOS,BAT,CMD乱码 转UTF-8&#xff0c;就不会汉字乱码了 936 GBK 65001 UTF-8

C语言中关于if else的理解

if else我们可以理解为 if(条件1) //如果条件1成立 语句1&#xff1b; //执行语句1 else //如果条件1不成立 语句2; //执行语句2 这是一个经典的if els…

Vue Echarts 多折线图只有一条X轴(合并X轴数据并去重排序) - 附完整示例

echarts&#xff1a;一个基于 JavaScript 的开源可视化图表库。 目录 效果 一、介绍 1、官方文档&#xff1a;Apache ECharts 2、官方示例 二、准备工作 1、安装依赖包 2、示例版本 三、使用步骤 1、在单页面引入 echarts 2、指定容器并设置容器宽高 3、数据处理&am…

Word中插入mathtype的行内公式显示不全,设置行距,最小值

Word中插入mathtype的行内公式显示不全 如下图&#xff1a;公式上下被遮住 解决方式&#xff1a; 设置所在段落的行距&#xff1a;最小值--xx磅。同时取消勾选 “如果定义了文档网格&#xff0c;则对齐到网格” 处理后效果&#xff1a;

电子科大软件架构设计——期末复习题集

文章目录 系统分析与设计概述面向对象建模语言系统规划系统需求分析系统架构设计软件建模详细设计与设计模式用户界面设计 系统分析与设计概述 1&#xff0e;下面哪个不是信息系统利益相关者?&#xff08;D) A&#xff0e;客户B&#xff0e;用户 C.开发人员D&#xff0e;监理…

免费SSL证书申请入口

免费SSL证书的优势 a. 低成本 免费SSL证书相对于其他类型的SSL证书来说&#xff0c;无需支付昂贵的费用。对于个人网站、小型企业或预算有限的组织而言&#xff0c;免费SSL证书是一种经济实惠的选择。 b. 提高搜索引擎排名 搜索引擎算法中&#xff0c;对于使用HTTPS协议的网…

Solana 生态铭文跨链桥 Sobit 是何神圣?其场外白名单已达到1200U

在短暂的沉寂&#xff0c;在与 Solana 手机 Saga 联合生态 Meme 币 Bonk 掀起一波 meme 浪潮&#xff0c;以及GPU 计算网路Render network 宣布将从公链Polygon迁往Solana 后&#xff0c;Solana 生态再次迎来爆发。随着 SOL 代币在 12 月暴涨&#xff0c;SOL 也在市值上超越了 …

性能测试-jemeter:安装 / 基础使用

一、理解jemeter 官网-Apache JMeter-Apache JMeter™ JMeter是一款开源的性能测试工具&#xff0c;主要用于模拟大量用户并发访问目标服务器&#xff0c;以评估服务器的性能和稳定性。 JMeter可以执行以下任务序号用途描述1性能测试通过模拟多个用户在同一时间对服务器进行…

python风格规范之missing whitespace after

python风格规范之missing whitespace after 规范错误/警告描述规范错误描述(示例)规范错误认知正确示例 规范错误/警告描述 PEP 8: E231 missing whitespace after [mark]规范错误描述(示例) 规范错误认知 上述规范错误表示"[mark]"后面没有添加空格 。错误示例如下…

第一届能源电子产业创新大赛太阳能光伏赛道在京顺利完成初赛评审

近日&#xff0c;第一届能源电子产业创新大赛太阳能光伏赛道初赛在北京顺利举行。本次太阳能光伏赛道赛事由工业和信息化部产业发展促进中心、宜宾市人民政府主办&#xff0c;宜宾市经济和信息化局、宜宾高新技术产业园区承办&#xff0c;中国国检测试控股集团股份有限公司协办…

C语言之字符串处理

目录 字符串长度 显示字符串 数字字符的出现次数 大小写字符转换 字符串数组的参数传递 非字符串的字符数组 目前我们所学习到的是围绕字符串的处理&#xff0c;仅仅是生成字符串、读取并显示字符串&#xff0c;下面我学习更加灵活处理字符串的方式。 字符串长度 我们来看…