【数据结构】—交换排序之快速排序究极详解,手把手带你从简单的冒泡排序升级到排序的难点{快速排序}(含C语言实现)

news2024/10/7 2:18:42

                                       食用指南:本文在有C基础的情况下食用更佳 

                                       🔥这就不得不推荐此专栏了:C语言

                                       ♈️今日夜电波:靴の花火—ヨルシカ

                                                                0:28━━━━━━️💟──────── 5:03
                                                                    🔄   ◀️   ⏸   ▶️    ☰ 

                                      💗关注👍点赞🙌收藏您的每一次鼓励都是对我莫大的支持😍 


目录

♉️一、前置知识—什么是交换排序

♊️二、冒泡排序

        冒泡排序的思想

        冒泡排序的实现

♋️三、快速排序 (排序算法中的重点,肥肠重要!!!)

        快速排序的思想

        快速排序的三种版本实现

        1、hoare版本(基础版本)

        ♒️快排的优化  

        2、 挖坑法

        3、前后指针法


♉️一、前置知识—什么是交换排序

        交换排序的基本思想是通过比较相邻元素的大小关系,如果两个相邻元素的大小关系不满足排序要求,就交换它们的位置,以达到排序的目的。交换排序分为两种,即冒泡排序和快速排序。

♊️二、冒泡排序

        冒泡排序的思想

        冒泡排序是一种基本的排序算法,它的思想是将待排序的元素依次比较相邻的两个元素,根据比较结果交换它们的位置,从而使较大(或较小)的元素逐渐往后移动,最终实现整个序列的排序。

        具体的实现过程如下:

        1. 从序列的第一个元素开始,依次比较相邻的两个元素的大小。

        2. 如果前一个元素比后一个元素大(或小),则交换它们的位置。

        3. 继续比较序列中下一个相邻的元素,直至最后一个元素。

        4. 重复以上操作,每一轮比较都将序列中最大(或最小)的元素排到了序列的末尾。

        5. 经过多轮比较后,序列中的元素就按照从小到大(或从大到小)的顺序排好了。

         一图理解~

         冒泡排序的实现

        太简单了,就不多解释了,看代码即可

void BubbleSort(int* a, int n)
{
	for (int j = 0; j < n; ++j)
	{
		bool exchange = false;
		for (int i = 1; i < n-j; i++)
		{
			if (a[i - 1] > a[i])
			{
				int tmp = a[i];
				a[i] = a[i - 1];
				a[i - 1] = tmp;

				exchange = true;
			}
		}

		if (exchange == false)
		{
			break;
		}
	}
}

