【C语言/数据结构】排序(快速排序及多种优化|递归及非递归版本)

 🌈个人主页:秦jh__https://blog.csdn.net/qinjh_?spm=1010.2135.3001.5343
🔥 系列专栏:《数据结构》https://blog.csdn.net/qinjh_/category_12536791.html?spm=1001.2014.3001.5482

9efbcbc3d25747719da38c01b3fa9b4f.gif​​​​

目录

 交换排序

快速排序

 hoare版代码呈现

快排优化

三数取中法

 小区间优化

 挖坑法

 前后指针版本

非递归版本快排


前言

    💬 hello! 各位铁子们大家好哇。

             今日更新了快速排序的内容
    🎉 欢迎大家关注🔍点赞👍收藏⭐️留言📝

 交换排序

快速排序

快排的过程图如下: 

 hoare版代码呈现

void QuickSort(int* a, int begin,int end)
{
	if (begin >= end)
	{
		return;
	}
	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]);
	keyi = left;
	// [begin,keyi-1] keyi [keyi+1,end]
	QuickSort(a, begin, keyi - 1);
	QuickSort(a, keyi+1, end);
}

 分析:快排过程就是左边找比key大的值,右边找比key小的值,找到后就交换。直到left与right相遇,就交换keyi和left对应的值。这是单趟的,后续过程重复,可以思考二叉树的递归过程,快排递归与其相似(见下图)。

下图中,划红线的地方是容易出错的地方。 

 

 理解了前面,这里解释一下为什么相遇位置比keyi位置的值要小?

因为右边先走。

相遇有2种情况

  1.  R遇L->R没有找到比key小,一直走,直到遇到L,相遇位置是L,比key小。
  2. L遇R->R先走,找到小的停下来了,L找大,没有找到,遇到R停下来了,相遇位置是R,比key小。

如果左边做key,R先走。

如果右边做key,L先走。

快排优化

  1. 三数取中法选key
  2. 递归到小的子区间时,可以考虑使用插入排序

三数取中法

快排对于有序的数据,效率不是很高。

如上图,我们前面的快排是固定选key的,也就是左边第一幅图,效率很低。理想情况下,每一次都二分,这样效率就能提高。这时就用到三数取中法。

三数取中法指三个数里面取中间大小的数,然后将他与key交换位置,让这个中间大小的数作key。

完整代码如下: 

int GetMidi(int* a, int begin, int end)
{
	int midi = (begin + end) / 2;
	//begin end midi三个数中选中位数
	if (a[begin] < a[midi])
	{
		if (a[midi] < a[end])
			return midi;
		else if (a[begin] > a[end])
			return begin;
		else
			return end;
	}
	else
	{
		if (a[midi] > a[end])
			return midi;
		else if (a[begin] < a[end])
			return begin;
		else
			return end;
	}
}

void QuickSort(int* a, int begin, int end)
{
	if (begin >= end)
		return;

	int midi = GetMidi(a, begin, end);
	Swap(&a[midi], &a[begin]);

	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]);
	keyi = left;
	// [begin,keyi-1] keyi [keyi+1,end]
	QuickSort(a, begin, keyi - 1);
	QuickSort(a, keyi + 1, end);
}

 小区间优化

假设在理想情况下,每次递归都像二叉树那样,递归到最后面几层时,假设还剩7个数,我们还得递归7次,这样明显不好。我们就可以在最后几层时,使用其他排序方法进行。这里使用插入排序。 

完整代码如下:

void QuickSort(int* a, int begin, int end)
{
	if (begin >= end)
		return;

	if (end - begin + 1 <= 10)
	{
		InsertSort(a+begin, end - begin + 1);
	}
	else 
	{
		int midi = GetMidi(a, begin, end);
		Swap(&a[midi], &a[begin]);
		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]);
		keyi = left;
		// [begin,keyi-1] keyi [keyi+1,end]
		QuickSort(a, begin, keyi - 1);
		QuickSort(a, keyi + 1, end);
	}
}

 挖坑法

