“掌握更多的快速排序技巧:三路划分、双路快排和非递归的深入理解”(中)

简介: “掌握更多的快速排序技巧:三路划分、双路快排和非递归的深入理解”(中)

前后指针版本


基本思路::需要两个指针,一个指针命名cur,一个指针命名prev;


1.选择数组的第一个元素下标作为基准值key。

2.初始化两个指针cur和prev,分别指向数组的起始位置和起始位置的下一个位置。

3.当cur遇到比keyi的大的值以后,只需要++cur,因为他们之间的值都是比key大的值,

4.如果cur指针指向的元素小于基准值,先将prev指针向右移动一位,然后在将快指针指向的元素与慢指针指向的元素交换。

5.重复步骤3到步骤4,直到cur指针超出数组范围。结束循环.

6.将基准值的元素与prev指针位置的元素交换。此时基准值的左边元素都是比基准值小或者等于基准值,右边都比他大或者等于基准值。

7.最后返回prev下标。


单趟排序:


  • 为了更仔细的观看,我自己手动模拟一下,

6773712cb5d9414299c67a71b373b85e.png

3a8c52eefc484600877b59bd80fa3296.png

fd58579e383d44d6b305c1ce4f5f1fe8.png


40427b998eca4157813e532d7f055633.png

18f878fed22e4673a93429902639f5ca.png



d9f635b83d1d425ea05a4b97fe101d84.png


0ce8db5cd73844e5966acaa076c31da7.png

f03b0ff153c74afe8009644aac86d754.png

14e8df4714774dafb08d3dea2c082a8d.png


a982a2c364134fcfacb508209a4a5cc9.png


7b93087e6fc84bd182e93033f1aa4616.png


  • 代码实现


// 前后指针版本
int Part_Sort3(int* a, int left, int right)
{
    int keyi = left; // 基准值的索引
    int prev = left; // 前指针
    int cur = left + 1; // 后指针
    while (cur <= right)
    {
    //如果当前元素小于基准值,将其与前指针指向的元素交换,并移动前指针
        if (a[cur] < a[keyi])
        {
          //这样写每次相同的元素都要交换 
            ++prev;
            swap(&a[prev], &a[cur]);
        }
        //可以优化成这样,这样相同下标位置的值就不用交换
    /*if (a[cur] < a[keyi] && ++prev != cur)
        {
            swap(&a[prev], &a[cur]);
        }*/
        ++cur;
    }
    swap(&a[prev], &a[keyi]); // 将基准值放到正确的位置上
    return prev; // 返回基准值的索引
}


以上代码大家可以自己手动模拟一下,配合着代码相信你们会更加能吃透.


三路划分版本



快速排序的三路划分是为了解决数组中存在大量重复元素时,快速排序算法性能下降的问题。在传统的快速排序算法中,选择一个基准元素,将数组划分为两个子数组,其中一个子数组中的元素都小于基准元素,另一个子数组中的元素都大于基准元素,然后对两个子数组进行递归排序。


然而,当数组中存在大量重复元素时,传统的快速排序算法会导致不必要的比较和交换操作,从而降低算法的效率。三路划分的主要目的是将数组划分为三个部分,分别存放小于、等于和大于基准元素的元素,以减少不必要的比较和交换操作。


通过三路划分,可以将相等的元素集中在一起,减少了对相等元素的重复比较和交换操作,提高了算法的效率。尤其在面对存在大量重复元素的情况下,三路划分可以有效地改善快速排序的性能。


三路划分本质:

1、小的甩到左边,大的甩到右边

2、跟key相等的值推到中间


283a286fc22a43219186cfa48d43f2e7.png

  • 三路划分的基本思想是将数组分成三个部分,分别存放小于、等于和大于基准元素的元

素。


基本思路:


1.选择一个基准元素。(通常是数组的第一个元素)

2.初始化三个指针:begin指针指向基准值的索引位置,cur指针指向begin + 1的位置,end指针指向数组末尾的位置

3.从数组的起始位置开始遍历到末尾位置。

4.a[c] < key如果当前元素小于基准元素,则将当前cur指针指向的元素交换到begin指针的位置,并将begin指针右移,cur指针右移。

5.a[c] > key如果当前cur指针元素大于基准元素,则将当前cur指针指向元素交换到end指针的位置,并将end指针左移。由于交换后的元素是未经比较的新元素,所以cur指针不移动。

6.a[c] == key如果当前元素等于基准元素,则将cur指针右移。

