排序方法——《快速排序》

P. S.:以下代码均在VS2019环境下测试,不代表所有编译器均可通过。
P. S.:测试代码均未展示头文件stdio.h的声明,使用时请自行添加。

  

请添加图片描述

                                           博主主页:Yan. yan.
                                              C语言专栏
                                            数据结构专栏
                                         力扣牛客经典题目专栏

  • 一、快速排序的定义
  • 二、快速排序的递归方法实现
    • 1、hoare法
    • 2、双指针法
    • 3、挖坑法
  • 三、非递归方法实现快速排序
  • 四、快速排序的优化
    • 1、三数区中
    • 2、小区间优化
  • 五、快速排序的特性总结

一、快速排序的定义

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


  快速排序分为两种: 递归法    非递归

  递归法又分为三种: hoare法双指针法挖坑法

二、快速排序的递归方法实现

1、hoare法


  hoare法的动图展示:
请添加图片描述
  起主要思想为:循环遍历数组元素

  • 定义三个变量:key,begin 和 end,其中 key 和 begin 指向数组首元素,end 指向数组尾元素
  • begin 从右边开始寻找比 key 小的值, end 从左边开始寻找比 key 大的值,两者找到相应的值以后再将各自的值进行交换,直到二者相遇。
  • 如果begin 和 end 相遇了,那么将相遇位置的值与 key 位置的值进行交换。
  • 将交换后的key的位置记录下来作为新的分割点, 再以相同的方法递归key左边和右边的元素


  完整代码展示:
void Swap(int* a, int* b)
{
	int ret = *a;
	*a = *b;
	*b = ret;
}

//快速排序  hoare
void QuickSort(int* arr, int left, int right)
{
	if (left >= right)
		return;

	int key = left;
	int begin = left;
	int end = right;

	while (begin < end)
	{
		while (begin < end && arr[end] >= arr[key])
		{
			end--;
		}
		while (begin < end && arr[begin] <= arr[key])
		{
			begin++;
		}
		Swap(&arr[begin], &arr[end]);
	}
	Swap(&arr[begin], &arr[key]);
	key = begin;

	QuickSort(arr, left, key - 1);
	QuickSort(arr, key + 1, right);
}

2、双指针法


  双指针法的动图展示:
请添加图片描述
  其主要思想为:

  • 定义三个变量:key prve cur,对应数组下标,key 和 prve 指向首元素下标,cur 指向prve 的下一个元素下标。
  • 如果cur位置的值小于key,那么prve++,然后将prve处的值与cur位置的值进行交换,再cur++
  • 如果cur位置的值大于key,那么cur++,直到找到小于key的值
  • 如果cur已经走出了数组(cur > right)那么就让prve处的值与key处的值交换
  • 将交换后的key的位置记录下来作为新的分割点, 再以相同的方法递归key左边和右边的元素


  完整代码展示:
void Swap(int* a, int* b)
{
	int ret = *a;
	*a = *b;
	*b = ret;
}

//快速排序  双指针法
void QuickSort(int* arr, int left, int right)
{
	if (left >= right)
		return;

	int key = left;
	int prve = left;
	int cur = prve + 1;
	while (cur <= right)
	{
		if (arr[cur] <= arr[key] && ++prve != cur)//这里如果prve++后与cur在同一位置,自己与自己交换没有意义,所以多加一个判断条件
		{                                
			Swap(&arr[prve], &arr[cur]);
		}
		cur++;
	}
	Swap(&arr[prve], &arr[key]);
	key = prve;

	QuickSort(arr, left, key - 1);
	QuickSort(arr, key + 1, right);
}

3、挖坑法


  挖坑法的动图展示:
请添加图片描述
  其主要思想为:

  • 创建四个变量:hole  key begin  end 
  • hole为坑位,key等于数组首元素的值,先将key处的数值拿走,hole即在key处为坑位,end从右边开始找比key小的值,begin从左边开始找比key大的值。
  • end找到比key晓得值以后,将end的值放到hole处,然后end处就变成了新的坑位,再让begin找大,找到以后再将值放到坑位处,begin处形成新的坑位。依次循环遍历数组
  • 当begin和end相遇后就把key的值放到坑位。
  • 将交换后的key的位置记录下来作为新的分割点, 再以相同的方法递归key左边和右边的元素


  完整代码展示:
void QuickSort(int* arr, int left, int right)
{
	if (left >= right)
		return;

	int key = arr[left];
	int begin = left;
	int end = right;
	int hole = begin;
	while (begin < end)
	{
		while (begin < end && arr[end] >= key)
		{
			end--;
		}
		arr[hole] = arr[end];
		hole = end;

		while (begin < end && arr[begin] <= key)
		{
			begin++;
		}
		arr[hole] = arr[begin];
		hole = begin;
	}
	arr[hole] = key;
	hole = begin;

	QuickSort(arr, left, key - 1);
	QuickSort(arr, key + 1, right);
}

三、非递归方法实现快速排序

  该方法要运用到栈的实现: 栈的实现
  用栈的实现来模拟递归从而实现快速排序。

  其主要思想为:

  • 定义两个变量:left right;
  • 因为栈有先进后出的特点,所以数组元素入栈时要先入尾在入头。
  • 现将left和right入栈,然后再找key,这里找key可以调用前面的hoare/双指针/挖坑法的任意一个
  • 再将key位置的右左两边的数入栈。


  完整代码展示:
//快速排序非递归
void QuickSortNonR(int* arr, int left, int right)
{
	ST st;
	STInit(&st);
	STPush(&st, right);
	STPush(&st, left);
	while (!STEmpty(&st))
	{
		int begin = STTop(&st);
		STPop(&st);

		int end = STTop(&st);
		STPop(&st);

		int key = PartSort2(arr, begin, end);
		//begin key-1  key  key+1  end
		if (end > key + 1)
		{
			STPush(&st, end);
			STPush(&st, key + 1);
		}

		if (key - 1 > begin)
		{
			STPush(&st, key - 1);
			STPush(&st, begin);
		}
	}
	STDestroy(&st);
}

四、快速排序的优化

  此优化可适用于任意方法的可快速排序。

  在日常生活中,我们所使用排序功能的目标对象可能不会想日常练习中那样少,或许是一个非常庞大的数量,且其所提供的数据可能在原本基础上就有部分有序化,例如所给的大量数据中第一位为最小值或最大值,而这样的数据我们从一开始就进行快速排序的话,会让end从末尾一直遍历到数据首部或begin从数据首部一直遍历到末尾,拥有时间复杂度上的浪费,所以我们提出了一个 三数取中的方法来进行优化,且针对于大量数据,当所处理数据的子数据数量过少时,我们还是用快速排序的方法有点大材小用了,故针对于此我们也提出了 小区间优化的方法应对。

1、三数区中

  针对于所给目标数组第一位即为最小值或最大值而导致的时间复杂度浪费,我们可以取其数组首位,中位,末位三个数进行比较,选取其中处于中间位置的数,令其与数据首位进行交换。

  • 其代码如下所示:
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[right] > a[left])
			return left;
		else
			return right;
	}
	else
	{
		if (a[mid] < a[right])
			return mid;
		else if (a[right] < a[left])
			return left;
		else
			return right;
	}
}

2、小区间优化

  对于根据分界点所得到的新子数组,若其数据量较小,我们还是用快速排序的化有些大材小用,故当数据量较小时,我们一般使用其他排序方法,如冒泡排序或插入排序等,进行快速的排序。

  • 其代码如下所示:
//插入排序
void InSertSort(int* arr, int n)
{
	for (int i = 0; i < n - 1; i++)
	{
		int end = i;
		int tem = arr[end + 1];
		while (end >= 0)
		{
			if (arr[end] >= tem)
			{
				arr[end + 1] = arr[end];
				end--;
			}
			else
			{
				break;
			}
		}
		arr[end + 1] = tem;
	}
}

if ((right - left + 1) < 10)
	{
		InSertSort(arr + left, right - left + 1);
	}


快速排序的优化结束,其完整代码如下所示(前后指针法快速排序):
void Swap(int* a, int* b)
{
	int ret = *a;
	*a = *b;
	*b = ret;
}

