124.【C语言】数据结构之快速排序的小区间优化和非递归的解决方法

目录

1.小区间优化

测试代码

运行结果

2.非递归的解决方法(重要!)

递归产生的问题

一般来说,递归改非递归有两种方法

算法分析

递归产生的二叉树

栈的示意图

先写代码框架

再填写细节部分


1.小区间优化

回顾121.【C语言】数据结构之快速排序(未优化的Hoare排序存在的问题)以及时间复杂度的分析文章的一般情况下快排的时间复杂度

类似二叉树, 下面分析二叉树的特点

注意到二叉树的最后一层节点个数为2^{n-1}近似占总节点个数2^n-1的一半(有关二叉树计算方面的知识参见100.【C语言】数据结构之二叉树的基本知识文章),因此越往二叉树的下方,递归的次数越多, 一般情况下快排递归近似二叉树,如果能适当减少递归的次数,可以提高效率

解决掉最后一层能减少一半递归次数,解决掉最后三层能减少\frac{1}{2}+ \frac{1}{4}+ \frac{1}{8} =87.5\%的递归次数!!

可以想到一个方法:越往二叉树的下方,子数组的元素个数越少,不用递归,用其他的排序方法

要注意到递归到小区间的特点,数组的大部分元素是有序的(即局部有序),选择合适的排序方法要利用好这一特点

目前讲过的排序方法有:

1.冒泡排序:118.【C语言】数据结构之排序(堆排序和冒泡排序) 点我跳转

2.选择排序:117.【C语言】数据结构之排序(选择排序) 点我跳转

3.直接插入排序:112.【C语言】数据结构之排序(详解插入排序) 点我跳转

4.希尔排序:115.【C语言】数据结构之排序(希尔排序) 点我跳转

5.堆排序:118.【C语言】数据结构之排序(堆排序和冒泡排序) 点我跳转

下面讲如何选择:

1.首先排除堆排序,堆排序要先建堆,耽误时间

2.再排除希尔排序,希尔排序是对直接插入排序的优化,由于数组的元素个数较少,没有必要使用希尔排序

3.再排除选择排序:无论最好还是最坏情况,时间复杂度都为O(N^2),而且当是小区间排序时数组的大部分元素是有序的,没有商量的余地,时间复杂度还是O(N^2)

4.冒泡排序 VS 直接插入排序

冒泡排序,最坏情况时间复杂度为O(N^2),最好情况(有序)时间复杂度为O(N)

直接插入排序,最坏情况时间复杂度为O(N^2),最好情况(有序)时间复杂度为O(N)

从时间复杂度上比较,看起来两者没有区别,但是要依据数组的实际情况来看这个问题!

从小区间的特点(数组的大部分元素是有序的(即局部有序:小段小段有序))上来看,选直接插入排序较好,原因:插入排序利用了局部有序这一特点,但是冒泡排序只是机械地将数组中最大、次大......的数依次移动,没有利用这一特点,所以直接插入排序较好

当然也可以稍加改造116.【C语言】测试排序性能的模板代码文章的测试性能的代码来比较冒泡排序和直接插入排序

例如区间长度大于10使用Hoare排序,区间长度小于等于10使用冒泡排序和直接插入排序

测试代码

void QuickSort_Hoare(int* arr, int left, int right)
{
	if (left >= right)
		return;
	if ((right - left + 1) <= 10)//添加这行代码
		return;
	//单趟快速排序
	int begin = left;
	int end = right;
	//随机选key
	srand((unsigned int)time(0));
	int rand_i = left+rand() % (right - left);
	Swap(&arr[left], &arr[rand_i]);
	
	int ret = GetMiddleNum(left, right, arr);
	if (left != ret)
	{
		Swap(&arr[left], &arr[ret]);
	}
	int key_i = left;
	while (left < right)
	{
		//由于key_i==left,因此right指针先走
		//右边找小
		while (left < right && arr[right] >= arr[key_i])
		{
			right--;
		}

		//左边找大
		while (left < right && arr[left] <= arr[key_i])
		{
			left++;
		}

		Swap(&arr[left], &arr[right]);
	}
	Swap(&arr[key_i], &arr[left]);
	key_i = left;//arr[key_i]和arr[left]交换后下标要改变,否则会对下次递归产生不利结果

	QuickSort_Hoare(arr, begin, key_i - 1);
	QuickSort_Hoare(arr, key_i + 1,end);
}