我们把不同方法的单趟排序重新弄成一个函数。

hoare版本:

//hoare版本 
int PartSort1(int* a, int begin, int end)
{
	int midi = GetMidi(a, begin, end);
	Swap(&a[midi], &a[begin]);
	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 midi = GetMidi(a, begin, end);
	Swap(&a[midi], &a[begin]);
	
	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;
} 

void QuickSort(int* a, int begin, int end)
{
	if (begin >= end)
		return;

	int keyi = PartSort2(a, begin, end);
	QuickSort(a, begin, keyi - 1);
	QuickSort(a, keyi+1, end);

}

 分析:挖坑法其实跟hoare版本比没啥提升,只不过更易理解,本质上没变。但不同的版本,单趟排序后的结果可能会不同。

 前后指针版本

分析:

  1. cur遇到比key大的值,cur++
  2. cur遇到比key小的值,++prev,交换prev和cur位置的值,++cur

代码实现

//前后指针法
int PartSort3(int* a, int begin, int end)
{
	int midi = GetMidi(a, begin, end);
	Swap(&a[midi], &a[begin]);
	int keyi = begin;

	int prev = begin;
	int cur = prev + 1;

	while (cur <= end)
	{
		if (a[cur] < a[keyi] && ++prev!=cur)
			Swap(&a[prev], &a[cur]);
		++cur;
	}
	Swap(&a[prev], &a[keyi]);
	keyi = prev;
	return keyi;
}

非递归版本快排

非递归版本的快排需要用到栈。下面给出需要的栈的函数:

void STInit(ST* pst)
{
	assert(pst);

	pst->a = NULL;
	pst->capacity = 0;
	pst->top = 0;
	//pst->top = -1;
}

void STDestroy(ST* pst)
{
	free(pst->a);
	pst->a = NULL;
	pst->capacity = pst->top = 0;

}

//栈顶插入删除
void STPush(ST* pst, STDataType x)
{
	assert(pst);
	if (pst->top == pst->capacity)
	{
		int newcapacity = pst->capacity == 0 ? 4 : pst->capacity * 2;
		STDataType* tmp = (STDataType*)realloc(pst->a, sizeof(STDataType) * newcapacity);
		if (tmp == NULL)
		{
			perror("realloc fail");
			return;
		}
		pst->a = tmp;
		pst->capacity = newcapacity;
	}
	pst->a[pst->top] = x;
	pst->top++;
}

void STPop(ST* pst)
{
	assert(pst);
	assert(pst->top > 0);
	pst->top--;
}

STDataType STTop(ST* pst)
{
	assert(pst);
	assert(pst->top > 0);
	return	pst->a[pst->top - 1];
}

bool STEmpty(ST* pst)
{
	assert(pst);
	//if (pst->top == 0)
	//{
	//	return true;
	//}
	//else
	//{
	//	return	false;
	//}
	return pst->top == 0;
}

 非递归代码实现:

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 keyi = PartSort3(a, left, right);
		// [left,keyi-1] keyi [keyi+1,right]
		
		if (keyi+1 < right)
		{
			STPush(&s, right);
			STPush(&s, keyi+1);
		}
		if (left < keyi - 1)
		{
			STPush(&s, keyi - 1);
			STPush(&s, left);
		}
	}
	STDestroy(&s);
}

分析:栈是后进先出,这里用栈是模拟递归的过程。先模拟递归左边,像二叉树递归那样,先入右边的数,再入左边,这样出的时候就先出左边的,然后就可以模拟先往左边递归了。

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

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

相关文章

前端qrcode生成二维码详解

文章目录 前言1、浏览器支持2、优点3、缺点4、相关方法5、安装及使用示例 前言 qrcode 是一个基于JavaScript的二维码生成库&#xff0c;主要是通过获取 DOM 的标签&#xff0c;再通过 HTML5 Canvas 绘制而成&#xff0c;不依赖任何库。 官方文档&#xff1a;https://www.npm…

NetExec:一款功能强大的自动化网络安全评估与漏洞测试工具

