【排序】冒泡排序与快速排序(三个版本+非递归图示详解哦)

news2024/12/23 0:47:23

全文目录

  • 引言
  • 冒泡排序
  • 快速排序
    • 思路
    • 实现
      • Hoare版本
        • 快排优化
      • 挖坑法
      • 前后指针法
  • 快排非递归版本
      • 思路
      • 实现
  • 总结

引言

在这篇文章中,将继续介绍排序算法:冒泡排序与快速排序:

它们都属于交换排序,即通过两两比较交换,将较大或较小的元素移动到末尾,最终实现排序。

冒泡排序

在前面的文章中,已经详细介绍过了冒泡排序:戳我康冒泡排序详解哦
在这里就只给出代码:

//冒泡排序
void bubble_sort(int* nums, int numsSize)
{
    int i = 0;
    int j = 0;
    int x = 0;
    for (i = 0; i < numsSize - 1; i++)
    {
        x = 1;
        for (j = 0; j < numsSize - 1 - i; j++)
        {
            if (nums[j]>nums[j + 1])
            {
                int temp = nums[j];
                nums[j] = nums[j + 1];
                nums[j + 1] = temp;
                x = 0;
            }
        }
        if (x)
        {
            break;
        }
    }
}

快速排序

思路

快速排序的思路为:
每次使区间的起始值为基准值,通过两两比较交换,使区间中比基准值小的元素都在其左边,比基准值大的元素都在其右边;
然后再使其左边的区间中的起始位置为基准值,使左边新区间中小于该基准值的在其左边,大于的在其右边;
迭代,直到某次调整后,基准值的左边的区间只有一个元素,就回到上一层,调整上一层区间中基准值右边的区间;
这样迭代,就可以实现最初区域,也就是整个数组的排序。

不难发现,这样的迭代思想与二叉树的前序遍历是非常像的,也具有很好的递归特性。
我们可以递归实现它,有三种实现方式:Hoare版、挖坑法、前后指针法:

实现

我们可以先来递归实现:

Hoare版本

Hoare版本实现时,需要三个参数:待排序的数组,以及需要排序的区间left与right(数组中区间的下标)。

首先,当区间只有一个元素或不存在,即当left大于等于right时,递归终止;
若区间存在,则基准值key为区间的起始值,我们可以通过记录下标来记录这个基准值(keyi);
存储区间的范围,方便递归;

然后while循环:每次循环中,先right从右向左遍历,找出小于key值的元素的下标;然后left从左往右遍历,找出大于key值的元素的下标。然后交换left与right的两个值。直到left与right相遇后终止循环;
循环结束后,将left与right相遇的那个位置的元素与keyi位置的元素交换。就实现了区间中key前面的元素全部小于key,key后面的元素大于key。即key确认在了正确的位置上

最后递归,将keyi前面的区间与后面的区间分别传参:
在这里插入图片描述

 //快速排序hoare版本
void QuickSort1(int* a, int left, int right)
{
	if (left >= right)
	{
		return;
	}
	
	int keyi = left;//keyi从最左边开始
	int left2 = left;
	int right2 = right;
	while (left < right)
	{
		while (left < right && a[right] >= a[keyi])
		{
			right--;
		}
		while (left < right && a[left] <= a[keyi])
		{
			left++;
		}
		Swap(a, left, right);
	}
	Swap(a, keyi, left);

	QuickSort1(a, left2, left - 1);
	QuickSort1(a, right + 1, right2);
}

快排优化

有了上面的实现,相信大家对于快排的思路有了更深的认识,那么快排为什么快呢?

类似于二叉树的递归,最优的情况下:第一层中确认一个元素的位置,第二层中确认2个元素的位置,第三层中确认4个元素的位置…
依次类推,假设有n个元素需要排序,最优情况下,需要logn层(以2为底),时间复杂度为O(nlogn)。

但是,当这个数组已经有序时,当key值选取最左边的值时,它就是区间中的最小(大)值。一层遍历实际只能确认一个元素的位置。排完整个数组就需要n层,时间复杂度为O(n^2)。
在这里插入图片描述