void TestTime()
{
	const int N = 500000;
	int* a1 = (int*)malloc(sizeof(int) * N);
	if (a1 == NULL)
	{
		perror("malloc");
		return;
	}
	int* a2 = (int*)malloc(sizeof(int) * N);
	if (a2 == NULL)
	{
		perror("malloc");
		return;
	}

	for (int i = 0; i < N; i++)
	{
		a1[i] = rand();
	}
	QuickSort_Hoare(a1, 0, N - 1);
	for (int i = 0; i < N; i++)
	{
		a2[i] = a1[i];
	}
	printf("区间长度大于10的排序完毕\n");
	clock_t begin1 = clock();
	BubbleSort(a1, N);//BubbleSort函数代码省略
	clock_t end1 = clock();
	printf("BubbleSort's time=%ldms\n", end1 - begin1);
	clock_t begin2 = clock();
	InsertSort(a2, N);//InsertSort函数代码省略
	clock_t end2 = clock();
	printf("InsertSort's time=%ldms\n", end2 - begin2);

	free(a1);
	free(a2);
}

int main()
{
	TestTime();
	return 0;
}

运行结果

从运行的时间上看,插入排序占很大的优势!

则Hoare排序代码应该修改为

void QuickSort_Hoare(int* arr, int left, int right)
{
	if (left >= right)
		return;
    //小区间直接插入排序
	if ((right - left + 1) > 10)
	{
		//单趟快速排序
		int begin = left;
		int end = right;
		//随机选key
		srand((unsigned int)time(0));
		int rand_i = left + rand() % (right - left);
		Swap(&arr[left], &arr[rand_i]);

		int ret = GetMiddleNum(left, right, arr);
		if (left != ret)
		{
			Swap(&arr[left], &arr[ret]);
		}
		int key_i = left;
		while (left < right)
		{
			//由于key_i==left,因此right指针先走
			//右边找小
			while (left < right && arr[right] >= arr[key_i])
			{
				right--;
			}

			//左边找大
			while (left < right && arr[left] <= arr[key_i])
			{
				left++;
			}

			Swap(&arr[left], &arr[right]);
		}
		Swap(&arr[key_i], &arr[left]);
		key_i = left;//arr[key_i]和arr[left]交换后下标要改变,否则会对下次递归产生不利结果

		QuickSort_Hoare(arr, begin, key_i - 1);
		QuickSort_Hoare(arr, key_i + 1, end);
	}
	else
	{
		InsertSort(arr+left, right - left + 1);
	}
}

注意InsertSort(arr+left, right - left + 1);的写法!!

长度小于等于10的区间不一定都在数组的两端,可能在中间,因此需要提供该区间第一个元素的下标arr+left,一共(right-left+1)各元素,由于是闭区间注意一定要+1!

2.非递归的解决方法(重要!)

递归产生的问题

1.效率(这个影响较小)

2.深度太深,栈会溢出!(严重的问题)

一般来说,递归改非递归有两种方法

1.改循环,可以参见L25.【LeetCode笔记】 三步问题的四种解法(含矩阵精彩解法!)文章和35.【C语言】详解函数递归

2.使用辅助改成循环(★)

快速排序算法复杂,改非递归需要用到

回顾有关栈的一系列操作

参见99.【C语言】数据结构之栈(含栈的源码)文章

算法分析

递归产生的二叉树

先看递归产生的二叉树的一个图例,用某个算法取出key_i的值

可以看到:快速排序中递归的本质是:区间在变化! 显然可以用栈来存区间

栈的示意图

将上方二叉树图转化为栈图

可以看到类似二叉树的前序遍历:先对该区间快速排序, 相当于访问根,再左区间快速排序,相当于访问左子树,最后访问右区间

(回顾前序遍历知识点参见106.【C语言】数据结构之二叉树的三种递归遍历方式文章),因此将99.【C语言】数据结构之栈文章的栈的源码嵌入快速排序中即可

先写代码框架

栈的初始化和销毁

void QuickSort_Hoare_Use_Stack(int* arr, int left, int right)//非递归,使用栈辅助改循环
{
	ST st;
	STInit(&st);
	//do_something
	STDestory(&st);
}

再填写细节部分

注意:区间的边界值的入栈顺序和出栈顺序时是有讲究的!

如果先对区间的边界入栈,再对区间的边界入栈,那么出栈时,第一次出栈为边界.第二次出栈为边界(顺序是着的!)

同理如果先对区间的边界入栈,再对区间边界入栈,那么出栈时,第一次出栈为边界.第二次出栈为边界

循环条件:只要栈不为空就继续快速排序

注意:

1.栈里取一段区间,单趟排序

2.单趟分割子区间(左区间和右区间)入栈

3.子区间只有一个值或不存在就不入栈

4.取区间等同于出栈