关于NetExec NetExec是一款功能强大的自动化网络安全评估与漏洞测试工具&#xff0c;该工具可以帮助广大研究人员以自动化的形式测试大型网络的安全&#xff0c;并通过利用网络服务漏洞来评估目标网络的安全态势。 支持的协议 1、SMB协议 2、LDAP协议 3、WinRM协议 4、MSSQL协…

二叉树--199. 二叉树的右视图/medium 理解度C

199. 二叉树的右视图 1、题目2、题目分析3、复杂度最优解代码示例4、适用场景 1、题目 给定一个二叉树的 根节点 root&#xff0c;想象自己站在它的右侧&#xff0c;按照从顶部到底部的顺序&#xff0c;返回从右侧所能看到的节点值。 示例 1: 输入: [1,2,3,null,5,null,4] 输出…

IT问题解答类型网站源码,多用户博客文章程序

问答网是一款为IT工程师提供的问答平台,旨在帮助用户在线获取专业知识和相关问题的答案。在问答网,用户可以轻松找到其他人的问答问题,并在这里寻求解答。如果您有任何想要解决的问题,都可以在此发布问题并得到其他同行的解答。 该平台提供多项功能,支持支付宝在线付款,…

数字艺术展厅有什么好处,搭建数字艺术展厅要注意什么

引言&#xff1a; 数字艺术展厅是一种利用数字科技手段搭建的艺术展览空间&#xff0c;通过数字化展示艺术品&#xff0c;能够为观众带来全新的艺术体验。那么数字艺术展厅有什么好处&#xff0c;搭建数字艺术展厅要注意什么呢&#xff1f; 一、数字艺术展厅的好处 1.创新艺术…

STM32连接阿里云物联网平台

文章目录 引言一、STM32连接阿里云物联网平台思路二、ESP8266烧录固件三、使用AT指令连接阿里云物联网平台四、STM32环形串口缓冲区驱动程序五、STM32连接阿里云驱动程序 引言 连续写了两篇关于阿里云连接的文章&#xff0c;都是使用Arduino ESP8266 & Arduino ESP32的方式…

MG7050VAN 基于声表的差分多输出 晶体振荡器(LVDS)

MG7050VAN的LVDS输出是一款差分多输出晶体振荡器&#xff0c;具有极低的抖动和超强的稳定性。该振荡器适用于多种应用场景&#xff0c;如服务器、存储器和网络仪器等。作为一款高端的LVDS输出设备&#xff0c;该振荡器在输出性能上具有明显优势。其超低抖动水平可以达到0.3ps M…

子窗口的qss应该放在,它构造函数里

子窗口 构造函数 加载qss QFile qss(QString(":/simulator/resources/main_theme.qss")); if (qss.open(QIODevice::ReadOnly)) {auto content qss.readAll();this->setStyleSheet(content);qss.close(); }黑色样式 放在main_theme.qss #init_scene{ backgro…

NPDP证书:让你的职业生涯飞升!

&#x1f31f;没错&#xff01;NPDP证书正在成为产品经理们的“新宠”&#xff01;越来越多的同行们纷纷选择考取NPDP证书&#xff0c;为什么这么火爆&#xff1f;一起来探究下吧&#xff01; &#x1f680;NPDP认证&#xff1a;产品经理的国际通行证 &#x1f4cd;NPDP&#x…

Java 数据结构篇-实现二叉搜索树的核心方法

&#x1f525;博客主页&#xff1a; 【小扳_-CSDN博客】 ❤感谢大家点赞&#x1f44d;收藏⭐评论✍ 文章目录 1.0 二叉搜索树的概述 2.0 二叉搜索树的成员变量及其构造方法 3.0 实现二叉树的核心接口 3.1 实现二叉搜索树 - 获取值 get(int key) 3.2 实现二叉搜索树 - 获取最小…

MyBatis框架-ResultMap