显然,这极大的影响的快排的效率。要解决这个问题,就需要key值在区间中是一个不大不小的值:key值越接近区间的中位数,快排的效率就越高。

所以就想到了三数取中的方法:
即比较起始元素,中间位置元素与末尾元素的值,将这三个数中的中位数交换到起始位置,就可以使起始为值的key值是一个不大不小的值,以提高效率:

void Swap(int* a, int m, int n)//交换
{
	int temp = a[n];
	a[n] = a[m];
	a[m] = temp;
}
int GetMidNumi(int* a, int left, int right)//三数取中
{
	int mid = (left + right) / 2;
	if (a[left] < a[mid])
	{
		if (a[mid] < a[right])
		{
			return mid;
		}
		else if (a[right] < a[left])
		{
			return left;
		}
		else
		{
			return right;
		}
	}
	else //a[left] >= a[mid]
	{
		if (a[right] < a[mid])
		{
			return mid;
		}
		else if (a[left] < a[right])
		{
			return left;
		}
		else
		{
			return right;
		}
	}
}

但是这个方案依旧不能解决所有元素相等的情况,就算再三数取中,也不能取一个不大不小的key值。在后面的文章中,可能会介绍解决这种情况的方法。

挖坑法

挖坑法实现时:参数部分与Hoare版本相同。

首先,当区间只有一个元素或不存在,即当left大于等于right时,递归终止;
然后,根据三数取中,选取一个基准值key,将其放在区间的起始位置,坑的初始值就是key的下标;
存储区间的范围,方便递归;

然后while循环:每次循环中,先right从右向左遍历,找出小于key值的元素的下标,然后将这个元素放到坑处,该元素的位置为新的坑;然后left从左往右遍历,找出大于key值的元素的下标,然后将这个元素放到坑处,该元素的位置为新的坑。直到left与right相遇后结束循环;
循环结束后,将key值放进坑中;

最后递归,将keyi前面的区间与后面的区间分别传参:
在这里插入图片描述

//快速排序挖坑法
void QuickSort2(int* a, int left, int right)
{
	if (left >= right)
	{
		return;
	}

	int midi = GetMidNumi(a, left, right);//三数取中,将中间的数放到左位置
	if (a[midi] != a[left])
	{
		Swap(a, left, midi);
	}

	int key = a[left];//key是最左边的数
	int hole = left;
	int left2 = left;//保存left与right的值
	int right2 = right;
	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;

	QuickSort2(a, left2, left - 1);
	QuickSort2(a, right + 1, right2);
}

前后指针法

前后指针法实现时,参数列表也与前两个相同;

首先,判断区间;三数取中选key,记录key下标keyi;存储区间的范围,方便递归;
然后,创建两个变量prev与cur,分别表示前后两个位置:prev初始化为区域的起始位置,cur初始化为prev的下一个位置;

然后while循环,当cur所在位置的元素的值小于key值时,prev向后移动一个元素,并交换prev与cur位置的元素;否则,cur向后移动一个元素,prev不动。直到cur移动到区域的末尾时,循环结束;
循环结束后,交换keyi位置的key值与prev位置的值,并将keyi改为prev;
这个过程的目的其实就是使prev后面的元素(不包括prev,包括cur)都是大于key值的,最后交换prev与keyi位置的值,就是使keyi前面区间的值全部小于key,后面的全部大于key

最后递归,将keyi前面的区间与后面的区间分别传参:

在这里插入图片描述

 //快速排序前后指针法
void QuickSort3(int* a, int left, int right)
{
	if (left >= right)
	{
		return;
	}

	int midi = GetMidNumi(a, left, right);//三数取中,将中间的数放到左位置
	if (a[midi] != a[left])
	{
		Swap(a, left, midi);
	}

	int keyi = left;//keyi还是从最左边开始
	int prev = keyi;
	int cur = prev + 1;
	while (cur <= right)
	{
		if (a[cur] < a[keyi])
		{
			prev++;
			if (a[prev] != a[cur])
			{
				Swap(a, prev, cur);
			}
		}
		cur++;
	}
	Swap(a, keyi, prev);
	keyi = prev;

	QuickSort3(a, left, keyi - 1);
	QuickSort3(a, keyi + 1, right);
}