7.重复步骤4-6,直到cur指针遇见end指针则遍历完成。循环结束。

8.最后, 数组被划分为了小于基准元素、等于基准元素和大于基准元素的三个部分。接下来,需要对小于和大于基准元素的两个部分分别进行递归排序。

  • 对小于基准元素的部分进行递归排序:将小于基准元素的部分作为新的子数组,重复进行

上述三路划分和递归排序的过程。

  • 对大于基准元素的部分进行递归排序:将大于基准元素的部分作为新的子数组,重复进行

上述三路划分和递归排序的过程。


  • 代码实现 (因为有可能 划分 出来是一个区间,我就直接在一个函数里面操作了,不封装

其他函数来完成了)


  • 为了更仔细的体会,我自己手动模拟一下一组数据,


95166e38780640549838c3f26d73db3b.png


491b35ae45694932bc9f98c13d0cff1d.png



c8da6f78fc234412a42bc26f5401981e.png

dc4e54ab3a97478196eff5773f11f369.png


20eaeee534924661953bdf4851ef5497.png

92421b7d4cba47d4a780a5bc751ddcd0.png

20d4a3e680aa4fe9aaa9ff70071e37c8.png

2ef5f4017f9942a6864c3c55e56ca00a.png

6b371a6f608b48599598b43637fcc324.png


6c4f55ae249648368fec6e1c7de2c137.png



  • 最后对小于和大于基准元素的两个部分分别进行递归排序。


//三路划分版本:解决数组中存在大量重复元素
//三路划分本质:
//1、小的甩到左边,大的甩到右边
//2、跟key相等的值推到中间
void Quicl_Sors_Dfp(int* a, int left, int right)
{
    if (left >= right)
        return;  
    int key = a[left];
    int begin = left;
    int cur = left + 1;
    int end = right;
    while (cur < end)
    {
        //a[c] < key,交换c和b位置的值,++b,++c
        if (a[cur] < key)
        {
            swap(&a[cur], &a[begin]);
            ++cur;
            ++begin;
        }
        //a[c] > key,交换c和e位置的值,--e
        else if (a[cur] > key)
        {
            swap(&a[cur], &a[end]);
            --end;
        }
        //a[c] == key,++c
        else
        {
            ++cur;
        }
    }
    //小  【b - e 相同】  大
    //[left begin-1] [begin end] [end+1 right]
    Quicl_Sors_Dfp(a, left, begin - 1);
    Quicl_Sors_Dfp(a, end + 1, right);
}


目录
相关文章
|
7月前
|
算法
快排三种递归及其优化,非递归和三路划分
快排三种递归及其优化,非递归和三路划分
38 0
|
7月前
|
算法
算法:分治思想处理归并递归问题
算法:分治思想处理归并递归问题
|
1月前
|
算法 数据处理 C语言
【数据结构与算法】快速排序(详解:快排的Hoare原版,挖坑法和双指针法|避免快排最坏时间复杂度的两种解决方案|小区间优化|非递归的快排)
【数据结构与算法】快速排序(详解:快排的Hoare原版,挖坑法和双指针法|避免快排最坏时间复杂度的两种解决方案|小区间优化|非递归的快排)
|
3月前
|
搜索推荐 算法 C++
【递归版】归并排序算法(1)
【递归版】归并排序算法(1)
24 0
|
4月前
|
存储 算法 搜索推荐
【算法系列篇】分治-归并
【算法系列篇】分治-归并
|
4月前
|
算法 搜索推荐 Java
【算法系列篇】分治-快排
【算法系列篇】分治-快排
|
10月前
|
搜索推荐
“掌握更多的快速排序技巧:三路划分、双路快排和非递归的深入理解”(上)
“掌握更多的快速排序技巧:三路划分、双路快排和非递归的深入理解”(上)
76 0
|
5月前
|
搜索推荐
排序算法:快速排序(三种排序方式、递归和非递归)
排序算法:快速排序(三种排序方式、递归和非递归)
68 0
|
算法 搜索推荐
分治法实现合并排序(归并排序),理解分治算法思想,实现分治算法的完美例子合并排序(含码源与解析)
分治法实现合并排序(归并排序),理解分治算法思想,实现分治算法的完美例子合并排序(含码源与解析)
106 0
|
10月前
|
存储 搜索推荐 算法
“掌握更多的快速排序技巧:三路划分、双路快排和非递归的深入理解”(下)
“掌握更多的快速排序技巧:三路划分、双路快排和非递归的深入理解”(下)
82 0