快速排序递归和非递归方法的简单介绍

基本思想为:任取待排序元素序列中 的某元素作为基准值,按照该排序码将待排序集合分割成两子序列,左子序列中所有元素均小于基准值,右 子序列中所有元素均大于基准值,然后最左右子序列重复该过程,直到所有元素都排列在相应位置上为止。

实现快速排序的单趟排序中有三种方法:1. 左右指针法;2. 挖坑法;3. 前后指针法

1. 左右指针法:其基本思想就是begin指针指向数组的首元素,begin作用是找大于key的值;end指针指向数组的末尾元素,end作用是找小于key的值;key为基准值此时选择末尾元素作为基准值(选择首元素作为基准),那么需要左边先走也就是begin先移动(那么需要右边先走也就是end移动)。begin找到大于key的值就和end位置的值交换(end找到小于begin的值就和begin位置的值交换),直到begin和end相遇,相遇后此位置就是key再数组最终有序后的位置。

画图举例:如下图所示key=8到了正确的位置,将数组分为左右两部分,然后递归调用单趟(第一趟)排序的方法即可。注意:选右(左)边为基准一定要让左(右)边先走,否则会出问题,具体画图得知

// 交换两个数
void Swap(int* q1, int* q2)
{int temp = *q1;*q1 = *q2;*q2 = temp;
}// 单趟 左右指针法
int partionsort1(int* arr, int begin, int end)
{assert(arr);// 以下两行代码是三数取中间值解决快速排序最坏的情况的时间复杂度,后面讲到了取消注释即可//int midIndex = Getmidindex(arr, begin, end);//Swap(&arr[midIndex], &arr[end]);int keyindex = end; // 选右边则begin左边先走反之右边先走while (begin < end){// begin先走 找大while (begin < end && arr[begin] <= arr[keyindex]) // 不加=号可能会导致死循环{++begin;}// end 找小while (begin < end && arr[end] >= arr[keyindex]){--end;}Swap(&arr[begin], &arr[end]);}Swap(&arr[begin], &arr[keyindex]);return begin;
}

2. 挖坑法:

选择end位置为初始坑,用key保存end位置的值,begin移动找到大于key的值,将该值放到end处,此时begin处就是新的坑(数据可以被覆盖);然后end移动找到小于key的值,将该值放到begin处,此时end处就是新的坑(数据可以被覆盖)。重复下去直到begin和end相遇。key放到他们相遇后的位置即可

画图举例:

// 方法2 挖坑法--这个方法比较好理解
int partionsort2(int* arr, int begin, int end)
{assert(arr);//int midIndex = Getmidindex(arr, begin, end);//Swap(&arr[midIndex], &arr[end]);// 第一个坑,初始坑,end位置就是坑,数据可以被覆盖int key = arr[end];while (begin < end){while (begin < end && arr[begin] <= key){begin++;}// 左边的值比key大,将begin的值填到end的坑上,begin位置就形成了新的坑arr[end] = arr[begin];while (begin < end && arr[end] >= key){end--;}// 右边的值比key小,将end的值填到begin的坑上,end位置就形成了新的坑arr[begin] = arr[end];}// begin和end相遇后就是 这个位置就是坑,也就是key的最终位置arr[begin] = key;return begin;
}

3. 前后指针法:

选择最后一个位置作为基准值key,prev指针指向首元素的前一个位置,cur指针指向首元素,当cur位置的数据小于key时,并且prev+1不等cur时将prev和cur位置的值互换,然后cur++;不进行互换时cur也++。当cur走到end位置后,只需要将key的值和prev+1位置的值互换即可。

画图举例:

// 方法3 前后指针法
int partionsort3(int* arr, int begin, int end)
{assert(arr);//int midIndex = Getmidindex(arr, begin, end);//Swap(&arr[midIndex], &arr[end]);int keyindex = end;int prev = begin - 1;int cur = begin;while (cur < end){if (arr[cur] < arr[keyindex] && ++prev != cur)Swap(&arr[cur], &arr[prev]);cur++;}// 画图演示,最后是要将key和prev的下一个位置的数据进行交换,key的左边才小于key右边才大于keySwap(&arr[++prev], &arr[keyindex]);return prev;
}

递归方法:

利用函数递归调用以上三种方法中的其中一种即可:第一调用时,数组被分为了小于key的前半部分和大于key的后半部分,分别在调用函数对这两部分进行排序即可