void QuickSort_Hoare_Use_Stack(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 left = begin;
		int right = end;

		//随机选key
		srand((unsigned int)time(0));
		int rand_i = left + rand() % (right - left);
		Swap(&arr[left], &arr[rand_i]);
		int key_i = left;
		//Hoare排序
		while (left < right)
		{
			//由于key_i==left,因此right指针先走
			//右边找小
			while (left < right && arr[right] >= arr[key_i])
			{
				right--;
			}

			//左边找大
			while (left < right && arr[left] <= arr[key_i])
			{
				left++;
			}

			Swap(&arr[left], &arr[right]);
		}
		Swap(&arr[key_i], &arr[left]);
		key_i = left;
		//左区间 [begin,key_i-1],key_i,右区间[key_i+1,end]
        //要想出栈时先对左区间排序,后对右区间排序,那么右区间先入栈,左区间后入栈
		if (key_i + 1 < end)
		{
			STPush(&st, end);
			STPush(&st, key_i + 1);
		}
		if (begin < key_i - 1)
		{
			STPush(&st, key_i - 1);
			STPush(&st, begin);
		}
	}
	STDestory(&st);
}

注意出栈的写法:先保存栈顶元素后将其pop,这一点和汇编指令的pop ax的做法是一样的

pop ax执行过程:

1.将SS:SP指向的内存单元处的数据送入寄存器AX中

2.SP+=2,SS:SP指向当前栈顶下面的单元,以当前栈顶下面的单元为新的栈顶

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

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

相关文章

赛车微型配件订销管理系统(源码+lw+部署文档+讲解),源码可白嫖!

摘要 赛车微型配件行业通常具有产品多样性、需求不确定性、市场竞争激烈等特点。配件供应商需要根据市场需求及时调整产品结构和库存&#xff0c;同时要把握好供应链管理和销售渠道。传统的赛车微型配件订销管理往往依赖于人工经验和简单的数据分析&#xff0c;效率低下且容易…

Java一个简单的反弹动画练习

文章目录 说明代码详解创建窗体代码创建绘图板创建线程 运行结果完整代码 说明 做了一个小球和星型做反弹动画的窗体作为练习&#xff0c;分享给大家&#xff0c;为了方便和我一样的小白可以看的比较明白&#xff0c;所以尽量详细的标注了注释&#xff0c;希望能帮到同样在学习…

基于YOLOv8的车辆跟踪、车速计算和车辆统计应用

1、环境搭建 通过conda创建一个python≥3.8环境&#xff0c;激活环境后安装ultralytics8.2、python-opencv、shapely>2.0.0: conda create -n yolov8 python3.10 conda activate yolov8 pip install ultralytics8.2 pip install python-opencv pip install shapely>2.0…

如何提升scrapy的效率

如何提升scrapy的效率 在settings配置文件中修改CONCURRENT_REQUESTS 100 scrapy默认开启的线程数量为32个&#xff0c;这样设置可以使其线程数量为100个在运行scrapy时,会有大量的日志信息输出&#xff0c;为了减少cpu的使用率&#xff0c;可以设置log输出信息为WORNING或者…

网络安全 | 网络安全法规:GDPR、CCPA与中国网络安全法

网络安全 | 网络安全法规&#xff1a;GDPR、CCPA与中国网络安全法 一、前言二、欧盟《通用数据保护条例》&#xff08;GDPR&#xff09;2.1 背景2.2 主要内容2.3 特点2.4 实施效果与影响 三、美国《加利福尼亚州消费者隐私法案》&#xff08;CCPA&#xff09;3.1 背景3.2 主要内…

HarmonyOS(ArkUI框架介绍)

ArkUI框架介绍 ArkUI简介 基本概念 UI&#xff1a; 即用户界面。开发者可以将应用的用户界面设计为多个功能页面&#xff0c;每个页面进行单独的文件管理&#xff0c;并通过页面路由API完成页面间的调度管理如跳转、回退等操作&#xff0c;以实现应用内的功能解耦。 组件&…

EasyExcel(二)导出Excel表自动换行和样式设置

EasyExcel(一)导出Excel表列宽自适应 背景 在上一篇文章中解决导出列宽自适应,然后也解决了导出列宽不可超过255的问题。但是实际应用场景中仍然会有导出数据的长度超过列宽255。这时导出效果就会出现如下现象: 多出列宽宽度的内容会浮出来,影响后边列数据的显示。 解决…

记录一下vue2项目优化,虚拟列表vue-virtual-scroll-list处理10万条数据

文章目录 封装BrandPickerVirtual.vue组件页面使用组件属性 select下拉接口一次性返回10万条数据&#xff0c;页面卡死&#xff0c;如何优化&#xff1f;&#xff1f;这里使用 分页 虚拟列表&#xff08;vue-virtual-scroll-list&#xff09;&#xff0c;去模拟一个下拉的内容…

迅为RK3568开发板篇OpenHarmony配置HDF驱动控制LED-配置创建私有配置文件