♋️三、快速排序 (排序算法中的重点,肥肠重要!!!)

        快速排序的思想

        快速排序的基本思想是选定一个轴值,将待排序序列划分为两部分,一部分是小于轴值的元素,一部分是大于轴值的元素。然后对于这两部分分别递归地进行快速排序,直到排序完成。具体实现时,可以选择待排序序列的第一个元素作为轴值(通常需要进行比较选出,后面会详讲,也可以随机选择一个元素作为轴值,然后将序列中的元素与轴值进行比较,小于轴值的元素放在轴值的左边,大于轴值的元素放在轴值的右边,相等的元素可以放在任 一 一边,最后将左右两部分序列递归地进行快速排序即可。快速排序一般有三种实现方法:hoare法、挖坑法、前后指针法

        一图先有个大概的了解~ 

        以下的内容是循序渐进的,建议大家一步一步往下看! 

        快速排序的三种版本实现

        1、hoare版本(基础版本)

        上来请先看一张图~

具体实现步骤如下:

  1. 选择一个基准元素 key,通常可以选择第一个或者最后一个元素作为基准元素(这里的key值选第一个)。

  2. 定义两个指针 left和 right 分别指向数组的开头和结尾。

  3. 从右向左扫描数组,如果当前元素大于或等于基准元素,则指针 right 向左移动一位。

  4. 从左向右扫描数组,如果当前元素小于或等于基准元素,则指针 left 向右移动一位。

  5. 如果 left < right,则交换指针所指向的元素。

  6. 当 left >= right 时,说明当前分区操作已经完成,交换基准元素和 left 指针指向的元素,并返回 left 的值。

  7. 对基准元素左侧的子数组和右侧的子数组分别递归执行上述步骤,直到数组长度为 1 或 0,此时数组就已经排好序了。

一些注意事项:

  1. Hoare 版本的快速排序可以避免对基准元素相同的元素的不必要交换操作,因此比 Lomuto 版本效率更高。

  2. 在实:现时需要注意边界条件的处理,尤其是数组下标越界问题。

        在这里大家可能会有一个疑惑,两个指针i,j相遇的位置的值,如何保证要比key的值小呢?

                答案:右边的值先走就能做到!!!

        当右侧的值先走时,如果它遇到了一个比基准元素小的值,那么它就会停下来,等待左侧的值去找到一个比基准元素大的值。接着左侧的值找到了一个比基准元素大的值后,交换左右值的位置,此时右侧的值就会从原来的位置继续走下去。由于左侧的值都比基准元素小,所以右侧的值再次遇到比基准元素小的值时,仍然会停下来等待左侧的值交换位置。这样,右侧的值先走就可以保证最后相遇的位置的值一定比基准元素的值小。

         在这里大家可能会有一个疑惑了,那我们能左边先走吗?

                当然可以,同样的道理,只需要key值在右边就行了!

         代码实现:

        详见代码内注释

void Swap(int* x, int* y)
{
	int tmp = *x;
	*x = *y;
	*y = tmp;
}

int Partsort(int* a, int left, int right)
{
	int key = left;
	while (left < right)
	{
		//找小,注意这里是右向左靠,在最后会的一步将是靠向左边
		while (left < right && a[right] >= a[key])
		{
			--right;
		}

		//找大
		while (left < right && a[left] <= a[key])
		{
			++left;
		}

		Swap(&a[left], &a[right]);//交换大于key和小于key位置的值
	}

	Swap(&a[key], &a[left]);//此时left==right,交换key和left的值
	return left;//用于递归下一步的key的左半边以及右半边

}

void Quicksort(int* a, int begin, int end)
{
	if (begin >= end)//当下标越界时,直接退出
		return;

	int key = Partsort(a, begin, end);
	// [begin, key-1] key [key+1, end] 大致形状
	Quicksort(a, begin, key - 1);//对左key半边进行排序
	Quicksort(a, key + 1, end);//对右半边进行排序
}

        ♒️快排的优化  

        先看个例子:(当我们的快排是排升序,然而我们要排的数据确是一个降序时)

         对此,快速排序有相应的优化:采用“三点取中法”,即:在待排序序列中随机选取三个元素,取中间值作为key值。这样做是为了避免选取到最大或最小值作为 key值,从而导致快排算法性能下降的问题。

        代码实现:

        详见代码内注释

void Swap(int* x, int* y)
{
	int tmp = *x;
	*x = *y;
	*y = tmp;
}

int Getmid(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[left] < a[right])
		{
			return right;
		}
		else
		{
			return left;
		}
	}
	else
	{
		if (a[mid] > a[right])
		{
			return mid;
		}
		else if (a[right] > a[left])
		{
			return left;
		}
		else
		{
			return right;
		}
	}
}

int Partsort(int* a, int left, int right)
{
	int mid = Getmid(a, left, right);//三点取中值,优化
	Swap(&a[left], &a[mid]);//将中值放到最左边

	int key = left;
	while (left < right)
	{
		//找小,注意这里是右向左靠,在最后会的一步将是靠向左边
		while (left < right && a[right] >= a[key])
		{
			--right;
		}

		//找大
		while (left < right && a[left] <= a[key])
		{
			++left;
		}

		Swap(&a[left], &a[right]);//交换大于key和小于key位置的值
	}

	Swap(&a[key], &a[left]);//此时left==right,交换key和left的值
	return left;//用于递归下一步的key的左半边以及右半边

}

void Quicksort(int* a, int begin, int end)
{
	if (begin >= end)//当下标越界时,直接退出
		return;

	int key = Partsort(a, begin, end);
	// [begin, key-1] key [key+1, end] 大致形状
	Quicksort(a, begin, key - 1);//对左key半边进行排序
	Quicksort(a, key + 1, end);//对右半边进行排序
}

        2、 挖坑法

        上来请先看一张图~