由于这个版本实现时,是最方便的,所以最推荐这种写法。

快排非递归版本

当然,所有的递归实现都是可以转化为非递归的,例如之前的斐波那契,可以直接转化为非递归。
但是如果直接非递归实现快排,就会有问题。即当一个左区域已经实现了排序后,返回到上一级区域,再递归到右区域。这个过程是需要知道知道上一级的区域范围的。直接转化非递归显然是不行的。

所以我们需要借助一个结构,来存储上层区域的范围。类似于完成一区域排序后向上返回,这个结构需要满足,后存储的区域先执行排序,就可以从最小的区域向上逐层完成最大的区域的排序,栈这个数据结构恰好符合我们的需求。当栈为空的时候,也就是所有的区域都完成排序的时候。

思路

上面的思路只是模拟从最小的部分向上返回的过程,在要快排一个数组时,完整的过程应该是由最大的区域递归下来到最小区域,再返回上去,所以完整的过程应该为:

先将最大的区域的左右区间入栈,然后进入循环:
循环中先将栈顶的区间取出,对该区间进行排序,将key值放在正确的位置;
然后将keyi后面的区间与前面的区间分别入栈(出栈的时候先出左边的区间);
再取栈顶的一组区间,排序,再将两个区域入栈,直到入栈的区域小于或等于1个元素时,就不入栈;
下次循环时,出栈的区域就是上级区域的右边;

如此循环,当栈为空时终止循环,排序结束:

实现

外层循环控制每层的入栈与出栈,内层循环控制每个区域中的排序(这个排序可以使用上面实现的三种方法,这里用的是前后指针法):

在这里插入图片描述

// 快速排序 非递归实现(前后指针法)(需要栈辅助实现)
void QuickSortNonR(int* a, int left, int right)
{
	ST st;
	STInit(&st);
	STPush(&st, left);
	STPush(&st, right);
	while (STEmpty(&st) == 0)
	{
		int end = STTop(&st);
		STPop(&st);
		int begin= STTop(&st);
		STPop(&st);

		//前后指针法 
		int midi = GetMidNumi(a, begin, end);//三数取中,将中间的数放到左位置
		if (a[midi] != a[begin])
		{
			Swap(a, begin, midi);
		}
		int keyi = begin;
		int prev = begin;
		int cur = prev + 1;
		while (cur <= end)
		{
			if (a[cur] < a[keyi])
			{
				prev++;
				if (a[prev] != a[cur])
				{
					Swap(a, prev, cur);
				}
			}
			cur++;
		}
		Swap(a, keyi, prev);
		keyi = prev;

		//新的区间入栈(先传右区间再传左区间,取的时候就是先左后右)
		if (keyi + 1 < end)
		{
			STPush(&st, keyi + 1);
			STPush(&st, end);
		}
		if (keyi - 1 >begin)
		{
			STPush(&st, begin);
			STPush(&st, keyi - 1);
		}
	}
	STDestroy(&st);
}

以后的一些不能直接转化为非递归的算法,也都可以借助栈来完成。

总结

到此,关于冒泡排序与快速排序三种实现与非递归实现的内容都已经介绍完了
接下来会继续介绍其他的排序,欢迎持续关注哦

如果大家认为我对某一部分没有介绍清楚或者某一部分出了问题,欢迎大家在评论区提出

如果本文对你有帮助,希望一键三连哦

希望与大家共同进步哦

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

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

相关文章

小朋友崇拜圈+灌溉(JAVA解法)

