快排(快速排序)的递归与非递归实现(文末附完整代码)

快排有几种不同的写法,下面一一来介绍并实现。其中又分为递归和非递归的写法,但大体思路相同,只是代码实现略有不同。(注:文章中的完整代码中,Swap()函数均省略未写,记得自己补充)

递归写法

递归的写法类似于二叉树的前序遍历,先数组本身排序,再递归左右两个区间,直至将无序数组变为有序。

hoare版本

霍尔版本是快排的提出者,也就是最开始的写法。

其基于分治的思想,在数组中选中一个值作为基准(keyi),利用这个值(a[keyi]),进行排序,将待排序元素分为两份,比a[keyi]小的值在其左侧,比a[keyi]大的值在其右侧。此时a[keyi]这个值就排好序了,然后用递归的方法对左右两侧进行重复操作,直至无序数组变为有序。

一次排序过程如下:

其中,还有一些小问题需要注意:

代码实现为:

// 快速排序hoare版本
int PartSort(int* a, int left, int right)
{
	int keyi = left;
	int begin = left, end = right;
	while (begin < end)
	{
		//右边找小
		while (begin < end && a[end] >= a[keyi])
		{
			end--;
		}

		//左边找大
		while (begin < end && a[begin] <= a[keyi])
		{
			begin++;
		}
		
		Swap(&a[begin], &a[end]);
	}

	Swap(&a[keyi], &a[begin]);
	keyi = begin;

	return keyi;
}

