排序算法指南:快速排序(非递归)

排序算法指南:快速排序(非递归)

前言:

         本文将通过图解与代码相结合的方式,详细介绍快速排序的非递归实现方法。虽然前文已展示递归实现方案,但在实际面试中,面试官更倾向于考察非递归版本的实现。这种实现方式不仅能加深对算法的理解,还能展现应聘者对栈结构的掌握程度。

        

一、非递归实现快排的思路

        

1.1核心原理:手动模拟栈 

        

        在标准的递归快速排序中,当我们写下 quickSort(a,left, right) 时,系统会自动分配一块内存(函数调用栈)来记住当前的 leftright 是多少,以及函数执行完后该回到哪里。

        在非递归版本中,我们不需要系统帮忙,而是自己创建一个栈(Stack)数据结构。

        

1.2核心操作:用栈存取数组区间

        

① 向栈中存储操作:存储每一次需要排序的子数组的起止下标(begin,end)。

                                 由于栈的特性是先进后出,我们优先处理左区间,再处理右区间,类似于二叉树的前序操作。

                                 故而在存储的时候优先存储右区间,再进行存储左区间。

        

② 向栈中拿取操作: 每次从栈里拿出一对下标对这段范围进行“分区操作”,然后把产生的新范围(左半区和右半区)再扔回栈里。

        

二、用栈来模拟存储区间

        

假设存在数组a为:  [6, 1, 2, 3, 4, 5, 9, 7, 10, 8]         

                    下标:  [0  1  2  3  4  5  6  7   8   9]

        

定义int left1   : 左区间数组的起始位置下标

定义int right1 : 左区间数组的终止位值下标

则左区间数组的范围是:[left1 , right1]

        

定义int left2   : 右区间数组的起始位置下标

定义int right2 : 右区间数组的终止位置下标

则右区间数组的范围是:[left2,right2]

        

 第一步:初始化栈

        

准备一个空栈,先把整个数组的任务扔进去:入栈:[0, N-1]      

      

          
第二步:循环处理

        

        

这是一个while(栈非空)的循环:

        

1.弹栈(Pop):拿出一个任务(begin,end)。

        

2.分区(Partition)
通过Hoare分区法进行分割当前处理任务,keyi = PartitionSlowFast(a, begin, end);   

        

                                            将其分为 [begin ,  keyi - 1]   keyi   [keyi+1 ,  end] 

               

3.记录左右区间:记录左区间   left1 = begin     right1=keyi-1    即 : [left1 , right1]

                                          

                            记录右区间   left2  = keyi+1    right2=end       即:[left2,  right2]

                                       

 4.入栈左右区间: 优先压入右区间,再压入左区间,因为栈是“后进先出”,这样下一轮循环就会先处理左边,模拟递归的顺序。

                                           

                              压入右区间:push (right2)   ->   push(left2)


                                        

                              压入左区间: push (right1)  ->   push(right2)

        

                              (💡 小贴士 : 区间只有一个元素不入栈,区间不存在也不入栈    )                            

                                   

                                     第三步:栈为空

        

        当栈变空时,说明所有的大区间都被切成了小区间,小区间都被切没了(排序完成),数组就有序了!🎉

       

区间分割如图所示:

        

        

三、代码实现

        

#include <iostream> #include <stack> #include <vector> #include <algorithm> using namespace std; // 1. 实现快慢指针法分区 (PartitionSlowFast) // prev 是慢指针,cur 是快指针 int PartitionSlowFast(int* a, int left, int right) { int keyi = left; // 选取最左边为 key 的下标 int prev = left; int cur = left + 1; while (cur <= right) { // 如果快指针指向的值小于 key,且 prev 下一步不是 cur 自己 // prev 前进一步,并交换 prev 和 cur 的值 if (a[cur] < a[keyi]&& ++prev!=cur) { swap(a[prev], a[cur]); } cur++; } // 最后将 key 放到 prev 的位置 swap(a[keyi], a[prev]); return prev; // 返回 key 最终的位置 } // 2. 非递归快速排序主函数 void QuickSortNonR(int* a, int left, int right) { stack<int> st; // 初始状态入栈:注意栈是后进先出 // 我们希望出来的时候先拿 begin,再拿 end // 所以先压入 right (end),再压入 left (begin) if (left < right) { st.push(right); st.push(left); } while (!st.empty()) { // 取栈顶元素 int begin = st.top(); st.pop(); int end = st.top(); st.pop(); // 使用快慢指针法进行一趟分割 int keyi = PartitionSlowFast(a, begin, end); // [begin, keyi-1] keyi [keyi+1, end] // 核心逻辑保持不变:先处理右边,再处理左边(为了模拟递归顺序) // 也就是先压入右区间,再压入左区间 // 处理右区间 [keyi+1, end] int left2 = keyi + 1; int right2 = end; //区间只有一个元素不入栈,区间不存在也不入栈 if (right2 - left2 >=1) { st.push(right2); st.push(left2); } // 处理左区间 [begin, keyi-1] int left1 = begin; int right1 = keyi - 1; if (right1 - left1>=1) { st.push(right1); st.push(left1); } } }

        

