​​快速排序(四)——挖坑法,前后指针法与非递归

2024-01-23 22:12

本文主要是介绍​​快速排序(四)——挖坑法,前后指针法与非递归,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

fe594ea5bf754ddbb223a54d8fb1e7bc.gif

目录

​一.前言

二.挖坑法

三.前后指针法

四.递归优化

五.非递归

六.结语


 

8fb442646f144d8daecdd2b61ec78ecd.png一.前言

本文我们接着上篇文章的重点快排,现在继续讲解对快排优化的挖坑法,前后指针法以及非递归方法,下面是上篇文章快排链接:https://mp.csdn.net/mp_blog/creation/editor/135719674。

码字不易,希望大家多多支持我呀!(三连+关注,你是我滴神!)

二.挖坑法

其实挖坑法很简单,因为它只是在hoare的基础上优化了一部分,就比如不用去纠结为什么key在右边时,右边先走。这里因为坑设定在第一位,所以自然要从右边找数去填坑。

因此这里面的循环逻辑就变得很简单,右找小(填左边坑),自己变成新坑,左找大(填右边坑),自己变新坑,就这样以此类推最后一定会相遇,而这个相遇的位置一般就是我们的居中值(三数取中法),最后把key放入即可。

int PartSort2(int* a, int left, int right)
{int midi = GetMidi(a, left, right);Swap(&a[left], &a[midi]);//通过三数取中把位于最左边的居中数给keyint key = a[left];//保存key值后,左边形成第一个坑位int hotel = left;//left与right不断相遇while (left < right){//右边先走,找小;找到后把值放到左边坑位中,右边形成新的坑位while (left < right && a[right] >= key){right--;}a[hotel] = a[right];hotel = right;//左边开始走,找大;找到后把值放到右边坑位中,左边形成新的坑位while (left < right && a[left] <= key){left++;}a[hotel] = a[left];hotel = left;}//最后二者相遇,并且其中一个必定是坑位//将key放入坑位中a[hotel] = key;return hotel;}

挖坑法由于有了前面hoare的铺垫,所以我们可以更简洁明了地写出代码,也更通俗易懂,但这些前提都是要理解hoare的核心,这样才能写出优化的挖坑法。

三.前后指针法

前后指针法写起来甚至比挖坑法更简单,因为它就只有一个核心点:

cur找比key小的数,prev++,交换prev与cur的值

而prev只有两者情况:

  1. 在cur没遇到比key大的值的时候,prev紧紧跟着cur(重合)
  2. 在cur遇到比key大的值的时候,prev在比key大的一组值的前面

这样就会出现一种情况,比key大的值会阻扰prev的前进,但只要cur再次找到比key小的值两者就会交换,prev就像是不断地推着比key大的值往后排。

3与7交换 

9与4交换

就这样持续到cur走出数组,这时可以看到prev的指向数是比key小的,最后prev与key进行交换。

写代码我们要注意一个地方,就是记得要在cur找小的时候添加一个条件如果该趟找不到小的已经越界了就及时退出,否则prev就会继续++,打乱了key的正确位置。

int PartSort3(int* a, int left, int right)
{int midi = GetMidi(a, left, right);Swap(&a[left], &a[midi]);int key = left;int prev = left;int cur = prev + 1;while (cur <= right){//找小while (cur<=right&&a[cur] >= a[key]){cur++;}//当找不到小时if (cur > right){break;}else{prev++;Swap(&a[prev], &a[cur]);}}Swap(&a[key], &a[prev]);return key;}

还有一种写法,我们可以观察到cur无论是找小还是找不到小都会++,那不妨可以这样写:

int PartSort3(int* a, int left, int right)
{int midi = GetMidi(a, left, right);Swap(&a[left], &a[midi]);int key = left;int prev = left;int cur = prev + 1;while (cur <= right){if (a[cur] < a[key]){prev++;Swap(&a[prev], &a[cur]);}cur++;}Swap(&a[key], &a[prev]);return key;}

注意!这两个优化的方法并不能提高快排的算法效率,只是思想上的优化,便于我们更好理解而已。

四.递归优化

我们再来处理优化递归的问题:

满二叉树最后一层节点会占总节点的50%,因为一共有2^h-1个节点,最后一层有2^(h-1)个

对比到快排的递归而言,为了让这10个数有序而走了那么多次递归有点不划算。

那如果10个数我们不用递归而采用直接插入法的话可以效率可以提高80%-90%,因为递归最后三层的消耗占据很多,在处理数量级小的情况下会很浪费。

代码部分: 

void QuickSort(int* a, int begin, int end)
{if (begin >= end){return;}if ((end - begin + 1) > 10){int key = PartSort3(a, begin, end);QuickSort(a, begin, key - 1);QuickSort(a, key+1, end);}else{InsertSort(a + begin, end - begin + 1);}
}

 

这是最精妙的一步(小区间优化,小区间不再递归分割排序,降低递归次数),由于我们在不断递归的过程中那些后面被分割的许多个小组序列会占据大多数空间,所以我们统一划定一个标准,只要是小于10个的范围都直接插入法去解决,又因为我们划分的无数个子树都对应原数组不同的位置,那我们就分别用[begin,end]来规定它们,当我们用到直接插入排序时,只要在原数组地址加上begin就可以对应到自己的位置。

五.非递归

非递归的关键就是能够控制与保存区间——借助栈

我们先把数组的范围录入栈中

找到key的下标后把0-9取出录入我们的右区间6-9 

再把左区间0-4录入

之所以先入右再入左是因为这样可以先把左区间给取出来(栈的特点),然后下一次循环栈不为空则取出左区间走,0-4走完单趟选出key后继续分割

0-4分割成0-1与3-4,我们再把它们分别录入栈中。 

再把0-1取出来选出key为1后分割成0-0与2-1(2-1说明这里已经没有数了,也可以参考范围[key+1,end],)而这两个区间也不用继续入栈了(没啥好分的了)。

 我们可以发现这就是递归的过程,只不过我们需要用栈以非递归的形式实现

接着我们取出3-4进行分割,然后按照区间规则它分出的两区间不用再入栈了。

就这样以此类推直到栈为空结束。

void QuickSortNonR(int* a, int begin, int end)
{ST st;STInit(&st);//入栈,先入右,如9STPush(&st, end);//再入左,如0STPush(&st, begin);while (!STEmpty(&st)){//准备出栈,获取栈顶元素,如0int left = STTop(&st);//出栈,如0STPop(&st);//准备出栈,获取栈顶元素,如9int right = STTop(&st);//出栈,如9STPop(&st);//通过获取的元素构成范围来寻找key,【0-9】int key = PartSort3(a, left, right);//[left,key-1]key[key+1,right]//对范围如【0-9】进行分割,先入栈右区间再入栈左区间//准备入6-9//判断区间合理性,等于或大于不能放if (key + 1 < right){STPush(&st, right);//如入9STPush(&st, key+1);//如入6}//6-9已经录入//同理把0-5录入if (left < key - 1){STPush(&st, key-1);//如入5STPush(&st, left);//如入0}//现在第一层结束后栈顶由上到下就是0 5  6 9//至此由第一层分割的范围录入结束,后面就是判断栈里是否为空(还有没有范围)//如果栈不为空则继续执行这个循环,直到为空的时候,非递归也就完成了。}
}

 栈相关代码:对于栈功能还不熟悉的友友也可以移步我的这篇文章学习一下:https://mp.csdn.net/mp_blog/creation/editor/133249808

#include "Stack.h"void STInit(ST* ps)
{assert(ps);ps->a = NULL;ps->capacity = 0;ps->top = 0;
}void STDestroy(ST* ps)
{assert(ps);free(ps->a);ps->a = NULL;ps->top = ps->capacity = 0;
}void STPush(ST* ps, STDataType x)
{assert(ps);// 11:40if (ps->top == ps->capacity){int newCapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;STDataType* tmp = (STDataType*)realloc(ps->a, sizeof(STDataType) * newCapacity);if (tmp == NULL){perror("realloc fail");exit(-1);}ps->a = tmp;ps->capacity = newCapacity;}ps->a[ps->top] = x;ps->top++;
}void STPop(ST* ps)
{assert(ps);// assert(ps->top > 0);--ps->top;
}STDataType STTop(ST* ps)
{assert(ps);// assert(ps->top > 0);return ps->a[ps->top - 1];
}int STSize(ST* ps)
{assert(ps);return ps->top;
}bool STEmpty(ST* ps)
{assert(ps);return ps->top == 0;
}

4b12323f94834afd9ec146a3c10df229.jpeg六.结语

本文整体上是对快排进行的优化,让它的性能更加强大,另外也帮助我们在掌握好递归的同时也能运用好非递归。最后感谢大家的观看,友友们能够学习到新的知识是额滴荣幸,期待我们下次相见~

这篇关于​​快速排序(四)——挖坑法,前后指针法与非递归的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/637685

相关文章

Python实现快速扫描目标主机的开放端口和服务

《Python实现快速扫描目标主机的开放端口和服务》这篇文章主要为大家详细介绍了如何使用Python编写一个功能强大的端口扫描器脚本,实现快速扫描目标主机的开放端口和服务,感兴趣的小伙伴可以了解下... 目录功能介绍场景应用1. 网络安全审计2. 系统管理维护3. 网络故障排查4. 合规性检查报错处理1.

MySQL快速复制一张表的四种核心方法(包括表结构和数据)

《MySQL快速复制一张表的四种核心方法(包括表结构和数据)》本文详细介绍了四种复制MySQL表(结构+数据)的方法,并对每种方法进行了对比分析,适用于不同场景和数据量的复制需求,特别是针对超大表(1... 目录一、mysql 复制表(结构+数据)的 4 种核心方法(面试结构化回答)方法 1:CREATE

SpringCloud Stream 快速入门实例教程

《SpringCloudStream快速入门实例教程》本文介绍了SpringCloudStream(SCS)组件在分布式系统中的作用,以及如何集成到SpringBoot项目中,通过SCS,可... 目录1.SCS 组件的出现的背景和作用2.SCS 集成srping Boot项目3.Yml 配置4.Sprin

SpringBoot集成iText快速生成PDF教程

《SpringBoot集成iText快速生成PDF教程》本文介绍了如何在SpringBoot项目中集成iText9.4.0生成PDF文档,包括新特性的介绍、环境准备、Service层实现、Contro... 目录SpringBoot集成iText 9.4.0生成PDF一、iText 9新特性与架构变革二、环

MySQL 批量插入的原理和实战方法(快速提升大数据导入效率)

《MySQL批量插入的原理和实战方法(快速提升大数据导入效率)》在日常开发中,我们经常需要将大量数据批量插入到MySQL数据库中,本文将介绍批量插入的原理、实现方法,并结合Python和PyMySQ... 目录一、批量插入的优势二、mysql 表的创建示例三、python 实现批量插入1. 安装 PyMyS

Mysql数据库聚簇索引与非聚簇索引举例详解

《Mysql数据库聚簇索引与非聚簇索引举例详解》在MySQL中聚簇索引和非聚簇索引是两种常见的索引结构,它们的主要区别在于数据的存储方式和索引的组织方式,:本文主要介绍Mysql数据库聚簇索引与非... 目录前言一、核心概念与本质区别二、聚簇索引(Clustered Index)1. 实现原理(以 Inno

Java Map排序如何按照值按照键排序

《JavaMap排序如何按照值按照键排序》该文章主要介绍Java中三种Map(HashMap、LinkedHashMap、TreeMap)的默认排序行为及实现按键排序和按值排序的方法,每种方法结合实... 目录一、先理清 3 种 Map 的默认排序行为二、按「键」排序的实现方式1. 方式 1:用 TreeM

使用EasyPoi快速导出Word文档功能的实现步骤

《使用EasyPoi快速导出Word文档功能的实现步骤》EasyPoi是一个基于ApachePOI的开源Java工具库,旨在简化Excel和Word文档的操作,本文将详细介绍如何使用EasyPoi快速... 目录一、准备工作1、引入依赖二、准备好一个word模版文件三、编写导出方法的工具类四、在Export

Rust 智能指针的使用详解

《Rust智能指针的使用详解》Rust智能指针是内存管理核心工具,本文就来详细的介绍一下Rust智能指针(Box、Rc、RefCell、Arc、Mutex、RwLock、Weak)的原理与使用场景,... 目录一、www.chinasem.cnRust 智能指针详解1、Box<T>:堆内存分配2、Rc<T>:

深度解析Python中递归下降解析器的原理与实现

《深度解析Python中递归下降解析器的原理与实现》在编译器设计、配置文件处理和数据转换领域,递归下降解析器是最常用且最直观的解析技术,本文将详细介绍递归下降解析器的原理与实现,感兴趣的小伙伴可以跟随... 目录引言:解析器的核心价值一、递归下降解析器基础1.1 核心概念解析1.2 基本架构二、简单算术表达