快速排序的hoare法

2023-12-19 20:12
文章标签 快速 排序 hoare

本文主要是介绍快速排序的hoare法,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

目录

快速排序(hoare版本)

初级实现

问题改进 

中级实现

时空复杂度 

高级实现

三数取中 


快速排序(hoare版本)

历史背景:快速排序是Hoare于1962年提出的一种基于二叉树思想的交换排序方法
基本思想:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列

初级实现

实现步骤: 1、确定排序开始时key (每轮排序作为基准元素的元素下标) 、left (负责寻找大于基准元素的元素下标) 、right (负责寻找小于基准元素的元素下标) 三者的初始值:
int left = begin;  //数组首元素下标
int right = end;   //数组尾元素下标
int keyi = begin;  //即可以是首元素下标、也可以是尾元素下标,一般来说是首元素下标

2、right和left开始自己的寻找任务,当a[right] > a[keyi],right就--继续向左走,当a[left] < a[keyi],left就++继续向右走,当二者都在找的过程中在某一位置停下时(两个while循环均结束),交换此时的a[left]和a[right]:

void QuickSort(int* a, int begin, int end)
{int left = begin, right = end;int keyi = begin;// 右边找小while (a[right] > a[keyi]){--right;}// 左边找大while (a[left] < a[keyi]){++left;}Swap(&a[left], &a[right]);}

3、当left与right相遇即left == right时,此时元素的值一定小于基准元素的值,所以交换当前元素与基准元素的位置(因为我们要做的就是将小于基准元素的数放在左边,大于的放在右边)

void QuickSort(int* a, int begin, int end)
{int left = begin, right = end;int keyi = begin;while (left < right){// 右边找小while (a[right] > a[keyi]){--right;}// 左边找大while (a[left] < a[keyi]){++left;}Swap(&a[left], &a[right]);}Swap(&a[left], &a[keyi]);
}

关于“为什么相遇位置一定会比基准元素小”的解释:

        因为我们规定右边先走(当然你也可以让左基准元素是数组尾元素然后左边先走,这里就不再分析了),这样就会有两种相遇的情况:

①right遇到left,right没找到比基准元素小的,一直走,找到时停下,然后left向右走,当二者相遇即right ==left时停下(原因我们后面会将),所处位置的元素的值小于基准元素:

②left遇到right,right先走,找到小于基准元素的位置停下,left开始找比基准元素大的,没有找到,一直走,遇到right停下,相遇位置是right,前面说过此时的位置应该是小于基准元素的位置(“right先走,找到小于基准元素的位置停下”)

至此,我们快速排序的初级实现已经完成了,接下来就是处理我们遗留的一些问题了: 

问题改进 

1、产生原因:有时我们写的代码只适用于部分数据,但是换成其它数据时就会出错,为了保证我们代码的通用性,我们要进行多次的用例测试,比如我们将数组换为{6,1,2,5,4,6,9,7,10,8}:

        

        可以发现,之前的代码并不能让right和left相遇,又因为我们规定right先走,所以我们为了能让二者相遇,需要保证left永远不会超过right,故在a[right] > a[keyi]之前加上left < right,即left < right && a[right] >= a[keyi],left也是一样的道理:

void QuickSort(int* a, int begin, int end)
{int left = begin, right = end;int keyi = begin;while (left < right){// 右边找小while (left < right && a[right] > a[keyi]){--right;}// 左边找大while (left < right && a[left] < a[keyi]){++left;}Swap(&a[left], &a[right]);}Swap(&a[left], &a[keyi]);
}

2、产生原因:当我们将基准元素换到数组的中的某个位置时,它左侧的元素经过一系列检查与交换的操作后已经全部是小于基准元素的元素,右边的元素也已经全部是大于基准元素的元素,现在我们要做的就是将左右两边的元素均变为有序,当左右两边均有序时该数组就完全有序(原因自己想去😡)这就需要用到递归思想了(在前面我们说过hoare版本的快排是基于二叉树思想的),当我们尝试对上面的数组开始递归操作时,如果还是原来的代码就会出现下图所示的问题:

       

         可以发现, 原本我们是想通过右递归将右侧大于基准元素的几个元素变为有序,但可以看到的是只有当a[right] == 4时才会停下,此时就已经在左递归的范围内了,因此为了保证不会越界,我们还需要为a[right] > a[keyi]加上一个"=",即a[right] >= a[keyi],左递归也是一样的道理:

void QuickSort(int* a, int begin, int end)
{int left = begin, right = end;int keyi = begin;while (left < right){// 右边找小while (left < right && a[right] >= a[keyi]){--right;}// 左边找大while (left < right && a[left] <= a[keyi]){++left;}Swap(&a[left], &a[right]);}Swap(&a[left], &a[keyi]);
}

中级实现

至此,我们开始进行递归操作,关于递归的过程如下图所示:

关于递归的代码也不再过多解释,自行理解即可: 

void QuickSort(int* a, int begin, int end)
{if (begin >= end)return;int left = begin, right = end;int keyi = begin;while (left < right){// 右边找小while (left < right && a[right] >= a[keyi]){--right;}// 左边找大while (left < right && a[left] <= a[keyi]){++left;}Swap(&a[left], &a[right]);}Swap(&a[left], &a[keyi]);keyi = left;// [begin, keyi-1] keyi [keyi+1, end]QuickSort(a, begin, keyi - 1);QuickSort(a, keyi+1, end);
}

以上就是一个“较为”完整的快速排序的代码 

时空复杂度 

最坏时间复杂度:O(N^2)(当数组已经降序有序或升序有序时,此时基准元素一直位于首元素或尾元素,n个元素要进行n次快速排序才能将当前的顺序改变,n*n)

最好时间复杂度:O(N*logN)每次划分都能将数组均匀地分成两个接近子数组,N个元素要进行logN次的排序,N*logN

空间复杂度:O(logN)或O(N)(在递归过程中需要使用栈来保存函数调用信息,所以快速排序的空间复杂度取决于递归调用的层数。在最坏情况下,递归调用栈可能达到O(n)的空间复杂度,最好的空间复杂度为O(logn))

高级实现

        当面对有序队列时,快速排序的效率确实会降低。这是因为快速排序的分区操作通常选择一个基准元素,并将小于等于基准的元素放在左侧,大于基准的元素放在右侧。如果输入数据已经有序,那么每次分区后只能将一个元素移到正确位置上,而剩余部分仍然需要进行递归调用。为了应对这种情况,可以采取以下方法来提高快速排序在有序队列上的效率:

  1. 随机化选择基准:通过随机选择基准值可以降低出现最坏情况(即已经有序)的概率。这样可以增加快速排序处理无序数据时的性能。

  2. 三数取中法:使用三数取中法来选择合适的基准值。从待排序数组中选取头、尾和中间位置上的三个数,并将它们按照大小顺序排列。然后选取其中位数作为划分子数组(即作为枢纽),以避免最坏情况发生。

  3. 插入排序优化:当待排序子数组长度较小时(比如小于某个阈值),可以切换到插入排序算法进行处理。插入算法对局部有序数据表现良好,在长度较短的子数组上可以提高排序效率。

  4. 优化递归调用:通过限制递归深度或者使用尾递归优化等方法,减少对有序数据的不必要处理。

        这些方法可以在特定情况下提高快速排序算法在有序队列上的性能,但需要根据具体场景选择合适的策略。

三数取中 

注意事项:获取的是下标为begin、midi、end的三个元素中的中位数(非最多,非最小)

完整代码如下:

int GetMidi(int* a, int begin, int end)
{int midi = (begin + end) / 2;// begin midi end 三个数选中位数if (a[begin] < a[midi]){if (a[midi] < a[end])return midi;   //返回a[midi] < a[midi] < a[end]else if (a[begin] > a[end])return begin;  //返回a[end] < a[begin] < a[midi]elsereturn end;    //返回a[begin] < a[end] < a[midi]}else // a[begin] > a[midi]{if (a[midi] > a[end])return midi;   //返回a[end] < a[mid] < a[begin]else if (a[begin] < a[end])return begin;  //返回a[midi] < a[begin] < a[end]else return end;    //返回a[midi] < a[end] < a[begin]}
}void QuickSort(int* a, int begin, int end)
{if (begin >= end)return;int midi = GetMidi(a, begin, end);Swap(&a[midi], &a[begin]);int left = begin, right = end;int keyi = begin;while (left < right){// 右边找小while (left < right && a[right] >= a[keyi]){--right;}// 左边找大while (left < right && a[left] <= a[keyi]){++left;}Swap(&a[left], &a[right]);}Swap(&a[left], &a[keyi]);keyi = left;// [begin, keyi-1] keyi [keyi+1, end]QuickSort(a, begin, keyi - 1);QuickSort(a, keyi+1, end);
}

~over~

这篇关于快速排序的hoare法的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



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

相关文章

大数据小内存排序问题如何巧妙解决

《大数据小内存排序问题如何巧妙解决》文章介绍了大数据小内存排序的三种方法:数据库排序、分治法和位图法,数据库排序简单但速度慢,对设备要求高;分治法高效但实现复杂;位图法可读性差,但存储空间受限... 目录三种方法:方法概要数据库排序(http://www.chinasem.cn对数据库设备要求较高)分治法(常

Python中lambda排序的六种方法

《Python中lambda排序的六种方法》本文主要介绍了Python中使用lambda函数进行排序的六种方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们... 目录1.对单个变量进行排序2. 对多个变量进行排序3. 降序排列4. 单独降序1.对单个变量进行排序

shell脚本快速检查192.168.1网段ip是否在用的方法

《shell脚本快速检查192.168.1网段ip是否在用的方法》该Shell脚本通过并发ping命令检查192.168.1网段中哪些IP地址正在使用,脚本定义了网络段、超时时间和并行扫描数量,并使用... 目录脚本:检查 192.168.1 网段 IP 是否在用脚本说明使用方法示例输出优化建议总结检查 1

关于Java内存访问重排序的研究

《关于Java内存访问重排序的研究》文章主要介绍了重排序现象及其在多线程编程中的影响,包括内存可见性问题和Java内存模型中对重排序的规则... 目录什么是重排序重排序图解重排序实验as-if-serial语义内存访问重排序与内存可见性内存访问重排序与Java内存模型重排序示意表内存屏障内存屏障示意表Int

Rust中的Option枚举快速入门教程

《Rust中的Option枚举快速入门教程》Rust中的Option枚举用于表示可能不存在的值,提供了多种方法来处理这些值,避免了空指针异常,文章介绍了Option的定义、常见方法、使用场景以及注意事... 目录引言Option介绍Option的常见方法Option使用场景场景一:函数返回可能不存在的值场景

电脑桌面文件删除了怎么找回来?别急,快速恢复攻略在此

在日常使用电脑的过程中,我们经常会遇到这样的情况:一不小心,桌面上的某个重要文件被删除了。这时,大多数人可能会感到惊慌失措,不知所措。 其实,不必过于担心,因为有很多方法可以帮助我们找回被删除的桌面文件。下面,就让我们一起来了解一下这些恢复桌面文件的方法吧。 一、使用撤销操作 如果我们刚刚删除了桌面上的文件,并且还没有进行其他操作,那么可以尝试使用撤销操作来恢复文件。在键盘上同时按下“C

【数据结构】——原来排序算法搞懂这些就行,轻松拿捏

前言:快速排序的实现最重要的是找基准值,下面让我们来了解如何实现找基准值 基准值的注释:在快排的过程中,每一次我们要取一个元素作为枢纽值,以这个数字来将序列划分为两部分。 在此我们采用三数取中法,也就是取左端、中间、右端三个数,然后进行排序,将中间数作为枢纽值。 快速排序实现主框架: //快速排序 void QuickSort(int* arr, int left, int rig

usaco 1.3 Mixing Milk (结构体排序 qsort) and hdu 2020(sort)

到了这题学会了结构体排序 于是回去修改了 1.2 milking cows 的算法~ 结构体排序核心: 1.结构体定义 struct Milk{int price;int milks;}milk[5000]; 2.自定义的比较函数,若返回值为正,qsort 函数判定a>b ;为负,a<b;为0,a==b; int milkcmp(const void *va,c

hdu 1285(拓扑排序)

题意: 给各个队间的胜负关系,让排名次,名词相同按从小到大排。 解析: 拓扑排序是应用于有向无回路图(Direct Acyclic Graph,简称DAG)上的一种排序方式,对一个有向无回路图进行拓扑排序后,所有的顶点形成一个序列,对所有边(u,v),满足u 在v 的前面。该序列说明了顶点表示的事件或状态发生的整体顺序。比较经典的是在工程活动上,某些工程完成后,另一些工程才能继续,此时

hdu 4565 推倒公式+矩阵快速幂

题意 求下式的值: Sn=⌈ (a+b√)n⌉%m S_n = \lceil\ (a + \sqrt{b}) ^ n \rceil\% m 其中: 0<a,m<215 0< a, m < 2^{15} 0<b,n<231 0 < b, n < 2^{31} (a−1)2<b<a2 (a-1)^2< b < a^2 解析 令: An=(a+b√)n A_n = (a +