文章目录 ResultMapsqlUser.java解决方案**方案一&#xff1a;为列名指定别名 , 别名和java实体类的属性名一致(自动映射)**UserMapper.xmlUserTest.java测试结果 **方案二&#xff1a;使用结果集映射->ResultMap 【推荐】&#xff08;手动映射&#xff09;**测试结果 Resul…

linux安装 黑方容灾备份与恢复系统软件v6.0 代理

1.环境准备 1.1硬件环境 内存>4G&#xff0c;cpu最低双核 1.2把SElinux状态改为Disabled &#xff08;1&#xff09;查看SElinux状态 输入getenforce命令 SELinux共有3个状态&#xff1a; enforcing &#xff08;执行中&#xff09;、permissive &#xff08;不执行但…

如何在Win系统安装Jupyter Notbook并实现无公网ip远程访问本地笔记

文章目录 1.前言2.Jupyter Notebook的安装2.1 Jupyter Notebook下载安装2.2 Jupyter Notebook的配置2.3 Cpolar下载安装 3.Cpolar端口设置3.1 Cpolar云端设置3.2.Cpolar本地设置 4.公网访问测试5.结语 1.前言 在数据分析工作中&#xff0c;使用最多的无疑就是各种函数、图表、…

YoloV8改进策略:Conv改进|DCNv4最新实践|高效涨点|多种改进教程|完整论文翻译

摘要 涨点效果:在我自己的数据集上,mAP50 由0.986涨到了0.993,mAP50-95由0.737涨到0.77,涨点明显! DCNv4是可变形卷积的第四版,速度和v3相比有了大幅度的提升,但是环境搭建有一定的难度,对新手不太友好。如果在使用过程遇到编译的问题,请严格按照我写的环境配置。 …

论文笔记:TimeGPT-1

时间序列的第一个基础大模型 1 方法 最basic的Transformer架构 采用了公开可用的最大时间序列数据集进行训练&#xff0c;包含超过1000亿个数据点。 训练集涵盖了来自金融、经济、人口统计、医疗保健、天气、物联网传感器数据、能源、网络流量、销售、交通和银行业等广泛领域…

自定义BeanNameGenerator生成规则

通过点进ComponentScan注解进入源码可以看到 追随BeanNameGenerator进入源码可以看到该类是个借口且只有一个方法 点击上面黑色箭头出现两个实现方法 点击第一个方法 进入determineBeanNameFromAnnotation方法中 通过上诉自定义一个生成beanName方法 先创建一个CustomeBeanN…

Docker本地部署APITable结合内网穿透实现公网访问

文章目录 前言1. 部署APITable2. cpolar的安装和注册3. 配置APITable公网访问地址4. 固定APITable公网地址 前言 vika维格表作为新一代数据生产力平台&#xff0c;是一款面向 API 的智能多维表格。它将复杂的可视化数据库、电子表格、实时在线协同、低代码开发技术四合为一&am…

QT+VS实现Kmeans++

1、Kmeans的原理如下&#xff1a; &#xff08;1&#xff09;首先选取样本中任一数据点作为第一个聚类中心&#xff1b; &#xff08;2&#xff09;计算样本每一个数据点至现所有聚类中心的最近距离&#xff0c;并记录下来&#xff1b; &#xff08;3&#xff09;逐一挑选所…

【C++】引用、内联函数、auto关键字等

前言&#xff1a;在前面我们讲解了C入门基础的一些学习例如命名空间、缺省参数、函数重载等。今天我们将进一步的学习&#xff0c;跟着博主的脚步再次往前迈一步吧。 &#x1f496; 博主CSDN主页:卫卫卫的个人主页 &#x1f49e; &#x1f449; 专栏分类:高质量&#xff23;学习…

SpringBoot 源码解析 - 持续更新

开始 spring initilizer&#xff1a;根据依赖构建工具、springboot 版本等生成 Java 工程。手把手教你手写一个最简单的 Spring Boot Starter Starter 命名规则 Spring 官方定义的 Starter 通常命名遵循的格式为 spring-boot-starter-{name}&#xff0c;例如 spring-boot-star…