接 下 来 新 建 vendor/hihope/rk3568/hdf_config/khdf/topeet/topeet_config.hcs 文 件 &#xff0c;topeet_config.hcs 为驱动私有配置文件&#xff0c;用来填写一些驱动的默认配置信息。HDF 框架在加载驱动时&#xff0c;会获取相应的配置信息并将其保存在 HdfDeviceObject …

nginx负载均衡-基于端口的负载均衡(一)

注意&#xff1a; (1) 做负载均衡技术至少需要三台服务器&#xff1a;一台独立的负载均衡器&#xff0c;两台web服务器做集群 一、nginx分别代理后端web1 和 web2的三台虚拟主机 1、web1&#xff08;nginx-10.0.0.7&#xff09;配置基于端口的虚拟主机 [rootOldboy extra]# …

金融项目实战 02|接口测试分析、设计以及实现

目录 ⼀、接口相关理论 二、接口测试 1、待测接口&#xff1a;投资业务 2、接口测试流程 3、设计用例理论 1️⃣设计方法 2️⃣工具 4、测试点提取 5、测试用例&#xff08;只涉及了必测的&#xff09; 1️⃣注册图⽚验证码、注册短信验证码 2️⃣注册 3️⃣登录 …

vue3使用vue3-video-play播放m3u8视频

1.安装vue3-video-play npm install vue3-video-play --save2.在组件中使用 import vue3-video-play/dist/style.css; import VideoPlay from vue3-video-play;// 视频配置项 const options reactive({src: https://test-streams.mux.dev/x36xhzz/x36xhzz.m3u8, //视频源mute…

Redis:数据类型

1. 字符串&#xff08;String&#xff09; 简介 概念&#xff1a;这是最简单的数据类型&#xff0c;可以存储字符串、整数或浮点数。特点&#xff1a;支持原子操作&#xff0c;如递增和递减数值。 示例 # 设置一个键值对 SET mykey "Hello, Redis!"# 获取该键的值…

【Web安全】SQL 注入攻击技巧详解:UNION 注入(UNION SQL Injection)

【Web安全】SQL 注入攻击技巧详解&#xff1a;UNION 注入&#xff08;UNION SQL Injection&#xff09; 引言 UNION注入是一种利用SQL的UNION操作符进行注入攻击的技术。攻击者通过合并两个或多个SELECT语句的结果集&#xff0c;可以获取数据库中未授权的数据。这种注入技术要…

移远BC28_opencpu方案_pin脚分配

先上图&#xff0c;BC28模块的pin脚如图所示&#xff1a; 下面看看GPIO的复用管脚 然后我自己整理了一份完整的pin功能列表

PHP多功能投票小程序源码

多功能投票小程序&#xff1a;全方位打造专属投票盛宴的得力助手 &#x1f389; &#x1f527; 基于先进的ThinkPHP框架与Uniapp技术深度融合&#xff0c;我们匠心独运&#xff0c;精心雕琢出一款功能全面、操作便捷的投票小程序&#xff0c;旨在为您带来前所未有的投票体验。…

ORB-SALM3配置流程及问题记录

目录 前言 一、OPB-SLAM3基本配置流程 1.下载编译Pangolin 二、ORB-SLAM3配置 1.下载源码 2.创建ROS工作空间并编译ORB-SLAM3-ROS源码 3.尝试编译 三、运行测试 一、OPB-SLAM3基本配置流程 ORB-SLAM3是一个支持视觉、视觉加惯导、混合地图的SLAM&#xff08;Simultane…

RabbitMQ介绍与使用

RabbitMQ官网 RabbitMQ 介绍 RabbitMQ 是一个开源的消息代理和队列服务器&#xff0c;基于 AMQP&#xff08;高级消息队列协议&#xff09;标准&#xff0c;使用 Erlang 编程语言构建。它是消息队列&#xff08;MQ&#xff09;的一种&#xff0c;广泛应用于分布式系统中&#x…

自然语言处理之jieba分词和TF-IDF分析

jieba分词和TF-IDF分析 目录 jieba分词和TF-IDF分析1 jieba1.1 简介1.2 终端下载1.3 基本语法 2 TF-IDF分析2.1 什么是语料库2.2 TF2.3 IDF2.4 TF-IDF2.5 函数导入2.6 方法 3 实际测试3.1 问题解析3.2 代码测试 1 jieba 1.1 简介 结巴分词&#xff08;Jieba&#xff09;是一个…

rust学习——环境搭建

rust安装&#xff1a;https://kaisery.github.io/trpl-zh-cn/ch01-01-installation.html 1、vscode装插件&#xff1a; toml语法支持 依赖管理 rust语法支持 2、创建demo 3、查看目录 4、执行文件的几种方式&#xff1a; rust安装&#xff1a;https://www.rust-lang.org/z…