目录 小朋友崇拜圈 题目链接&#xff1a; 题目描述 输入描述 输出描述 输入输出样例 灌溉 题目链接&#xff1a; 题目描述 输入描述 输出描述 输入输出样例 小朋友崇拜圈 题目链接&#xff1a; https://www.lanqiao.cn/problems/182/learning/?page5&first_c…

手撕源码(一)HashMap-JDK8

目录 1.使用示例2.new HashMap<>() 解析2.1 加载因子2.2 构造方法 3.put() 解析3.1 原始put(k, v)3.2 计算哈希1&#xff09;为什么要进行二次hash&#xff1f;2&#xff09;二次hash计算示例&#xff1a;3&#xff09;为什么使用 (length-1)&hash 而不是 hash%lengt…

互联网医院系统|线上问诊系统定制|互联网医院源码开发技术

当下医疗成为人们比较关注的问题&#xff0c;移动医疗技术不断的进步&#xff0c;互联网医院系统成为了医疗企业发展的必经之路&#xff0c;通过移动终端与互联网医院系统进行连接&#xff0c;实现医疗服务的远程交互与管理。可以使医疗机构的医生通过移动设备随时随地的查看患…

词的表示方法笔记——词向量+代码练习

词的表示方法&#xff1a; 一、one-hot&#xff08;最简单&#xff09; 独热编码是一种将单词转化为稀疏向量的方法&#xff0c;其中每个单词都表示为一个只有一个元素为1其余元素均为0的向量&#xff0c;其维度由词库的大小决定。。例如&#xff0c;对于包含 4个单词的词汇表 …

Python二分查找(折半查找)的实现

时间复杂度 最优时间复杂度&#xff1a;O(1) 最坏时间复杂度&#xff1a;O(logn) 思路 对有序的顺序表进行查找&#xff0c;以下标查找&#xff0c;每次取一半查找&#xff0c;如[1,2,3,4,5,6,7,8,9]&#xff0c;查3, 下标从0~8&#xff0c;(08)//24,对比折半到下标为2的元素…

【基础算法】栈和队列

系列综述&#xff1a; &#x1f49e;目的&#xff1a;本系列是个人整理为了秋招算法的&#xff0c;整理期间苛求每个知识点&#xff0c;平衡理解简易度与深入程度。 &#x1f970;来源&#xff1a;材料主要源于代码随想录进行的&#xff0c;每个算法代码参考leetcode高赞回答和…

手写axios源码系列二:创建axios函数对象

文章目录 一、模块化目录介绍二、创建 axios 函数对象1、创建 axios.js 文件2、创建 defaults.js 文件3、创建 _Axios.js 文件4、总结 当前篇章正式进入手写 axios 源码系列&#xff0c;我们要真枪实弹的开始写代码了。 因为 axios 源码的代码量比较庞大&#xff0c;所以我们这…

Xilinx FPGA下如何加快QSPI Flash加载速度

1. 首先&#xff0c;不同型号的FPGA对外部QSPI Flash支持的最高频率是不一样的。XC6SLX45支持的最高频率仅为26MHz&#xff0c; 而XC7K325T支持的最高频率高达66MHz。 所以&#xff0c;当我们添加 set_property BITSTREAM.CONFIG.CONFIGRATE 50 [current_design] 的时候&…

特征选择算法 | Matlab实现基于互信息特征选择算法的分类数据特征选择 MI

文章目录 效果一览文章概述部分源码参考资料效果一览 文章概述 特征选择算法 | Matlab实现基于互信息特征选择算法的分类数据特征选择 MI 部分源码 %

【1105. 填充书架】

来源&#xff1a;力扣&#xff08;LeetCode&#xff09; 描述&#xff1a; 给定一个数组 books &#xff0c;其中 books[i] [thicknessi, heighti] 表示第 i 本书的厚度和高度。你也会得到一个整数 shelfWidth 。 按顺序 将这些书摆放到总宽度为 shelfWidth 的书架上。 先…

题目3180:蓝桥杯2023年第十四届省赛真题-互质数的个数======及探讨互质专题