//快速排序  hoare
void QuickSort(int* arr, int left, int right)
{
	if (left >= right)
		return;

	//小区间优化
	if (right - left < 10)
	{
		InSertSort(a, right - left + 1);
	}	

	//三数取中
	int mid = GetMid(a, left, right);
	Swap(&a[left], &a[mid]);

	int key = left;
	int begin = left;
	int end = right;

	while (begin < end)
	{
		while (begin < end && arr[end] >= arr[key])
		{
			end--;
		}
		while (begin < end && arr[begin] <= arr[key])
		{
			begin++;
		}
		Swap(&arr[begin], &arr[end]);
	}
	Swap(&arr[begin], &arr[key]);
	key = begin;

	QuickSort(arr, left, key - 1);
	QuickSort(arr, key + 1, right);
}

五、快速排序的特性总结

  快速排序的特性总结:

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

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

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

相关文章

10、系统安全及应用

1、账号安全 用户的账号是计算机使用者的身份凭证或标识&#xff0c;每个要访问系统资源的人&#xff0c;必须凭借其用户账号才能进入计算机。 1.1 系统账号清理 在Linux系统中&#xff0c;除了用户手动创建的各种账号之外&#xff0c;还包括随系统或程序安装过程而生成的其…

Java版本+企业电子招投标系统源代码+支持二开+Spring cloud +鸿鹄电子招投标系统

随着公司的迅速扩张&#xff0c;对内部采购管理的要求也随之提高。为了构建一个公平、透明、公正的采购体系&#xff0c;并有效降低采购成本&#xff0c;我们决定开发一款符合国家电子招投标法规的电子招标采购软件。该软件将提升招投标工作的公开性和透明度&#xff0c;通过电…

macOS Sequoia 开发者测试版下载和安装教程

macOS Sequoia 于 2024年6月10日在WWDC 2024 上发布&#xff0c;里面添加了AI、窗口排列、操控iPhone等功能&#xff0c;目前发布的为测试版本&#xff0c;可能很多人不知道怎么去下载安装&#xff0c;现在小编教一下大家怎么安装最新的 macOS Sequoia 开发者测试版。 下载 mac…

Qt系统相关

本文目录 1.Qt事件事件的处理标签事件鼠标事件滚轮事件按键事件定时器事件窗口事件事件派发器 2.Qt文件操作QFile的基本使用 3.Qt多线程使用线程线程锁connect的第五个参数 条件变量和信号量 4.Qt网络编程UDP SocketTCP SocketQTcpServerQTcpSocket HTTP的编写 5.QT多媒体播放音…

vivado HW_SIO_GT

描述 Xilinx的可定制LogiCORE™IP集成误码率测试仪&#xff08;IBERT&#xff09;核心 FPGA是为评估和监控千兆收发器&#xff08;GTs&#xff09;而设计的。IBERT core支持系统内串行I/O验证和调试&#xff0c;使您能够进行测量和优化 您的设计中的高速串行I/O链路。参考综合误…

【ARM Coresight Debug 系列 -- ARMv8/v9 软件实现断点地址设置】

请阅读【嵌入式开发学习必备专栏 】 文章目录 ARMv8/v8 软件设置段带你断点地址软件配置流程代码实现 ARMv8/v8 软件设置段带你 在ARMv8/9架构中&#xff0c;可以通过寄存器 DBGBVR0_EL1 设置断点。这个寄存器是一系列调试断点值寄存器中的第一个DBGBVRn_EL1&#xff0c;其中n…

Transformer结合U-Net登上Nature子刊!最新成果让精度和效率都很美丽

最近一种基于视觉Transformer改进的U-Net来检测多光谱卫星图像中甲烷排放的深度学习方法登上了Nature子刊。与传统方法相比&#xff0c;该方法可以识别更小的甲烷羽流&#xff0c;显著提高检测能力。 这类Transformer与U-Net结合的策略是一种创新的深度学习方法&#xff0c;它…

大模型生成短视频

最近看到一个开源项目可以通过AI生成短视频&#xff0c;然后尝试了下&#xff0c;感觉还不错&#xff0c;下面是具体步骤。 项目名叫moneyprinterTurbo&#xff0c;它本意是对接到Youtube&#xff0c;自动生成视频并上传到Youtube获取流量赚钱&#xff0c;所以项目名叫moneypri…

函数计时的方法

1. console 对象 可以调⽤ console 对象的 time 和 timeEnd ⽅法来对⼀段程序进⾏时间计算。例如&#xff1a; function fib(n) {if (n 0) return;let a arguments[1] || 1;let b arguments[2] || 1;[a, b] [b, a b];fib(--n, a, b); } console.time(); // 记时开始 fib…

