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

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

前言:

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

        

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

        

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 三方库 image_compare 鸿蒙图像治理算法域双向适配解析:突破千万级相册视觉感知哈希运算指纹比对墙,大体量空间冗余清扫提供高精雷达矩阵-适配鸿蒙 HarmonyOS ohos

Flutter 三方库 image_compare 鸿蒙图像治理算法域双向适配解析:突破千万级相册视觉感知哈希运算指纹比对墙,大体量空间冗余清扫提供高精雷达矩阵-适配鸿蒙 HarmonyOS ohos

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.ZEEKLOG.net Flutter 三方库 image_compare 鸿蒙图像治理算法域双向适配解析:突破千万级相册视觉感知哈希运算指纹比对墙,为大体量空间冗余清扫提供高精雷达矩阵 前言 在 OpenHarmony 应用的内容社交或相册管理开发中,由于重复下载或连拍,用户的磁盘空间极易被重复图像挤占。image_compare 为 Flutter 开发者提供了一套高性能、专注于图像指纹算法的对比方案。本文将介绍如何在鸿蒙端打造极致的视觉资产治理底座。 一、原理解析 / 概念介绍 1.1 基础原理/概念介绍 image_compare 的核心逻辑是基于 感知哈希(Perceptual Hashing, pHash)与颜色直方图空间映射 (Visual-Entropy Map)。它并非简单的逐像素二进制对比,而是通过将图像进行灰度化、离散余弦变换(DCT)降噪,提取反映图像“骨架结构”的

By Ne0inhk
《算法闯关指南:优选算法--位运算》--36.两个整数之和,37.只出现一次的数字 ||

《算法闯关指南:优选算法--位运算》--36.两个整数之和,37.只出现一次的数字 ||

🔥草莓熊Lotso:个人主页 ❄️个人专栏: 《C++知识分享》《Linux 入门到实践:零基础也能懂》 ✨生活是默默的坚持,毅力是永久的享受! 🎬 博主简介: 文章目录 * 前言: * 36. 两个整数之和 * 解法(位运算): * 算法思路: * C++算法代码: * 算法总结&&笔记展示: * 37.只出现一次的数字 || * 解法(比特位计数): * 算法思路: * C++算法代码: * 算法总结&&笔记展示: * 结尾: 前言: 聚焦算法题实战,系统讲解三大核心板块:优选算法:剖析动态规划、二分法等高效策略,学会寻找“最优解”。 递归与回溯:掌握问题分解与状态回退,攻克组合、排列等难题。 贪心算法:理解“

By Ne0inhk
Redis高级数据结构实战:从Stream到HyperLogLog的深度解析

Redis高级数据结构实战:从Stream到HyperLogLog的深度解析

目录 📖 摘要 🎯 第一章:为什么Redis高级特性如此重要? 1.1 我的Redis踩坑史 1.2 Redis vs 其他中间件的实战对比 1.3 Python + Redis的黄金组合 🏗️ 第二章:Redis Stream - 轻量级消息队列的王者 2.1 Stream设计哲学:为什么不是List/PubSub? 2.2 Stream消费组架构设计 📊 第三章:HyperLogLog - 海量基数统计的魔法 3.1 HyperLogLog算法原理 3.2 HyperLogLog实战:实时UV统计系统 🔒 第四章:分布式锁 - 高并发下的数据安全 4.1 分布式锁设计模式 4.2 分布式锁实战:

By Ne0inhk
Flutter 三方库 linalg 的鸿蒙化适配指南 - 掌控高性能线性代数、矩阵运算实战、鸿蒙级算法中枢

Flutter 三方库 linalg 的鸿蒙化适配指南 - 掌控高性能线性代数、矩阵运算实战、鸿蒙级算法中枢

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.ZEEKLOG.net Flutter 三方库 linalg 的鸿蒙化适配指南 - 掌控高性能线性代数、矩阵运算实战、鸿蒙级算法中枢 在鸿蒙跨平台应用处理 3D 图形变换、复杂的信号处理(DSP)或是端侧的小型机器学习模型时,高效的矩阵(Matrix)与向量(Vector)运算是一切算法的基石。如果你不想手写枯燥且易错的嵌套循环。今天我们要深度解析的 linalg——一个纯 Dart 实现的、遵循线性代数标准的专业级数学库,正是帮你搭建“算法堡垒”的数字基石。 前言 linalg 提供了一套直观且功能完备的线性代数 API。它不仅支持基础的向量加减、点积(Dot Product)和叉积(Cross Product),还涵盖了复杂的矩阵乘法、转置(Transpose)以及行列式计算。在鸿蒙端项目中,

By Ne0inhk