原题链接 https://www.dotcpp.com/oj/problem3162.html 想直接看题解的&#xff0c;跳转到第三次尝试即可。 已AC。 解析&#xff1a; &#xff08;1&#xff09;首先大家要知道什么叫互质&#xff1a; 以及它们的性质&#xff1a; 欧拉函数 在数论中&#xff0c;对正整…

世界读书日|这些值得程序员反复阅读的经典书

2023年是第28个世界读书日&#xff0c;每年的这个时候&#xff0c;小编都会准备一份书单与您分享。 与经典同行&#xff0c;伴书香成长。小编今天推荐一份值得程序员反复阅读的经典书。 1、浪潮之巅 第四版 这不只是一部科技产业发展历史集…… 更是在这个智能时代&#xff…

【远程工具】- MobaXterm 的下载、安装、使用、配置【Telnet/ssh/Serial】

一、MobaXterm 概述 在远程终端工具中&#xff0c;secureCrt 和 XShell 是两款比较有名的远程工具&#xff0c;但这两款软件现在都是收费的&#xff0c;有些公司不允许破解使用。今天就推荐一款免费的、免安装的、功能丰富的远程终端软件–MobaXterm。 MobaXterm是由Mobatek开…

JavaScript概述三(循环结构+BOM浏览器对象模型+JSON对象)

1.循环结构 1.1 普通循环(for循环,while循环,do……while循环) JavaScript中的普通循环和Java中的普通循环基本类似&#xff0c;此处以for循环为例&#xff0c;while和do……while便不再赘述。 <script type"text/javascript">var ary1new Array(1,false,嘿嘿…

Redis队列Stream、Redis多线程详解(三)

Redis中的线程和IO模型 什么是Reactor模式 &#xff1f; “反应”器名字中”反应“的由来&#xff1a; “反应”即“倒置”&#xff0c;“控制逆转”,具体事件处理程序不调用反应器&#xff0c;而向反应器注册一个事件处理器&#xff0c;表示自己对某些事件感兴趣&#xff0…

CTA进网测试《5G消息 终端测试方法》标准依据:YDT 3958-2021

GB 21288-2022 强制国标要求变化​ 与GB 21288-2007相比&#xff0c; 新国标主要有以下变化&#xff1a; 1. 增加职业暴露定义&#xff1a; 2. 增加吸收功率密度定义&#xff1a; 3. 增加不同频率、不同人体部位适用的暴露限值&#xff1a; 4. 增加产品说明书的注释&#xff1a…

ArcGIS Pro用户界面

目录 1 功能区 1.1 快速访问工具栏 1.2 自定义快速访问工具栏 1.3 自定义功能区选项 1.3.1 添加组和命令 1.3.2 添加新选项卡 2 视图 3 用户界面排列 ​编辑 4 窗格 4.1 内容窗格 4.2 目录窗格 4.3 目录视图&#xff08;类似ArcCatalog&#xff09; 4.4 浏览对话框…

注册表取证

目录 操作系统安装时间 计算机名称 本地用户 最后登录的用户 当前登录用户 U盘序列号 USB挂载的盘符 卷标名称 安装的程序 ​编辑卸载的程序 最近使用的文件 最近运行的命令行 操作系统安装时间 HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion…

无需代码!新人可操作!分享20个可视化大屏(内附下载)

做前端开发的各位&#xff0c;大都知晓老板钟爱的可视化大屏—清晰、便捷、撑排面&#xff0c;轻轻松松打破数据孤岛等一系列问题。我真的深有体会&#xff0c;前些天&#xff0c;老板去人家公司开会&#xff0c;回来就把这大屏安排上了。之前就认为大屏也不过是面子工程&#…

03 - 大学生如何使用GPT

大学生如何使用GPT提高学习效率 一、引言 在当今的高速发展的信息时代&#xff0c;大学生面临着越来越多的学习挑战。作为一种先进的人工智能技术&#xff0c;GPT为大学生提供了一种强大的学习工具。本文将介绍大学生在不同场景中如何使用GPT来提高学习效率&#xff0c;并给出…