void QuickSort1(int* arr, int left, int right)
{assert(arr);if (left >= right)return;int div = partionsort1(arr, left, right);// 排左边部分 [left,div-1] div [div+1 right]QuickSort1(arr, left, div - 1);// 排右边部分QuickSort1(arr, div + 1, right);//if (left < right)//{//	int div = partionsort(arr, left, right);//	// 排左边部分//	QuickSort(arr, left, div - 1);//	// 排右边部分//	QuickSort(arr, div + 1, right);//}
}

快速排序的缺点: 数据已经有序的情况下每一次都选择左边或者右边作为key,左边或者右边剩下很多数据,要继续排序建栈帧,会导致栈溢出,比如 1w个有序数,就要建1W个栈帧,此时就是最坏的情况 。(时间复杂度为 O (n²))

前面提到三数取中间值的方法就能够解决最坏的情况,基准值被选在数组中间位置,让数组能较为均衡地被划分,快速排序的时间复杂度得以维持在 O (nlogn)。

// 获得前中后三个数中的中间值,解决快速排序的最坏的情况
int Getmidindex(int* arr, int begin, int end)
{assert(arr);int mid = (begin + end) / 2;if (arr[begin] < arr[mid]){if (arr[mid] < arr[end])return mid;else if (arr[begin] > arr[end])return begin;elsereturn end;}else  // arr[begin] > arr[mid]{if (arr[mid] > arr[end])return mid;else if (arr[end] > arr[begin])return begin;elsereturn end;}
}

优化:待排序的区间小于10的时候使用直接插入排序

// 优化
void QuickSort2(int* arr, int left, int right)
{assert(arr);if (left >= right)return;// 区间大于10if ((right - left + 1) > 10){int div = partionsort3(arr, left, right);// 排左边部分 [left,div-1] div [div+1 right]QuickSort2(arr, left, div - 1);  // 左递归// 排右边部分QuickSort2(arr, div + 1, right);  // 右递归}//区间小于10使用插入排序else{InsertSort(arr + left, right - left + 1);}//if (left < right)//{//	int div = partionsort(arr, left, right);//	// 排左边部分//	QuickSort(arr, left, div - 1);//	// 排右边部分//	QuickSort(arr, div + 1, right);//}
}

非递归方法:

递归的实现,就是给每一个递归函数建立函数栈帧,通过压栈的方式存储函数的参数。这里使用栈来模拟函数栈帧。

前面已经讲到了栈的相关实现代码,这里就直接使用了:

画图理解,下图画出了递归的区间变化图,非递归只需要将left和right压入栈中,再取出来即可传给partionsort即可,只要栈不空,那么就有子区间还是无序状态,栈空后数组就排序完成。

// 递归改非递归,1:改循环(斐波那契数列求解)一些简单的递归才能改;2:栈模拟存储数据
// 非递归作用:1:提高效率(建立函数栈帧还是有消耗的,但是对于现代计算机,这个优化微乎其微可以忽略不计)
//           2:递归最大缺陷就是,如果栈帧的深度太深,可能会导致栈溢出,因为系统的栈区空间一般不大在M级别,
//			 数据结构栈模拟非递归, 数据是存储在堆区上面的,堆区空间是G级别的
// 快速排序--非递归方法--利用栈替代栈区(函数栈帧)
void QuickSortNonR(int* arr, int left, int right)
{assert(arr);Stack st;       // 建栈StackInit(&st); // 初始化栈// 将最开始的区间压入栈StackPush(&st, right);StackPush(&st, left);while (!StackEmpty(&st)){// 数组左区间值int begin = StackTop(&st);  // 取栈顶数据StackPop(&st);              // 出栈// 数组右区间值int end = StackTop(&st);StackPop(&st);int div = partionsort2(arr, begin, end);  // 排序// [begin,div-1] div [div+1,end]// 如果剩下待排序的两段区间的元素个数大于1个那么就入栈,再取出来排序// 和递归一样先排左区间再排右区间if (div + 1 < end)  // 右区间,相当于右递归{StackPush(&st, end);StackPush(&st, div+1);}if (begin < div - 1)  // 左区间,相当于左递归{StackPush(&st, div - 1);StackPush(&st, begin);}}StackDetory(&st);
}

结论:

快速排序是一种基于分治思想的高效排序算法,其核心是通过基准值将数组划分为左右两部分,递归处理直至有序。本文详细介绍了三种单趟排序实现方法:1)左右指针法通过双指针交换元素;2)挖坑法通过移动基准值"坑位";3)前后指针法利用双指针交替移动。针对递归可能导致的栈溢出问题,提出三数取中优化基准值选择,并建议小规模数据使用插入排序。最后介绍了用栈模拟递归的非递归实现,有效避免深度递归风险。各种方法均附有代码实现和图示说明,完整展现了快速排序的优化思路。

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

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

相关文章

从零开始的云计算生活——第三十二天,四面楚歌,HAProxy负载均衡

目录 一.HAProxy简介 二.HAProxy特点和优点&#xff1a; 三.HAProxy保持会话的三种解决方法 四.HAProxy的balance 8种负载均衡算法 1&#xff09;RR&#xff08;Round Robin&#xff09; 2&#xff09;LC&#xff08;Least Connections&#xff09; 3&#xff09;SH&am…

策略模式及优化

策略模式&#xff08;Strategy Pattern&#xff09;是一种行为设计模式&#xff0c;其核心思想是将算法的定义与使用分离&#xff0c;使算法可以独立于客户端进行变化。它通过定义一系列算法&#xff0c;将每个算法封装到独立的类中&#xff0c;并使它们可以互相替换&#xff0…

微信小程序开发-桌面端和移动端UI表现不一致问题记录

桌面端和移动端UI表现不一致零、引擎说明一、样式不同1、text 单行&#xff1a;1.1 空格开发者工具不展示&#xff0c;手机/PC端正常1.2 正常展示省略号&#xff0c;需要2、点击按钮z-index: -1。webview - 桌面端不行&#xff0c; skyline - 移动端可以&#xff1b;3、其他说明…

极限状态下函数开根号的计算理解(含示意图)

遇到一个挺有意思的题做个记录&#xff1a; 求曲线y (x21)(x2−1)0.5\frac{\left(x^{2}1\right)}{\left(x^{2}-1\right)^{0.5}}(x2−1)0.5(x21)​渐近线的条数 比较明显的x 1是无定义点。但是在求极限的时候发现1和1-得到的极限值似乎不一样。似乎是1是趋向于∞&#xff0c;1…

C++——模版(函数模版和类模版)

C 模板&#xff08;Templates&#xff09;完整介绍模板是 C 中一种强大的泛型编程机制&#xff0c;允许开发者编写与类型无关的代码&#xff0c;从而提高代码的复用性和灵活性。通过模板&#xff0c;可以避免为不同数据类型重复编写相似的函数或类&#xff0c;实现真正的代码复…

Python之cv2:cv2(OpenCV,opencv-python)库pip下载超时、下载失败、无法下载的解决方案大全

Python之cv2&#xff1a;cv2(OpenCV&#xff0c;opencv-python)库pip下载超时、下载失败、无法下载的解决方案大全 在学习和使用 OpenCV&#xff08;Python 包名&#xff1a;opencv-python 或简称 cv2&#xff09;的过程中&#xff0c;很多初学者常常会遇到通过 pip install o…

asyncio 与 uvloop

事件循环 事件循环 协调所有协程执行的中央调度器&#xff0c;它通过非阻塞机制&#xff0c;实现并发执行多个异步任务。 事件循环是 异步编程的核心机制&#xff0c;用一句话概括就是&#xff1a; 事件循环不断检查任务队列&#xff0c;一旦某个异步任务完成&#xff0c;它…

一文读懂循环神经网络(RNN)—语言模型+n元语法(1)

目录 什么是语言模型&#xff1f; 语言模型的核心目的 一.量化文本的合理性 二.支持下游 NLP 任务 三. 语义和上下文依赖 一元语法、二元语法和三元语法详解 核心概念&#xff1a;n-gram 模型 1. 一元语法&#xff08;Unigram&#xff09; 2. 二元语法&#xff08;Bigram…

DirectX12(D3D12)基础教程九 间接绘制

在学习directx12 microsoft提供了很多示例&#xff0c;有简单的也有复杂,下载网址&#xff1a;https://github.com/microsoft/DirectX-Graphics-Samples 本章对D3D12ExecuteIndirect 示例做了简化&#xff0c;只保留间接绘制部分&#xff0c;删除了计算着色器部分。 间接绘制…

