首页 > 其他分享 >快速排序的深入优化探讨

快速排序的深入优化探讨

时间:2024-09-07 20:53:47浏览次数:6  
标签:right end cur int 探讨 key 排序 优化 left

目录

1. 快排性能的关键的分析:

1.1 三路划分算法思想:

1.2 三路划分的快排

1.3 introsort的快排


1. 快排性能的关键的分析:

决定快排性能的关键点是每次单躺排序后,key对数组的分割,如果每次选key基本二分居中,那么快排的递归树就是可均匀的满二叉树,性能最佳。但是实践中虽然不可能每次都是二分居中,但是性能也是可控的。但是如果每次选到最小值/最大值,划分成0个和N-1个子问题时,时间复杂度为O(N^2),数组序列有序就会出现这样的问题,我们前面已经使用三数取中或者随机数选key解决了这个问题,也就是说我们解决了绝大多数的问题,但是现在还是有一些场景没解决(数组中有大量重复数据时)。

//数组中有多个和key相等的值
int a1[] = { 6,1,8,6,6,6,6,4,9 };
int a2[] = { 3,2,3,3,3,3,3,2,3 };
//数组中全是相同的值
int a3[] = { 2,2,2,2,2,2,2,2,2 };

1.1 三路划分算法思想:

我们之前选key值,比key大的在右边,比key小的在左边,那么和key相等的值并没有规定,也就是说可以在左边也可以在右边,那么三路划分就规定了和key相等的值要放在中间。所谓三路就指的是左边,右边,和中间。

当面对有大量跟key相同的值时,三路划分的核心思想有点类似hoare的左右指针和lomuto的前后指针的结合。核心思想就是把数组中的数据分为3段,比key小的值,跟key相等的值,比key大的值,所有叫做三路划分。

  1. key默认取left位置的值。
  2. left指向区间最左边,right指向区间最右边,cur指向left+1位置。
  3. cur遇到比key小的值后跟left位置交换,换到左边,left++,cur++。
  4. cur遇到比key大的值后跟right位置交换,换到右边,right--。
  5. cur遇到跟key相等的值后,cur++。
  6. 直到cur>right结束。

. - 力扣(LeetCode). - 备战技术面试?力扣提供海量技术面试资源,帮助你高效提升编程技能,轻松拿下世界 IT 名企 Dream Offer。icon-default.png?t=O83Ahttps://leetcode.cn/problems/sort-an-array/这个OJ,当我们用快排的时候,lomuto的方法过不了这个题目,hoare版本可以过这个题目。堆排序和归并和希尔是可以过的,其他几个O(N^2)也过不了,因为这个题的测试用例中部仅仅有数据很大的数组,也有一些特殊数据的数组,如大量重复数据的数组。堆排序和归并排序和希尔不是很受数据样本的分布和形态的影响,但是快排会,因为快排要选key,每次key都当趟分割都很偏,就会出现效率退化问题。

lomuto的前后指针面对大量重复数据时,效率会退化,hoare版本会好很多,所以hoare版本是可以过这个OJ的,但是OJ还是一个相对局限的测试,就像Leetcode官方刚开始写的答案是lomuto,说明那会lomuto是可以过的,后面加了大量重复数据的测试用例,所以就过不了。那么hoare现在可以过,leetcode哪天增加了一个特殊用例以后就过不了,三路划分也类似,因为他们的思想还是在特殊场景下效率会退化,比如大多数选key都是接近最小值或者最大值,导致划分不均衡,效率退化。

  1. introsort是由David Musser在1997年设计的排序算法,C++ sgi STL sort中用的introspectivesort(内省排序)思想实现的。内省排序可以认为不受数据分布的影响,无论什么原因划分不均匀,导致递归深度太深,它就转换成堆排了,堆排不受数据分布影响。
  2. 其次三路划分针对有大量重复数据时,效率很好,其他场景就一般,但是三路划分思想还是很有价值的,有些快排思想变形体,要用划分去选数,他能保证跟key相等的数都排到中间去,三路划分的价值就体现出来了。

1.2 三路划分的快排

代码:

/**
 * Note: The returned array must be malloced, assume caller calls free().
 */