挖坑法的实现步骤如下:

  1. 设置左指针left和右指针right,以及基准元素key。

  2. 从右指针开始,向左遍历数组,直到找到第一个小于准元素key的元素,将其填入左指针所在位置的坑中,并将右指针指向左边一位。

  3. 从左指针开始,向右遍历数组,直到找到第一个大于准元素key的元素,将其填入右指针所在位置的坑中,并将左指针指向右边一位。

  4. 重复执行步骤2和步骤3,直到左指针等于右指针。

  5. 将基准元素填入最后的坑中。

  6. 对基准元素左边的子数组和右边的子数组,分别递归执行以上步骤,直到完成排序。

一些注意事项: 

  • 坑位可以是基准元素的位置,也可以是左指针或右指针的位置。

  • 在填坑的过程中,需要先将当前指针指向的元素保存起来,然后将其填入对应的坑中。

  • 在左指针等于右指针时,需要将基准元素填入最后的坑中。

代码实现:

        详见代码内注释

void Swap(int* x, int* y)
{
	int tmp = *x;
	*x = *y;
	*y = tmp;
}


int Getmid(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[left] < a[right])
		{
			return right;
		}
		else
		{
			return left;
		}
	}
	else
	{
		if (a[mid] > a[right])
		{
			return mid;
		}
		else if (a[right] > a[left])
		{
			return left;
		}
		else
		{
			return right;
		}
	}
}

int Partsort2(int* a, int left, int right)
{
	int midi = Getmid(a, left, right);
	Swap(&a[left], &a[midi]);

	int key = a[left];
	// 保存key值以后,左边形成第一个坑
	int hole = left;

	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;
	return hole;
}

void Quicksort(int* a, int begin, int end)
{
	if (begin >= end)//当下标越界时,直接退出
		return;

	int key = Partsort2(a, begin, end);
	// [begin, key-1] key [key+1, end] 大致形状
	Quicksort(a, begin, key - 1);//对左key半边进行排序
	Quicksort(a, key + 1, end);//对右半边进行排序
}

        3、前后指针法

        上来请先看一张图~

前后指针法的实现步骤如下:

  1. 选取基准数。可以选择数组的第一个元素、最后一个元素、中间元素等。

  2. 设置两个指针,一个指向数组的第一个位置,另一个指向数组的最后一个位置。

  3. 从后往前遍历,找到第一个比基准数小的数,停止遍历。

  4. 从前往后遍历,找到第一个比基准数大的数,停止遍历。

  5. 交换两个数的位置。

  6. 重复步骤3到步骤5,直到前后指针相遇。

  7. 将基准数放到前后指针相遇的位置。

  8. 对基准数左右两边的子序列分别进行快速排序。

  9. 重复以上步骤,直到数组被完全排序。

 一些注意事项: 

         在交换两个数的位置时,如果前后指针指向的数相同,那么就不能进行交换操作,因为这样会导致两个数的值发生变化。同时,在递归的过程中,需要对子序列进行边界检查,避免出现数组越界的情况。

代码实现:

        详见代码内注释

void Swap(int* x, int* y)
{
	int tmp = *x;
	*x = *y;
	*y = tmp;
}


int Getmid(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[left] < a[right])
		{
			return right;
		}
		else
		{
			return left;
		}
	}
	else
	{
		if (a[mid] > a[right])
		{
			return mid;
		}
		else if (a[right] > a[left])
		{
			return left;
		}
		else
		{
			return right;
		}
	}
}


// 前后指针
int Partsort3(int* a, int left, int right)
{
	int midi = Getmid(a, left, right);
	Swap(&a[left], &a[midi]);

	int prev = left;//前指针,从最左边开始
	int cur = prev + 1;//后指针

	int keyi = left;
	while (cur <= right)
	{
		if (a[cur] < a[keyi] && ++prev != cur)//当后指针cur找到比key小的值时,先让前指针++prev让prev到交换位置(因为原来比cur小1个位置)再判断前指针是否更cur相等,如果相等了就没有必要再交换了
		{
			Swap(&a[prev], &a[cur]);
		}

		++cur;//继续遍历找比key值小的值
	}

	Swap(&a[prev], &a[keyi]);//最后交换prev与key的值

	return prev;//此时prev在key最终位置,后续用于递归
}

