【数据结构与算法】:快速排序和归并排序的非递归实现

1. 递归实现的缺陷

在以前的文章中我们把快速排序和归并排序的递归实现方式进行了介绍,但是在校招面试和在企业的日常开发过程中,仅掌握递归方法是不够的,因为递归也有它的缺陷。

我们知道在函数调用过程中会在内存中建立栈帧,栈帧的建立是会消耗空间的。而递归最致命的缺陷就是:在极端情况下,当栈帧的深度太深时,栈空间不够用,就会导致栈溢出!

1.1 栈溢出的例子
可以举一个简单的例子来证明存在栈溢出的情况。
比如:我们用递归实现 1 + 2 + 3 + …… + n 的求和。

#define _CRT_SECURE_NO_WARNINGS #include <stdio.h>int func(int n)
{return n == 1 ? 1 : n + func(n - 1);
}int main()
{int n = 0;scanf("%d", &n);int sum = func(n);printf("%d\n", sum);return 0;
}

当输入的 n = 10000 时 ,调试结果如下:

![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/656443118d0e4b79a8993831e158fa7d.png

2. 递归改非递归的实现方式

通常来说,递归改非递归有两种方式:
1. 直接改成循环(迭代)
2. 借助数据结构的栈模拟

3. 快速排序的非递归 — 使用栈

1.首先先来观察快排的递归实现(三种方法均可,这里用的"前后"指针法 ):

在这里插入图片描述
通过观察我们发现,每次递归调用传过去的是一个数组一个区间,数组不用多说,这个区间就是我们的突破点。
也就是说我们要想一个方法来拿到每左右子区间,再对它们分别进行排序,这样才能模拟出递归的过程。那该如何做呢?借助数据结构的栈。

2.非递归的代码实现:

注意:由于C语言没有栈函数的库,所以这里使用的栈要提前准备好,实现栈的过程这里不再介绍。若想了解请前往我的主页。


void Swap(int* p1, int* p2)
{int tmp = *p1;*p1 = *p2;*p2 = tmp;
}void QuickSortNonR(int* arr, int n)
{ST st;StackInit(&st);//先入右,后入左StackPush(&st, n - 1);StackPush(&st, 0);while (!StackEmpty(&st)){//先出左边界int left = StackTop(&st);StackPop(&st);//后出右边界int right = StackTop(&st);StackPop(&st);int keyi = left;int prev = left;//关键字默认左边第一个元素int cur = left + 1;while (cur <= right){//++prev != cur是指当cur和prev重合时不用多于的交换if (arr[cur] < arr[keyi] && ++prev != cur){Swap(&arr[cur], &arr[prev]);}cur++;}Swap(&arr[keyi], &arr[prev]);keyi = prev;if (keyi + 1 < right){StackPush(&st, right);StackPush(&st, keyi + 1);}if (left < keyi - 1){StackPush(&st, keyi - 1);StackPush(&st, left);}}StackDestory(&st);
}

排序结果如下:

在这里插入图片描述

3.步骤总结:

3.1.首先要先把数组 最右端最左端 入栈,这样就有了一个初始区间,然后开始循环。

3.2.取出栈顶的两个数据,分别赋给 leftright注意在这之后要pop掉取出的数据

3.3.再使用前后指针法走完一趟后就得到了keyi

3.4.然后数组就被 keyIndex 分成了两个子区间,分别是:
左区间:[left,keyi -1]
右区间:[keyi +1,right]

3.5.由于我们一般是先排左区间,再排右区间,根据栈的后进先出特性,所以要先入右区间

分别将左区间和右区间入栈,注意这里要判断
keyi + 1 < right
left < keyi - 1
否则会出现数组访问越界或是死循环的情况。

3.6.循环结束后,销毁栈。

4.总结一下

最后我们要知道的是,快排的非递归并不会使性能受到破坏,它的时间复杂度也是O(N*logN),它的效率依旧极高。使用非递归的主要原因就是防止溢出。

4. 归并排序的非递归 — 使用循环

首先我们知道归并排序的思想是将两组有序的数据合成一组有序的数据。

1. 循环步骤如下:

1.1 那么我们会这样想,当第一组只有1个数据,第二组也只有1个数据时,我们认为这两个数是有序的,把它们一一归并到一个临时数组中,此时临时数组里的2个数据就有序了。

1.2.当第一组有2个有序数据,第二组也有2个有序数据时,把它们两两归并到一个临时数组中此时临时数组里的4个数据就有序了。

1.3.当第一组有3个有序数据,第二组也有3个有序数据时,把它们三三归并到一个临时数组中,此时临时数组里的6个数据就有序了。

1.4 重复上述步骤……直到全部数据有序为止。

2.循环图解如下:

我们假设 gap 为每一组的数据个数,这里最难的是如何控制每一组的范围,即那个区间的边界

假设循环的初始值从 i = 0 开始,则两组的范围可以分别表示为:

第一组:[ i , i + gap - 1 ]
第二组:[ i + gap , i + 2gap - 1 ]

每组1个数据,一一归,归完后临时数组里的2个数据就有序了;
![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/388cbe63cedd45ed9a8c8a013bd8a29e.png

每组2个数据,二二归,归完后临时数组里的4个数据就有序了;
在这里插入图片描述
每组4个数据,四四归,归完后临时数组里的8个数据就有序了;
在这里插入图片描述

2. 非递归的代码实现:

这里有两种边界修正问题需要特别注意:
(1)在归并过程中右半区间可能不存在
这时直接跳出循环,把剩下的那一个数据直接拷贝进数组即可。
比如在一一归并时:
在这里插入图片描述

(2)在归并过程中右半区间可能算多了

此时要把右半区间的右边界修改为数组最后一个元素的下标。
在这里插入图片描述


//归并排序的非递归
void MergeSortNonR(int* a, int sz)
{int* tmp = (int*)malloc(sizeof(int) *sz);int gap = 1; // 每组数据个数while (gap < sz){for (int i = 0; i < sz; i += 2 * gap){// [i, i+gap-1] [i+gap,i+2*gap-1]int begin1 = i, end1 = i + gap - 1;int begin2 = i + gap, end2 = i + 2 * gap - 1;// 归并过程中右半区间可能就不存在if (begin2 >= sz)break;// 归并过程中右半区间算多了, 修正一下if (end2 >= sz){end2 = sz - 1;}//归并的过程int index = i;while (begin1 <= end1 && begin2 <= end2){if (a[begin1] < a[begin2]){tmp[index++] = a[begin1++];}else{tmp[index++] = a[begin2++];}}while (begin1 <= end1){tmp[index++] = a[begin1++];}while (begin2 <= end2){tmp[index++] = a[begin2++];}// 归一部分,拷一部分,没归的就不拷for (int j = i; j <= end2; ++j){a[j] = tmp[j];}}gap *= 2;}free(tmp);
}

排序结果如下:

在这里插入图片描述

3.总结一下

归并排序的非递归的时间复杂度也是O(N*logN),它与递归的性能也差不多。

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

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

相关文章

solidity(3)

地址类型 pragma solidity ^0.8.0;contract AddressExample {// 地址address public _address 0x7A58c0Be72BE218B41C608b7Fe7C5bB630736C71;address payable public _address1 payable(_address); // payable address&#xff0c;可以转账、查余额// 地址类型的成员uint256…

C/C++与Python:各自的优势与前景展望

在讨论C/C和Python这两种编程语言的前景时&#xff0c;我们必须认识到每种语言都有其独特的定位和应用场景&#xff0c;并不存在绝对意义上的“谁更有前景”。它们分别在不同的领域发挥着重要作用&#xff0c;而且在未来的技术发展过程中&#xff0c;二者都将继续保持其不可替代…

Linux进程间通信【二】

system V进程间通信 管道通信本质是基于文件的&#xff0c;也就是说操作系统并没有为此做过多的设计工作&#xff0c;而system V IPC是操作系统特地设计的一种通信方式。但是不管怎么样&#xff0c;它们的本质都是一样的&#xff0c;都是在想尽办法让不同的进程看到同一份由操…

【深度学习】海洋生物数据集,图片分类

文章目录 任务描述数据收集数据处理模型训练指标评测web app代码和帮助 任务描述 收集9种以上的海洋生物图片&#xff0c;然后基于深度学习做一个分类模型&#xff0c;训练完成后&#xff0c;分类模型就可以对未知图片进行分类。 在之后随便传一张图片&#xff0c;分类模型就…

【静态分析】静态分析笔记01 - Introduction

参考&#xff1a; BV1zE411s77Z [南京大学]-[软件分析]课程学习笔记(一)-introduction_南京大学软件分析笔记-CSDN博客 ------------------------------------------------------------------------------------------------------ 1. program language and static analysis…

ORB-SLAM3整体流程详解

0. 简介 在之前&#xff0c;作者曾经转过一篇《一文详解ORB-SLAM3》的文章。那篇文章中提到了ORB-SLAM3是一个支持视觉、视觉加惯导、混合地图的SLAM系统&#xff0c;可以在单目&#xff0c;双目和RGB-D相机上利用针孔或者鱼眼模型运行。与ORB-SLAM2相比&#xff0c;ORB-SLAM3…

接口自动化框架设计必备利器之参数传递

在我们设计自动化测试框架的时候&#xff0c;我们会经常将测试数据保存在外部的文件&#xff08;如Excel、YAML&#xff09;中&#xff0c;实现测试脚本与测试数据解耦&#xff0c;方便后期维护。 当涉及到业务场景接口用例时&#xff0c;由于接口与接口存在关联关系&#xff…

STL容器之unordered_map类

文章目录 STL容器之unordered_map类1、unordered_map1.1、unordered_map介绍1.2、unordered_map的使用1.2.1、unordered_map的常见构造1.2.2、unordered_map的迭代器1.2.3、unordered_map的容量1.2.4、unordered_map的增删查1.2.5、unordered_map的桶操作 2、unordered_multima…

【快速上手ESP32(基于ESP-IDFVSCode)】04-PWM

PWM PWM&#xff0c;即脉冲宽度调制&#xff08;Pulse Width Modulation&#xff09;&#xff0c;是一种模拟控制方式&#xff0c;根据相应载荷的变化来调制晶体管基极或MOS管栅极的偏置&#xff0c;来实现晶体管或MOS管导通时间的改变&#xff0c;从而达到控制开关稳压电源输出…

物联网系统设计 8

1 规划中小型LoRa 中小型分时复用&#xff0c;大型项目需要学习LoRaWAN 1.1 通信记录 1.2 节点能耗 1278芯片 满功率20DMB&#xff0c;增加PA&#xff0c;发送功率 30 DBM 内置天线柔性 棒状 3db 203 休眠、发生、接收 计算链路预算&#xff0c;工作电流&#xff0c;工…

Blocs v5.2.3 mac版 可视化网页设计工具 兼容 M1/M2/M3

Mac毒搜集到的Blocs 是一款mac上专业强大的精美网页设计软件。特点就是简介、高效、高质&#xff0c;可以帮助我们快速设计出精美的网页&#xff0c;retina视网膜特性支持&#xff0c;优化自适应网页的开发。 应用介绍 Blocs 是一款mac上专业强大的精美网页设计软件。专注于简…

实现WAF对CC攻击的零误封防护:关键技术解析与实践

一、引言 Web应用防火墙&#xff08;WAF&#xff09;作为网站安全的重要防线&#xff0c;其在防御CC&#xff08;Challenge Collapsar&#xff0c;即挑战黑洞&#xff0c;一种分布式拒绝服务攻击&#xff09;攻击中的效能至关重要。然而&#xff0c;精准识别并有效拦截CC攻击的…

2024阿里云2核4G服务器优惠价格表_2核4G性能测评

阿里云2核4G服务器多少钱一年&#xff1f;2核4G服务器1个月费用多少&#xff1f;2核4G服务器30元3个月、85元一年&#xff0c;轻量应用服务器2核4G4M带宽165元一年&#xff0c;企业用户2核4G5M带宽199元一年。本文阿里云服务器网整理的2核4G参加活动的主机是ECS经济型e实例和u1…

pytesseract,一个超强的 Python 库!

更多资料获取 &#x1f4da; 个人网站&#xff1a;ipengtao.com 大家好&#xff0c;今天为大家分享一个超强的 Python 库 - pytesseract。 Github地址&#xff1a;https://github.com/madmaze/pytesseract 在当今数字化时代&#xff0c;文字识别技术扮演着越来越重要的角色。…

设计模式-装饰者模式(Decorator)

1. 概念 装饰者模式是一种结构型设计模式&#xff0c;它允许用户在不修改原始对象的基础上&#xff0c;通过将对象包装在装饰者类的对象中&#xff0c;动态地给原始对象添加新的行为或职责。 2. 原理结构图 图1 图2 抽象组件&#xff08;Component&#xff09;&#xff1a…

离散化实现低通滤波器(选用双线性变换)

学习了离散化后整理了其中的核心步骤&#xff0c;此处不对原理进行深究&#xff0c;仅仅介绍如何对一个连续系统进行离散化 此处可以得到低通滤波器的截止频率为1000rad/s. 离散化的过程为将s 2/Ts * [(z - 1) / (z 1)] (选用双线性变换) 得到离散化序列为 Y(z) [w…

Python-VBA函数之旅-bytearray函数

目录 1、bytearray函数&#xff1a; 1-1、Python&#xff1a; 1-2、VBA&#xff1a; 2、相关文章&#xff1a; 个人主页&#xff1a;非风V非雨-CSDN博客 bytearray函数在Python中提供了一种可变字节序列的表示方式&#xff0c;这在实际编程中有多种应用场景。常见的应用场…

如何用Python编写简单的网络爬虫(页面代码简单分析过程)

一、什么是网络爬虫 在当今信息爆炸的时代&#xff0c;网络上蕴藏着大量宝贵的信息&#xff0c;如何高效地从中获取所需信息成为了一个重要课题。网络爬虫&#xff08;Web crawler&#xff09;作为一种自动化工具&#xff0c;可以帮助我们实现这一目标&#xff0c;用于数据分析…

项目实战 | 使用python分析Excel销售数据(用groupby)

项目实战 | 使用python分析Excel销售数据 本文目录&#xff1a; 零、00时光宝盒 一、提出问题 二、理解数据 2.1、安装python读取excel文件的库 2.2、查看excel表的字段名和前几行记录 2.3、查看excel表结构 2.4、查看索引 2.5、查看每一列的列表头内容 2.6、查看每一…

移除元素Java实现

题意 给你一个数组 nums 和一个值 val&#xff0c;你需要 原地 移除所有数值等于 val 的元素&#xff0c;并返回移除后数组的新长度。 不要使用额外的数组空间&#xff0c;你必须仅使用 额外空间 并 原地 修改数组。 元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元…