void Swap(int* x,int* y)
{
    int z = *x;
    *x = *y;
    *y = z;
}
void QuickSort(int* arr,int left,int right)
{
    if(left >= right) return;
    int begin = left;
    int end = right;

    //随机数选k,如果数据有序的情况下就保证k不是最小的
    int randi = left + (rand() % (right-left + 1));
    Swap(&arr[left], &arr[randi]);

    int key = arr[left];
    int cur = left+1;
    while(cur <= right)
    {
        if(arr[cur] < key)
        {
            Swap(arr+cur,arr+left);
            left++;
            cur++;
        }
        else if(arr[cur] > key)
        {
            Swap(arr+cur,arr+right);
            right--;
        }
        else cur++;
    }
    QuickSort(arr,begin,left-1);
    QuickSort(arr,right+1,end);
}
int* sortArray(int* nums, int numsSize, int* returnSize) {
    srand(time(NULL));
    QuickSort(nums,0,numsSize-1);
    *returnSize = numsSize;
    return nums;
}

1.3 introsort的快排

intrsort是introspective sort采用了缩写,他的名字其实表达了他的实现思路,他的思路就是进行自我侦测和反省,快排递归深度太深(sgi stl中使用的是深度为2倍排序元素数量的对数值)那就说明在这种数据序列下,选key出现了问题,性能在快速退化,那么就不要再进行快排分割递归了,改换成堆排序进行排序。

void Swap(int* x, int* y)
 {
    int tmp = *x;
    *x = *y;
    *y = tmp;
 }
 void AdjustDown(int* a, int n, int parent)
 {
    int child = parent * 2 + 1;
    while (child < n)
    {
        // 选出左右孩⼦中⼤的那⼀个
        if (child + 1 < n && a[child + 1] > a[child])
        {
            ++child;
        }
        if (a[child] > a[parent])
        {
            Swap(&a[child], &a[parent]);
            parent = child;
            child = parent * 2 + 1;
        }
        else
        {
            break;
        }
    }
 }
void HeapSort(int* a, int n)
{
    // 建堆-- 向下调整建堆 -- O(N) 
    for (int i = (n - 1 - 1) / 2; i >= 0; --i)
    {
        AdjustDown(a, n, i);
    }
    // ⾃⼰先实现 -- O(N*logN) 
    int end = n - 1;
    while (end > 0)
    {
        Swap(&a[end], &a[0]);
        AdjustDown(a, end, 0);
        --end;
    }
 }
void InsertSort(int* a, int n)
{
    for (int i = 1; i < n; i++)
    {
        int end = i-1;
        int tmp = a[i];
        // 将tmp插⼊到[0,end]区间中,保持有序
        while (end >= 0)
        {
            if (tmp < a[end])
            {
                a[end + 1] = a[end];
                --end;
            }
            else
            {
                break;
            }
        }
        a[end + 1] = tmp;
    }
}
void IntroSort(int* a, int left, int right, int depth, int defaultDepth)
{
    if (left >= right)
        return;
    
    // 数组⻓度⼩于16的⼩数组,换为插⼊排序,简单递归次数
 
    if(right - left + 1 < 16)
    {
        InsertSort(a+left, right-left+1);
        return;        
    }
    // 当深度超过2*logN时改⽤堆排序
    if(depth > defaultDepth)
    {
        HeapSort(a+left, right-left+1);
        return;
    }
    //递归层数
    depth++;
    int begin = left;
    int end = right;
    //随机数选k 
    int randi = left + (rand() % (right-left + 1));
    Swap(&a[left], &a[randi]);
    int prev = left;
    int cur = prev + 1;
    int keyi = left;
    //lomuto前后指针 
    while (cur <= right)
    {
        if (a[cur] < a[keyi] && ++prev != cur)
        {
            Swap(&a[prev], &a[cur]);
        }
        ++cur;
    }
    Swap(&a[prev], &a[keyi]);
    keyi = prev;
    // [begin, keyi-1] keyi [keyi+1, end]
    IntroSort(a, begin, keyi - 1, depth, defaultDepth);
    IntroSort(a, keyi+1, end, depth, defaultDepth);
}
void QuickSort(int* a, int left, int right)
{
    int depth = 0;
    int logn = 0;
    int N = right-left+1;
    for(int i = 1; i < N; i *= 2)
    {
        logn++;
    }
    // introspective sort -- ⾃省排序
    //这里使用2倍的logn的话比较合适,3倍的logn和1倍的logn也是可以的
    IntroSort(a, left, right, depth, logn*2);
}
int* sortArray(int* nums, int numsSize, int* returnSize){
    //设置随机数的种子
    srand(time(0));
    QuickSort(nums, 0, numsSize-1);
    *returnSize = numsSize;
    return nums;
}