四、非递归实现快排的优势

4.1防止栈溢出

        

递归的深度是有限制的。如果数组极其巨大,或者处于最坏情况(比如倒序数组排成正序),递归层数太深会导致程序崩溃。

非递归使用堆内存(Heap)来存栈,空间通常比系统栈大得多,更安全。

4.2性能优化:

        

在某些极端环境下,函数调用本身是有开销的(保存现场、恢复现场),手动模拟可以省去这些微小的开销。

        

既然看到这里了,不妨关注+点赞+收藏,感谢大家,若有问题请指正。

                                                                         

Read more

从零构建可扩展 Flutter 应用:v1.0 → v2.0 全代码详解 -《已适配开源鸿蒙》

从零构建可扩展 Flutter 应用:v1.0 → v2.0 全代码详解 -《已适配开源鸿蒙》

* 个人首页: VON * 鸿蒙系列专栏: 鸿蒙开发小型案例总结 * 综合案例 :鸿蒙综合案例开发 * 鸿蒙6.0:从0开始的开源鸿蒙6.0.0 * 鸿蒙5.0:鸿蒙5.0零基础入门到项目实战 * Electron适配开源鸿蒙专栏:Electron for OpenHarmony * 本文章所属专栏:Flutter for OpenHarmony * 文章AtomGit地址:Template_V2.0 v1.0 → v2.0 全代码详解 * 从零构建可扩展 Flutter 应用:v1.0 → v2.0 全代码详解 * 🧱 第一阶段:v1.0 —— 干净的基础骨架 * ✅ 目标 * 📁 项目结构 * 1. `lib/main.dart`

全网最全:小乌龟(TortoiseSVN/TortoiseGit)工具使用方法

你想知道本地文件处于 modified(已修改)状态时,是否需要先执行拉取(Git:Pull / SVN:Update)操作,答案是:强烈建议先拉取 / 更新,而非必须,但这是规避版本冲突、保障协作顺畅的核心最佳实践。 下面结合小乌龟(TortoiseSVN/TortoiseGit)工具,详细拆解背后的逻辑、操作细节及注意事项: 一、 为什么强烈建议先拉取 / 更新? 本地文件标记为 modified 仅代表你修改了受版本管控的文件,但团队其他成员可能早已将同一文件的最新修改提交到了远程仓库。如果直接提交你的 modified 文件,而不先同步远程最新版本,很可能出现 版本冲突(比如你和同事都修改了同一文件的同一行代码,版本控制系统无法自动判断保留哪一个修改)。先执行拉取 / 更新,本质是将远程仓库的最新代码同步到本地,让你的本地修改基于团队最新版本进行叠加,最大程度避免不必要的冲突,即使出现冲突也能在本地提前解决,不影响远程仓库的整洁性。 二、 关键操作前提:拉取 / 更新前,先妥善处理本地 modified

2025年AI领域年度深度总结:始于DeepSeek R1开源发布,终于Manus天价出海

2025年AI领域年度深度总结:始于DeepSeek R1开源发布,终于Manus天价出海

2025年AI领域年度深度总结:始于DeepSeek R1开源发布,终于Manus天价出海 摘要 站在2025年12月31日的终章回望,吴恩达曾说过:“2025年,是AI工业时代的黎明。”在经历了2023-2024年的“大炼模型”狂热后,2025年,AI终于从“概率模仿”跃向了“逻辑推理”的新阶段,从“对话框”到“行动流”的转折也逐渐显现。这一年,AI技术与产业的演进不仅仅是技术迭代那么简单,而是一场深刻的变革,清晰的产业蓝图开始显现:始于DeepSeek R1的开源突破,终于Manus的数十亿美元收购,验证了Agent商业化的巨大潜力。 2025年,AI不再是实验室中的抽象概念,而是逐步嵌入日常生产生活,以更加务实的姿态和广泛的应用场景,真正走向了社会的主流。从年初DeepSeek R1的开源发布到年末Manus的天价收购,这两件大事为2025年的AI发展定下了基调:开源与闭源的博弈,技术与商业的融合,模型与应用的深度对接,无疑为AI的未来铺设了一条发展道路。技术突破和产业落地不断交织,AI的角色正在悄然发生深刻的转变——从“辅助工具”走向了“自主执行者”。 文章目录