void QuickSort(int* a, int left, int right)
{
	if (left >= right)
	{
		return;
	}

	int keyi = PartSort(a, left, right);

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

挖坑法

挖坑法的思想是选中一个坑位(其值记为tmp,坑位的起始位置为begin),然后从右边找比tmp小的值,填入其中(此时要begin++),然后从左边找比tmp大的值填入新的坑位(此时坑位在end处,填入后要end--)。

最后begin和end相遇时,将tmp的值填入。

代码实现为:

// 挖坑法
int PartSort(int* a, int left, int right)
{
	int tmp = a[left];
	int begin = left, end = right;
	while (begin < end)
	{
		while (begin < end && a[end] >= tmp)
		{
			end--;
		}
		a[begin] = a[end];

		if (begin >= end)
		{
			break;
		}
		begin++;

		while (begin < end && a[begin] <= tmp)
		{
			begin++;
		}
		a[end] = a[begin];

		if (begin >= end)
		{
			break;
		}
		end--;
	}
	a[begin] = tmp;

	return begin;
}

void QuickSort(int* a, int left, int right)
{
	if (left >= right)
	{
		return;
	}

	int keyi = PartSort(a, left, right);

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

写的这个版本看起来有些长,原因是我在写时,没有再用一个变量来记录坑位的位置,我们可以再添加一个变量来记录坑位的位置。

// 挖坑法
int PartSort(int* a, int left, int right)
{
	int tmp = a[left];
	int begin = left, end = right;
	int key = begin;

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

		a[key] = a[end];
		key = end;


		while (begin < end && a[begin] <= tmp)
		{
			begin++;
		}

		a[key] = a[begin];
		key = begin;
	}

	a[key] = tmp;
	return key;
}

void QuickSort(int* a, int left, int right)
{
	if (left >= right)
	{
		return;
	}

	int keyi = PartSort(a, left, right);

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

一趟排序过程如下:

双指针

双指针法的中心思想是利用 prevcur 两个指针来操作。

 具体代码实现如下:

// 快速排序前后指针法
int PartSort3(int* a, int left, int right)
{
	int keyi = left;
	int prev = left, cur = left + 1;
	while (cur <= right)
	{
		//这里如果a[keyi] > a[cur],就会判断++prev != cur,
		//prev就会加一,只有prev和cur所在位置不同时才会发生交换。
		if (a[keyi] > a[cur] && ++prev != cur)
		{
			Swap(&a[prev], &a[cur]);
		}
		cur++;
	}

	//最后交换prev位置和keyi位置的值
	Swap(&a[prev], &a[keyi]);
	keyi = prev;

	return keyi;
}

void QuickSort(int* a, int left, int right)
{
	if (left >= right)
	{
		return;
	}

	int keyi = PartSort3(a, left, right);

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

非递归

有时候,递归深度太深会导致栈溢出,所以我们有时要用非递归来实现快排,非递归实现快排,我们就需要借助栈来实现。其思想就是模拟递归的过程,并用循环来替代,循环每走一次,就相当于递归了一次。所以要用栈来记录每次要排序的区间(也就是每次递归的 leftright )。栈的实现详解(点这里)。

// 快速排序hoare版本
int PartSort(int* a, int left, int right)
{
	int keyi = left;
	int begin = left, end = right;
	while (begin < end)
	{
		//右边找小
		while (begin < end && a[end] >= a[keyi])
		{
			end--;
		}

		//左边找大
		while (begin < end && a[begin] <= a[keyi])
		{
			begin++;
		}
		
		Swap(&a[begin], &a[end]);
	}

	Swap(&a[keyi], &a[begin]);
	keyi = begin;

	return keyi;
}

#include "Stacktest.h"

// 快速排序 非递归实现
void QuickSortNonR(int* a, int left, int right)
{
	stack st;
	StackInit(&st);

    //先入右,再入左
	StackPush(&st, right);
	StackPush(&st, left);

	while (!StackEmpty(&st))
	{
		int begin = StackTop(&st);
		StackPop(&st);

		int end = StackTop(&st);
		StackPop(&st);

        //排序
		int keyi = PartSort(a, begin, end);

		if (keyi + 1 < end)
		{
			StackPush(&st, end);
			StackPush(&st, keyi + 1);
		}

		if (begin < keyi - 1)
		{
			StackPush(&st, keyi - 1);
			StackPush(&st, begin);
		}
	}

	StackDestory(&st);
}

具体过程如下:

优化

快排中 keyi 的选取是十分最重要的,快排的时间复杂度为O(N*logN),但其最坏情况下为O(N^2),如果基准值是数组中最大或最小的数值,则快速排序的递归深度会非常深,排序效率会很低。若是一个有序数组使用快速排序,则递归深度为n,单趟排序也为n,此时时间复杂度为O(N^2),为了避免最坏情况的发生,我们通常是在数组中随意选择一个数作为基准。这里有几种方法:随机数法、取中间位置和三数取中法。

随机数法

随机选一个,有概率选到最大值或最小值。

#include<stdlib.h>
#include<time.h>

int GetNumber(int* a, int left, int right)
{
	return rand() % (right - left + 1) + left;
}

// 快速排序hoare版本
int PartSort1(int* a, int left, int right)
{
	int n = GetNumber(a, left, right);
	Swap(&a[left], &a[n]);

	int keyi = left;
	int begin = left, end = right;
	while (begin < end)
	{
		//右边找小
		while (begin < end && a[end] >= a[keyi])
		{
			end--;
		}

		//左边找大
		while (begin < end && a[begin] <= a[keyi])
		{
			begin++;
		}
		
		Swap(&a[begin], &a[end]);
	}

	Swap(&a[keyi], &a[begin]);
	keyi = begin;

	return keyi;
}

void QuickSort(int* a, int left, int right)
{
	if (left >= right)
	{
		return;
	}

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

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

int main()
{
	srand((unsigned int)time(NULL));
	int arr[] = { 6,5,7,9,2,0,3,1,8,4,10 };
	int len = sizeof(arr) / sizeof(int);
	QuickSort(arr, 0, len - 1);
    //打印数组
	Print(arr, len);
	return 0;
}

取中间位置的数

取中间元素位置,也有可能选到最大值或最小值。

​
​
#include<stdlib.h>
#include<time.h>

int GetNumber(int* a, int left, int right)
{
	return (right + left) / 2;
}

// 快速排序hoare版本
int PartSort1(int* a, int left, int right)
{
	int n = GetNumber(a, left, right);
	Swap(&a[left], &a[n]);

	int keyi = left;
	int begin = left, end = right;
	while (begin < end)
	{
		//右边找小
		while (begin < end && a[end] >= a[keyi])
		{
			end--;
		}

		//左边找大
		while (begin < end && a[begin] <= a[keyi])
		{
			begin++;
		}
		
		Swap(&a[begin], &a[end]);
	}

	Swap(&a[keyi], &a[begin]);
	keyi = begin;

	return keyi;
}

void QuickSort(int* a, int left, int right)
{
	if (left >= right)
	{
		return;
	}

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

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

int main()
{
	srand((unsigned int)time(NULL));
	int arr[] = { 6,5,7,9,2,0,3,1,8,4,10 };
	int len = sizeof(arr) / sizeof(int);
	QuickSort(arr, 0, len - 1);
    //打印数组
	Print(arr, len);
	return 0;
}

三数取中法

三数取中法是指比较最左边、最右边和中间元素的大小选出折中值。不会选到最大值或最小值。

​
​
​
#include<stdlib.h>
#include<time.h>

int GetNumber(int* a, int left, int right)
{
	int mid = (right + left) / 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 //a[mid] <= a[left]
	{
		if (a[mid] > a[right])
		{
			return mid;
		}
		else if (a[left] < a[right])
		{
			return left;
		}
		else
		{
			return right;
		}
	}
}

// 快速排序hoare版本
int PartSort1(int* a, int left, int right)
{
	int n = GetNumber(a, left, right);
	Swap(&a[left], &a[n]);

	int keyi = left;
	int begin = left, end = right;
	while (begin < end)
	{
		//右边找小
		while (begin < end && a[end] >= a[keyi])
		{
			end--;
		}

		//左边找大
		while (begin < end && a[begin] <= a[keyi])
		{
			begin++;
		}
		
		Swap(&a[begin], &a[end]);
	}

	Swap(&a[keyi], &a[begin]);
	keyi = begin;

	return keyi;
}

void QuickSort(int* a, int left, int right)
{
	if (left >= right)
	{
		return;
	}

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

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

小区间优化

递归不断的拆分左右区间,越往深递归,所耗费的时间就越多,当递归至区间中元素个数为个位数字时,使用快排反而降低了效率,这是我们可以考虑使用插入排序来进行小区间优化。

void QuickSort(int* a, int left, int right)
{
	if (left >= right)
	{
		return;
	}

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

	else
	{
		int keyi = PartSort1(a, left, right);

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

完整代码

#include<stdio.h>
#include<stdlib.h>
#include<time.h>

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

void Print(int* arr, int n)
{
	for (int i = 0; i < n; i++)
	{
		printf("%d ", arr[i]);
	}
	printf("\n");
}

//插入排序
void InsertSort(int* a, int n)
{
	for (int i = 1; i < n - 1; i++)
	{
		int end = i;
		int tmp = a[end + 1];
		while (end >= 0)
		{
			if (tmp < a[end])
			{
				a[end + 1] = a[end];
				end--;
			}
			else
			{
				break;
			}
		}
		a[end + 1] = tmp;
	}
}

// 快速排序递归实现

int GetNumber(int* a, int left, int right)
{
	//return (right - left) / 2;

	//return rand() % (right - left + 1) + left;

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

// 快速排序hoare版本
int PartSort1(int* a, int left, int right)
{
	int n = GetNumber(a, left, right);
	Swap(&a[left], &a[n]);
	int keyi = left;
	int begin = left, end = right;
	while (begin < end)
	{
		//右边找小
		while (begin < end && a[end] >= a[keyi])
		{
			end--;
		}

		//左边找大
		while (begin < end && a[begin] <= a[keyi])
		{
			begin++;
		}
		
		Swap(&a[begin], &a[end]);
	}

	Swap(&a[keyi], &a[begin]);
	keyi = begin;

	return keyi;
}

// 快速排序挖坑法
int PartSort2(int* a, int left, int right)
{
	int tmp = a[left];
	int begin = left, end = right;
	int key = begin;
	while (begin < end)
	{
		while (begin < end && a[end] >= tmp)
		{
			end--;
		}

		/*a[begin] = a[end];

		if (begin >= end)
		{
			break;
		}
		begin++;*/

		a[key] = a[end];
		key = end;


		while (begin < end && a[begin] <= tmp)
		{
			begin++;
		}

		/*a[end] = a[begin];

		if (begin >= end)
		{
			break;
		}

		end--;*/
		a[key] = a[begin];
		key = begin;
	}
	a[key] = tmp;
	return key;
}

// 快速排序前后指针法
int PartSort3(int* a, int left, int right)
{
	int keyi = left;
	int prev = left, cur = left + 1;
	while (cur <= right)
	{
		//这里如果a[keyi] > a[cur],就会判断++prev != cur,
		//prev就会加一,只有prev和cur所在位置不同时才会发生交换。
		if (a[keyi] > a[cur] && ++prev != cur)
		{
			Swap(&a[prev], &a[cur]);
		}
		cur++;
	}

	//最后交换prev位置和keyi位置的值
	Swap(&a[prev], &a[keyi]);
	keyi = prev;

	return keyi;
}

void QuickSort(int* a, int left, int right)
{
	if (left >= right)
	{
		return;
	}

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

	else
	{
		int keyi = PartSort1(a, left, right);

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

#include "Stacktest.h"

// 快速排序 非递归实现
void QuickSortNonR(int* a, int left, int right)
{
	stack st;
	StackInit(&st);

	StackPush(&st, right);
	StackPush(&st, left);

	while (!StackEmpty(&st))
	{
		int begin = StackTop(&st);
		StackPop(&st);

		int end = StackTop(&st);
		StackPop(&st);

		int keyi = PartSort1(a, begin, end);

		if (keyi + 1 < end)
		{
			StackPush(&st, end);
			StackPush(&st, keyi + 1);
		}

		if (begin < keyi - 1)
		{
			StackPush(&st, keyi - 1);
			StackPush(&st, begin);
		}
	}

	StackDestory(&st);
}

int main()
{
	srand((unsigned int)time(NULL));
	int arr[] = { 6,5,7,9,2,0,3,1,8,4,10 };
	int len = sizeof(arr) / sizeof(int);
	QuickSort(arr, 0, len - 1);
	Print(arr, len);
	return 0;
}

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

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

相关文章

手写kNN算法的实现-用欧几里德空间来度量距离

kNN的算法思路&#xff1a;找K个离预测点最近的点&#xff0c;然后让它们进行投票决定预测点的类型。 step 1: kNN存储样本点的特征数据和标签数据step 2: 计算预测点到所有样本点的距离&#xff0c;关于这个距离&#xff0c;我们用欧几里德距离来度量&#xff08;其实还有很多…

LangChain4j实战

基础 LangChain4j模型适配: Provider Native Image Sync Completion Streaming Completion Embedding Image Generation Scoring Function Calling OpenAI ✅ ✅ ✅ ✅ ✅ ✅ Azure OpenAI ✅ ✅ ✅ ✅ ✅ Hugging Face ✅ ✅ Amazon Bedrock ✅ ✅…

UltraScale+系列模块化仪器,可以同时用作控制器、算法加速器和高速数字信号处理器

基于 XCZU7EG / XCZU4EG / XCZU2EG • 灵活的模块组合 • 易于嵌入的紧凑型外观结构 • 高性能的 ARM Cortex 处理器 • 成熟的 FPGA 可编程逻辑 &#xff0c;基于 IP 核的软件库 基于 Xilinx Zynq UltraScaleMPSoC 的 FPGA 技术&#xff0c;采用 Xilinx Zynq UltraScale&a…

陆面生态水文模拟与多源遥感数据同化技术

原文链接&#xff1a;陆面生态水文模拟与多源遥感数据同化技术 了解陆表过程的主要研究内容以及陆面模型在生态水文研究中的地位和作用;熟悉模 型的发展历程&#xff0c;常见模型及各自特点;理解Noah-MP模型的原理&#xff0c;掌握Noah-MP 模型在单 站和区域的模拟、模拟结果的…

华为坤灵路由器配置SSH

配置SSH服务器的管理网口IP地址。 <HUAWEI> system-view [HUAWEI] sysname SSH Server [SSH Server] interface meth 0/0/0 [SSH Server-MEth0/0/0] ip address 10.248.103.194 255.255.255.0 [SSH Server-MEth0/0/0] quit 在SSH服务器端生成本地密钥对。 [SSH Server…

C语言:定义和使用结构体变量

定义和使用结构体变量 介绍基础用法1.定义结构体2. 声明结构体变量3. 初始化和访问结构体成员4. 使用指针访问结构体成员5. 使用结构体数组 高级用法6. 嵌套结构体7. 匿名结构体8. 结构体和动态内存分配9. 结构体作为函数参数按值传递按引用传递 介绍 在C语言中&#xff0c;结…

统信UOS1070上配置文件管理器默认属性01

原文链接&#xff1a;统信UOS 1070上配置文件管理器默认属性01 Hello&#xff0c;大家好啊&#xff01;今天给大家带来一篇关于在统信UOS 1070上配置文件管理器默认属性的文章。文件管理器是我们日常操作系统使用中非常重要的工具&#xff0c;了解如何配置其默认属性可以极大地…

最新下载:PDFFactoryFinePrint【软件附加安装教程】

简介&#xff1a; pdfFactory是一款无须 Acrobat 创建 Adobe pdf 文件的打印机驱动程序&#xff0c; 提供的创建 PDF 文件的方法比其他方法更方便和高效。 pdfFactory 支持从所有应用程序轻松、可靠地创建 PDF 文件。 支持将单页或两页的文档&#xff0c;直接打印为PDF文件&a…

python 多任务之多线程

多线程 线程是程序执行的最小单位&#xff0c;实际上进程只负责分配资源&#xff0c;而利用这些资源执行程序的是线程&#xff0c;也就是说进程是线程的容器&#xff0c;一个进程中最少有一个线程来负责执行程序&#xff0c;它可以与同属一个进程的其它线程共享进程所拥有的全…

基于Simulink的双端行波测距

1 输电线路故障仿真模型 基于双端行波测距理论&#xff0c;在MATLAB软件中搭建的三相50Hz的输电线路故障仿真模型如图1所示&#xff0c;该模型包含了三相电源、输电线路、故障发生器和示波器模块等。主要仿真参数设置如下:仿真时间为 0~0.1s,采用固定步长 10-7和ode3 算法&…

为什么Kubernetes(K8S)弃用Docker:深度解析与未来展望

为什么Kubernetes弃用Docker&#xff1a;深度解析与未来展望 &#x1f680; 为什么Kubernetes弃用Docker&#xff1a;深度解析与未来展望摘要引言正文内容&#xff08;详细介绍&#xff09;什么是 Kubernetes&#xff1f;什么是 Docker&#xff1f;Kubernetes 和 Docker 的关系…

小柴带你学AutoSar系列一、基础知识篇(5)makefile基础

Flechazohttps://www.zhihu.com/people/jiu_sheng 小柴带你学AutoSar总目录https://blog.csdn.net/qianshang52013/article/details/138140235?spm=1001.2014.3001.5501

动态IP在云计算中的应用与优势(短效IP的作用)

一、云计算概述 云计算是指通过互联网将计算资源和服务提供给用户的一种模式。它具有高灵活性、可扩展性和成本效益等特点&#xff0c;使得企业能够快速响应市场变化&#xff0c;降低IT投入成本。云计算的核心优势在于其资源的动态分配和高效利用。 二、动态IP在云计算中的角…

nodejs最新某东h5st(4.7.2)参数分析与javascript逆向纯算法还原(含算法源码)(2024-06-09)

一、作者声明&#xff1a; 文章仅供学习交流与参考&#xff01;严禁用于任何商业与非法用途&#xff01;否则由此产生的一切后果均与作者无关&#xff01;如有侵权&#xff0c;请联系作者本人进行删除&#xff01; 二 、写在前面 h5st从4.1一路更新到4.7.2&#xff0c;逐渐vmp…

电子电气架构 ---车载安全防火墙

我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 屏蔽力是信息过载时代一个人的特殊竞争力,任何消耗你的人和事,多看一眼都是你的不对。非必要不费力证明自己,无利益不试图说服别人,是精神上的节…

gdb 【Linux】

程序发布方式&#xff1a;  1、debug版本&#xff1a;程序会被加入调试信息&#xff0c;以便于进行调试。  2、release版本&#xff1a;不添加任何调试信息&#xff0c;是不可调试   确定一个可执行程序是debug&#xff0c;还是release [cxqiZ7xviiy0goapxtblgih6oZ test_g…

1.2-自然语言的分布式表示-基于计数的方法

本篇笔记对应的视频链接为&#xff1a; 3-基于计数的方法表示单词-将文字转换成编号的预处理工作_哔哩哔哩_bilibili&#xff1b;4-基于计数的方法表示单词-使用共现矩阵进行单词的分布式表示_哔哩哔哩_bilibili&#xff1b;5-基于计数的方法表示单词-单词之间相似度计算_哔哩哔…

都怪我当初没有好好了解你,Java虚拟机(JVM)

初始JVM JVM本质是一个运行在计算机上的程序&#xff0c;作用是运行Java字节码文件。 下面是它的运行流程&#xff1a; 看完上述运行过程&#xff0c;现在提出一个问题&#xff1a;Java是编译型语言还是解释型语言&#xff1f; 这里先补充什么是编译&#xff0c;什么是解释&am…

泛微开发修炼之旅--13通过Ecology拦截器(注解的方式),拦截后端接口,实现接口执行成功后或执行前操作源码示例

文章链接&#xff1a;泛微开发修炼之旅--13通过Ecology拦截器(注解的方式)&#xff0c;拦截后端接口&#xff0c;实现接口执行成功后或执行前操作源码示例

Codeforces Round 949 (Div. 2) A~D

A. Turtle and Piggy Are Playing a Game &#xff08;思维&#xff09; 题意&#xff1a; 给出一个整数 x x x &#xff0c;使得 l ≤ x ≤ r l \le x \le r l≤x≤r &#xff0c;其中 l , r l, r l,r 为给定值。同时保证 2 l ≤ r 2l \le r 2l≤r 。 执行以下操作&…