标签:right,end,cur,int,探讨,key,排序,优化,left
From: https://blog.csdn.net/m0_74271757/article/details/141995227

相关文章

  • [Mysql]慢查询优化
    慢查询可能的原因SQL没加索引很多时候,我们的慢查询,都是因为没有加索引。如果没有加索引的话,会导致全表扫描的。因此,应考虑在where的条件列,建立索引,尽量避免全表扫描。反例:select*fromuser_infowherename='捡田螺的小男孩公众号';正例://添加索引altertableuser_......
  • [c++][笔记]浅谈几种排序方式---冒泡排序,选择排序,桶排序
     一、algorithm里的sort函数 #include<cstdio>//数据小的可以用iostream#include<algorithm>//不能忘记算法库,否则会编译失败。usingnamespacestd;intmain(){intn;scanf("%d",&n);inta[n+5]={};for(inti=1;i<=n;i++){......
  • 深度学习实战4--GAN进阶与优化
            GAN  的问题主要有两点:Loss 等于0的梯度消失问题和梯度不稳定以及多样性受损。前者是因为选择的分布函数使用JS距离,这个距离不能衡量两个不相交的分布的距离;后者是因为Loss  函数要求KL距离最小,JS 距离最大,所以梯度不稳定,而且 Loss 函数对正确率要......
  • 『功能项目』项目优化 - 默认管线转URP【31】
    打开上一篇30状态模式转换场景的项目,进入战斗场景本章要做的事情是将默认(普通)管线项目转成URP渲染管线后,更换场景首先在资源商店里导入一个免费的URP场景双击外包的场景资源是粉色(说明普通管线不支持URP渲染管线场景)接下来我们通过配置将默认管线项目升级到URP管线......
  • 【题解】【结构体排序】——[NOIP2007 普及组] 奖学金
    【题解】【结构体排序】——[NOIP2007普及组]奖学金[NOIP2007普及组]奖学金题目背景题目描述输入格式输出格式输入输出样例输入#1输出#1输入#2输出#21.题意解析2.AC代码[NOIP2007普及组]奖学金通往洛谷的传送门题目背景NOIP2007普及组T1题目描述某......
  • 淘宝客APP的架构设计与性能优化
    淘宝客APP的架构设计与性能优化大家好,我是微赚淘客返利系统3.0的小编,是个冬天不穿秋裤,天冷也要风度的程序猿!淘宝客APP作为一种电商推广工具,其架构设计和性能优化对于用户体验至关重要。本文将探讨淘宝客APP的架构设计以及如何进行性能优化。1.架构设计淘宝客APP的架构......
  • 深入探讨 TON 中的 get 方法:与其他方法的区别
    在TON(TheOpenNetwork)的智能合约开发中,理解不同类型方法的用途和限制对于编写高效的合约至关重要。本文将聚焦于 get 方法,与其他方法的对比,帮助你更好地在合约中使用这些工具。什么是 get 方法?get 方法是TON智能合约中的一种特殊的只读方法,用于读取数据而不改变合约状......
  • 五子棋AI:实现逻辑与相关背景探讨(上)bu
    合集-五子棋AI:遗传算法(1)1.五子棋AI:实现逻辑与相关背景探讨(上)09-07收起绪论本合集将详细讲述如何实现基于群只能遗传算法的五子棋AI,采用C++作为底层编程语言本篇将简要讨论实现思路,并在后续的文中逐一展开了解五子棋五子棋规则五子棋是一种经典的棋类游戏,规则简单却充......
  • 迷宫,返回所有路径并排序(C++)(回溯+dfs)
    题意说明:要求给出一个m*n的矩阵迷宫,0代表入口,1代表道路,2代表墙体,3代表出口,要求返回从入口到出口的所有移动路径并按长短从小到大排序。移动路径:就是wasd的移动路径,如一共走三步:下,右,下,则输出:sds。代码如下:#include<iostream>#include<string>#include<vector>#include<alg......
  • 基于贝叶斯算法优化回声状态网络(BO-ESN/Bayes-ESN)的数据多特征分类预测 Matlab代码+
    ......