fastApi连接数据库

1&#xff1a;pip install tortoise-orm2&#xff1a;pip install aiomysql3&#xff1a;pip install asyncmy或者使用国内清华园pip install -i https://pypi.tuna.tsinghua.edu.cn/simple asyncmy4&#xff1a;pip install aerich通过 python -m 直接运行&#xff08;推荐&a…

Apache-web服务器环境搭建

目录 实验要求 思路总结 1.常规配置web服务 2.通过用户主页配置web服务 3.通过虚拟目录配置web服务 4.添加DNS解析服务&#xff0c;访问虚拟机域名&#xff1a; www.TestWeb.com 实验要求 (ip 192.168.48.130) 1、常规配置web服务 2、通过用户主页配置web服务 3、通过虚…

Altium Designer 25 安装与配置完整教程

本教程将带您一步步完成 Altium Designer 25 的下载、安装与激活配置 第一步&#xff1a;下载安装包 首先&#xff0c;需要获取 Altium Designer 25 的完整安装程序。 &#x1f449; 下载链接&#xff1a; 百度网盘&#xff1a;百度网盘 请输入提取码 提取码: dxei 夸克网盘…

【工具】AndroidStudio修改中文语言汉化

AndroidStudio修改中文语言汉化 https://github.com/sollyu/AndroidStudioChineseLanguagePackhttps://github.com/sollyu/AndroidStudioChineseLanguagePack

代码随想录|图论|15并查集理论基础

并查集理论基础 | 代码随想录 并查集还是比较简单的&#xff0c;只要搞清楚两个事情&#xff1a; 并查集是干啥的&#xff1f;解决什么类型问题&#xff1f;并查集模板&#xff08;背下来&#xff09; 1、并查集是干啥的 并查集主要是两个功能&#xff1a; 两个元素添加到…

用MYSQL学习sql第一次总结和作业

总结 数据库&#xff08;Database&#xff09; 理解为“文件夹”&#xff0c;里面可以装很多张表。作业中要求先建一个名字叫 mydb6_product 的数据库。 表&#xff08;Table&#xff09; 理解为“Excel 工作表”&#xff0c;由“列&#xff08;字段&#xff09;”和“行&…

SQLite技术架构解析,适用场景有哪些?

一、SQLite技术架构解析 SQLite是一款轻量级、无服务器、嵌入式关系型数据库&#xff0c;其架构设计围绕“简化复杂性、提升效率”展开&#xff0c;核心由前端&#xff08;SQL处理&#xff09;、执行引擎&#xff08;VDBE&#xff09;、存储引擎&#xff08;B-Tree&#xff09;…

【Luogu】每日一题——Day3. P6392 中意 (数学 取模)

链接&#xff1a;P6392 中意 - 洛谷 题目&#xff1a; 思路&#xff1a; 数论这一块 题目让我们求这个结果对 MOD 取模&#xff0c;那么我们肯定是不像看到这个除法&#xff0c;所以考虑如何消除这个除法 我们可以想到&#xff0c;向上取整就是加上一个数&#xff0c;假设其为…

React强大且灵活hooks库——ahooks入门实践之DOM类hook(dom)详解

什么是 ahooks&#xff1f; ahooks 是一个 React Hooks 库&#xff0c;提供了大量实用的自定义 hooks&#xff0c;帮助开发者更高效地构建 React 应用。其中 DOM 类 hooks 是 ahooks 的一个重要分类&#xff0c;专门用于处理 DOM 相关操作&#xff0c;如事件监听、元素状态、拖…

GeoTools 工厂设计模式

前言使用GeoTools开发时有必要了解其工厂设计模式&#xff0c;作为软件开发核心设计模式&#xff0c;其设计思想具有普遍性和研究性。明白方法原理有助于提高开发效率&#xff0c;达到事半功倍的效果。1. 工厂模式 工厂模式&#xff08;Factory Pattern&#xff09;是面向对象中…

npu-smi info命令参数解释

华为昇腾npu-smi显示npu-smi工具的帮助信息npu-smi -h字段说明-h命令的帮助信息–help命令的帮助信息-vnpu-smi版本信息info显示硬件详细信息set修改设备配置属性clear清除设备信息upgrade升级MCU固件 npu-smi info 用于监控和管理华为NPU的状态和性能字段值说明npu-smi24.1.rc…