void Quicksort(int* a, int begin, int end)
{
	if (begin >= end)//当下标越界时,直接退出
		return;

	int key = Partsort3(a, begin, end);
	// [begin, key-1] key [key+1, end] 大致形状
	Quicksort(a, begin, key - 1);//对左key半边进行排序
	Quicksort(a, key + 1, end);//对右半边进行排序
}


                    感谢你耐心的看到这里ღ( ´・ᴗ・` )比心,如有哪里有错误请踢一脚作者o(╥﹏╥)o! 

                                       

                                                                         给个三连再走嘛~  

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

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

相关文章

搜款网VVIC根据ID取商品详情 API 关键词搜索商品列表

搜款网是一家服装批发平台&#xff0c;提供多个品牌和供应商的服装、鞋子、箱包等商品供采购者选择&#xff0c;为了获取商品详情&#xff0c;您需要使用搜款网的API接口。 建议您联系搜款网的客服或开发者&#xff0c;以获取更多关于API接口的信息&#xff0c;包括使用方法、…

5+氧化应激+预后模型

今天给同学们分享一篇氧化应激预后模型的生信文章“A four oxidative stress gene prognostic model and integrated immunity-analysis in pancreatic adenocarcinoma”&#xff0c;这篇文章于2023年1月13日发表在Front Oncol期刊上&#xff0c;影响因子为5.738。 胰腺腺癌&am…

macOS Sonoma 14 RC2(23A344)/Ventura13.6/Monterey 12.7 三版系统同时更新

macOS Sonoma 14 RC2&#xff08;23A344&#xff09;/macOS13.6/macOS 12.7 同时更新

[论文阅读]YOLOV1:You Only Look Once:Unified, Real-Time Object Detection

摘要 我们提出了YOLO&#xff0c;一种新的目标检测方法。之前的目标检测工作重新使用分类器来执行检测。相反&#xff0c;我们将目标检测表述为空间分离的边界框和相关类概率的回归问题。单个神经网络在一次评估中直接从完整图像中预测边界框和类别概率。由于整个检测管道是一…

reduce the indexing time and cpu load with pre-build jdk shared indexes

在idea2022.3.3版本中打开项目 遇到问题 提示&#xff1a; Idea突然不停indexing reduce the indexing time and cpu load with pre-build jdk shared indexes解决方法 找到设置&#xff0c;设置成如图效果&#xff1a; 然后再找到如图位置&#xff1a; 勾选清理&#xff…

Android Version、MinSDK(API)和https的对应关系

这边主要是对Android开发过程中的各个版本对应的API Levl做个总结&#xff0c;同时分析一下Https在哪个Android版本上面做强制要求。对于Android的开发也有一定的帮助。

瑞云科技联合华为云推出云渲染AI加速模式,助力CG行业渲染效率再升级

如今&#xff0c;无论是在工业设计、建筑设计等设计行业&#xff0c;还是在影视、动画等娱乐行业&#xff0c;越来越多企业开始将目光转向云技术&#xff0c;以提升数字内容生产效率。而在数字内容制作过程中&#xff0c;渲染起着至关重要的作用&#xff0c;因此&#xff0c;提…

Qt测量屏幕的分辨率

Qt 入门实战教程&#xff08;目录&#xff09; 什么是屏幕分辨率 我们手机&#xff0c;平板&#xff0c;个人台式机显示器&#xff0c;笔记本显示器家里的液晶电视显示器在显示画面的时候&#xff0c;都是由一个一个的像素点组成的。 不同的像素点显示不同的颜色&#xff0c…

Windows和VMware中的Ubuntu互传文件

1、方法一&#xff1a;创建共享文件夹 1.1、创建环境 (1)VMware: V17.0.0 (2)Ununtu: Ubuntu 22.04.3 LTS (3)Windows: Windows10 (4)需要在Ubuntu开机状态下进行配置。 1.2、创建步骤 (1)打开VMware&#xff0c;依次点击“虚拟机”→“设置”。 (2)依次点击“选项”→“…

使用transformers进行端到端的目标检测

目录 目标检测的旧方法 使用transformers进行端到端的目标检测 抛去了目标检测旧的方法 网络架构 Transformer encoder Transformers and Parallel Decoding 注意力起到的作用 使用Hungarian algorithm算法完成匹配 在使用transformers的端到端目标检测中&#xff0c;匈…

正确理解redux Toolkits中createSlice的action.payload

使用redux Toolkits中的createSlice编写extraReducers经常看到使用action.payload来更新state状态值&#xff1a; 那么action.payload指的到底是什么&#xff1f; 让我们看看action的定义部分&#xff1a; 注意&#xff1a; action.payload不是上面ajax请求的返回内容&#x…

C#求100-999之间的水仙花数,你知道多少个?让我们一起来探索!

目录 背景: 扩展: 水仙花数例子: 效果展示:​ 总结: 背景: 水仙花数&#xff08;Narcissistic number&#xff09;也被称为超完全数字不变数&#xff08;pluperfect digital invariant, PPDI&#xff09;、自恋数、自幂数、阿姆斯壮数或阿姆斯特朗数&#xff08;Armstrong…

335. 路径交叉-数学归纳法(力扣目前c语言)

335. 路径交叉-数学归纳法 给你一个整数数组 distance 。 从 X-Y 平面上的点 (0,0) 开始&#xff0c;先向北移动 distance[0] 米&#xff0c;然后向西移动 distance[1] 米&#xff0c;向南移动 distance[2] 米&#xff0c;向东移动 distance[3] 米&#xff0c;持续移动。也就…

移动端H5封装一个 ScrollList 横向滚动列表组件,实现向左滑动

效果&#xff1a; 1.封装组件&#xff1a; <template><div class"scroll-list"><divclass"scroll-list-content":style"{ background, color, fontSize: size }"ref"scrollListContent"><div class"scroll…

子序列问题集合

子序列问题 最长公共子序列&#xff1a;最长上升子序列&#xff08;要输出序列&#xff0c;和最大长度&#xff09;1.dp2.贪心二分 导弹拦截 &#xff08;最长上升/下降子序列长度&#xff09; 最长公共子序列&#xff1a; class Solution { public://dfs(i,j)代表s串i前字符和…

C语言自定义类型详解(1)结构体知识汇总

本篇概要 本篇主要讲述C语言结构体的相关知识&#xff0c;包括结构体的基本声明&#xff0c;结构体的匿名结构&#xff0c;结构体的自引用&#xff0c;结构体变量的定义和初始化以及结构体的内存对齐等相关知识。 文章目录 本篇概要1.结构体1.1结构体的基本声明1.2结构体的特殊…

应用程序转换工具Unite mac中文版软件特点

Unite mac是一款Mac平台上的应用程序转换工具&#xff0c;它可以将任何网站或Web应用程序转换成本地应用程序&#xff0c;以便更方便地访问和使用。 Unite mac软件特点 网站转应用程序&#xff1a;该软件可以将任何网站或Web应用程序转换成本地应用程序&#xff0c;方便用户更…

AI创作工具-AI创作工具技术解读

创作是广告、文章、小说、社交媒体内容等各个领域的关键&#xff0c;但它通常需要创作者花费大量时间和精力&#xff0c;思考、编写和编辑内容。有时候&#xff0c;创作者可能面临写作灵感枯竭、时间紧迫或需要大量内容的情况。 添加图片注释&#xff0c;不超过 140 字&#xf…

Java集合--Collection、Map、List、Set、Iterator、Collections工具类

文章目录 一、Java集合框架概述二、Collection接口方法2.1、Collection子接口一&#xff1a;List2.1.1、ArrayLIst的源码分析2.1.2、LinkedList的源码分析2.1.3、List接口中的常用方法 2.2、Collection子接口二&#xff1a;Set2.2.1、Set接口的无序性2.2.2、添加元素的过程&…

Leetcode---363周赛

题目列表 2859. 计算 K 置位下标对应元素的和 2860. 让所有学生保持开心的分组方法数 2861. 最大合金数 2862. 完全子集的最大元素和 一、计算k置为下标对应元素的和 简单题&#xff0c;直接暴力模拟&#xff0c;代码如下 class Solution { public:int sumIndicesWithKS…