纹理贴图必须要输入顶点坐标或纹理坐标吗

最近知识星球的一位同学,面试时被问到:纹理贴图必须要输入顶点坐标或纹理坐标吗? 他一下子被这个问题问蒙了,虽然他知道正确答案是否定的,但是说不上来理由。 这个就引出了文本提到的全屏三角形,它不需要顶点缓冲区,而是利用顶点着色器直接生成所需的顶点坐标和纹理坐标…

推荐一些企业热门的 DevOps 工具(非常详细)零基础入门到精通,收藏这一篇就够了

最近一段时间&#xff0c;我们见证了 DevOps 技术的飞速发展。当今流行且功能强大的工具可能会成为下一年度的过时工具&#xff0c;甚至可能很快被另一种工具取代。如前所述&#xff0c;作者的目的不是通过这篇文章来评判哪些工具最受欢迎或功能最全&#xff0c;而是让读者全面…

【每日一题】错误的集合

错误的集合 ✨审题&#xff1a;在一个1-n的数组中&#xff0c;会有一个元素重复&#xff0c;一个元素丢失&#xff1b;&#x1f449;目标;找到重复的元素和丢失的元素并放入一个数组中返还回去 ✨有没有想到单身狗问题的进阶版那个思路&#xff0c;找2个单身狗&#xff0c;一个…

tcp协议的延迟应答(介绍+原则),拥塞控制(拥塞窗口,网络出现拥塞时,滑动窗口的大小如何确定,慢启动,阈值)

目录 延迟应答 引入 介绍 原则 拥塞控制 引入 网络出现拥塞 引入 介绍 介绍 拥塞窗口 介绍 决定滑动窗口的大小 慢启动 介绍 为什么要有慢启动 阈值 算法 总结 延迟应答 引入 发送方一次发送更多的数据,发送效率就越高 因为要写入网卡硬件的io速度很慢,尽量…

176.二叉树:从中序与后序遍历序列构造二叉树(力扣)

代码解决 /*** Definition for a binary tree node.* struct TreeNode {* int val;* TreeNode *left;* TreeNode *right;* TreeNode() : val(0), left(nullptr), right(nullptr) {}* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}* Tre…

神卓互联内网穿透:使用超简单,拿捏

神卓互联内网穿透技术是一种能够打破内网与外网之间壁垒的创新技术。它通过一系列智能的网络协议和算法&#xff0c;实现了将企业内部网络资源安全、稳定地暴露给外部网络访问。这使得无需进行复杂的网络配置和改造&#xff0c;就能轻松实现远程办公、跨地域协作等重要应用。 神…

【第1章】Vue环境搭建

文章目录 前言一、安装Node1. 下载2. 安装3. 验证3.1 npm版本与Node.js版本3.2 验证环境 4. npm4.1 安装npm4.2 安装包4.3 全局安装包4.4 更新包4.5 删除包4.6 查看已安装的包4.7 初始化package.json 5. 国内源 二、安装Visual Studio Code1.下载2.安装3.安装Vue - Official 三…

【品质】如何培养幽默感,如何幽默的沟通与应对生活(自卑vs自信,悲观vs乐观)

【品质】如何培养幽默感&#xff0c;如何幽默和正能量的沟通与应对生活&#xff08;自卑vs自信&#xff0c;悲观vs乐观&#xff09; 文章目录 一、性格底色&#xff08;自我认知&#xff0c;世界观&#xff09;1、从悲观的底色开始2、用摆烂、自嘲的方式与世界和解 二、沟通方法…

同余式,乘法逆元,费马小定理

同余式 同余式是 数论 的基本概念之一&#xff0c;设m是给定的一个正整数&#xff0c;a、b是整数&#xff0c;若满足m| (a-b)&#xff0c;则称a与b对模m 同余 &#xff0c;记为a≡b (mod m)&#xff0c;或记为a≡b (m)。 这个式子称为模m的同余式&#xff0c;若m∤ (a-b)&…

express入门02静态资源托管

目录 1 搭建静态资源结构2 代码助手3 多目录托管4 服务器热启动总结 上一篇我们讲解了使用express搭建服务器的过程&#xff0c;服务器搭建好了之后&#xff0c;除了在地址栏里输入URL发起get请求或者post请求外&#xff0c;通常我们还需要访问静态资源&#xff0c;比如html、c…