• 【cpp】快速排序&优化


    标题:【cpp】快速排序

    @水墨不写bug


    正文开始:

     快速排序的局限性:

    虽然快速排序是一种高效的排序算法,但也存在一些局限性:

    1. 最坏情况下的时间复杂度:如果选择的基准元素不合适,或者数组中存在大量重复的元素,快速排序的时间复杂度可能退化为O(n^2)。这种情况发生在每次划分之后,基准元素都是当前子数组中的最小或最大元素。

    2. 对于小规模数据的排序效果较差:当数组规模较小时,例如只有几个元素,快速排序的性能可能不如其他简单的排序算法,如插入排序或选择排序。

    3. 对于有序数组的排序效果较差:如果待排序的数组已经接近有序,快速排序的性能会下降。因为在划分过程中,基准元素的选择可能会导致子数组的不平衡,使得算法执行的时间增加。

    4. 快速排序是一种不稳定的排序算法:当原始数组中存在相等元素时,算法执行过程中可能会改变它们的相对顺序。

            如何优化重复元素的问题,以免快排的时间复杂度退化为O(N^2)?

            通过下面这一问题,我们或许会得到一些启发。 

    (一)颜色分类(leetcode)

            给定一个包含红色、白色和蓝色、共 n 个元素的数组 nums ,原地对它们进行排序,使得相同颜色的元素相邻,并按照红色、白色、蓝色顺序排列。

            我们使用整数 0、 1 和 2 分别表示红色、白色和蓝色。

            必须在不使用库内置的 sort 函数的情况下解决这个问题。

    示例 1:

    输入:nums = [2,0,2,1,1,0]
    输出:[0,0,1,1,2,2]
    

    示例 2:

    输入:nums = [2,0,1]
    输出:[0,1,2]
    

    提示:

    • n == nums.length
    • 1 <= n <= 300
    • nums[i] 为 01 或 2

    进阶:

    • 你能想出一个仅使用常数空间的一趟扫描算法吗?

            颜色分类思想:

            定义三个指针(left,cur,right):

            在排序过程中,这三个指针(left,cur,right),将数组分为四个部分,接下来希望你拿出纸笔跟我一起画一下:

            四个部分:

            一:【0,left】 

            二:【left+1,cur-1】 

            三:【cur,right-1】 

            四:【right,len-1】  

    主体思路是:

            cur向后遍历,将小于1的元素放在left左边,等于1的元素不变位置,大于1的元素放在right的右边。

            需要注意的细节是,对于(nums[cur] == 0 )由于left是右闭区间,其指向的元素满足要求,所以要先自增再交换;交换之后cur++向后遍历。

            对于(nums[cur] == 1)cur直接向后遍历;

            对于(nums[cur] == 2)由于left是左闭区间,其指向的元素满足要求,所以要先自减再交换;right在交换之前指向的元素本身就是未遍历的元素,所以交换之后不需要cur自增,直接判断cur交换得到的未遍历的元素即可。

            

    1. class Solution {
    2. public:
    3. void sortColors(vector<int>& nums) {
    4. int n = nums.size();
    5. for(int left = -1,right = n,cur = 0;cur < right;)
    6. {
    7. if(nums[cur] == 0) swap(nums[++left],nums[cur++]);
    8. else if(nums[cur] == 1) cur++;
    9. else swap(nums[--right],nums[cur]);
    10. }
    11. }
    12. };

    (二)排序数组(leetcode)

            给你一个整数数组 nums,请你将该数组升序排列。

    示例 1:

    输入:nums = [5,2,3,1]
    输出:[1,2,3,5]
    

    示例 2:

    输入:nums = [5,1,1,2,0,0]
    输出:[0,0,1,1,2,5]
    

    提示:

    • 1 <= nums.length <= 5 * 10^4
    • -5 * 10^4 <= nums[i] <= 5 * 10^4

            根据颜色分类的主体思想,如果用颜色分类来优化快排,那么这时再出现大量重复元素,就可以一次将他们排好序,不会再出现 在每次划分之后,基准元素都是当前子数组中的最小或最大元素的情况。

    1. class Solution {
    2. public:
    3. vector<int> sortArray(vector<int>& nums) {
    4. srand(time(NULL));
    5. qsort(nums,0,nums.size()-1);
    6. return nums;
    7. }
    8. void qsort(vector<int>& nums,int l,int r)
    9. {
    10. //递归出口
    11. if(l >= r) return;
    12. //数组分三块
    13. int key = GetRandom(nums,l,r);
    14. int cur = l,left = l-1,right = r+1;
    15. while(cur < right)
    16. {
    17. if(nums[cur] < key) swap(nums[++left],nums[cur++]);
    18. else if(nums[cur] == key) cur++;
    19. else swap(nums[--right],nums[cur]);
    20. }
    21. //递归排序子区间
    22. qsort(nums,l,left);
    23. qsort(nums,right,r);
    24. }
    25. int GetRandom(vector<int>& nums,int left,int right)
    26. {
    27. int r = rand();
    28. return nums[ r % ( right - left + 1 ) + left];
    29. }
    30. };

    代码理解: 

             上述代码是用C++实现的快速排序算法,用于对一个整数数组进行排序。sortArray函数接收一个输入向量nums,并使用qsort函数对其进行排序。

    1.函数参数

    qsort函数是一个递归函数,用于执行快速排序算法。它接收三个参数:输入向量nums、左边界索引l和右边界索引r

    2.递归出口的定义

            函数有一个基本情况,即如果左边界索引大于或等于右边界索引,那么只有一个元素或没有元素需要排序,所以函数返回。

    3.key的选取

            使用GetRandom函数选择一个随机的轴元素(key),该函数在当前分区的范围内生成一个随机的索引。

    4.递归主体

            然后,函数将数组分为三部分:小于轴元素(key)的元素、等于轴元素(key)的元素和大于轴元素(key)的元素。它使用三个指针:curleftright

    cur指针从左边界索引开始,向右边界索引移动,并相应地交换元素。如果当前元素小于轴元素,则将其与left指针处的元素交换,并同时递增这两个指针。如果当前元素等于轴元素,则递增cur指针。如果当前元素大于轴元素,则将其与right指针处的元素交换,并递减right指针。

            分区完成后,函数在左子数组和右子数组上递归调用自身以进行排序。

    5.GetRandom函数

    GetRandom函数在给定的左边界和右边界索引范围内生成一个随机的索引。它使用rand函数生成一个随机数,然后通过对右边界和左边界索引之间的差值加一取模,并加上左边界索引来计算索引值。

    6.sortArray函数

            最后,在sortArray函数中,在开始排序过程之前,调用srand函数以用当前时间初始化随机数生成器,以确保每次运行程序时都会生成不同的随机数。

    然后返回排序后的数组。


    完~

    未经作者同意禁止转载

  • 相关阅读:
    趣学算法:贪心算法
    利用Python生成随机密码,灰常简单,小伙伴可以试试哟
    笨方法学Python
    Modbus CRC
    Linux C++静态库动态库以及链接方式介绍
    虚拟内存地址和物理内存地址?为什么我们程序里地址连续?为什么需要TLB Translation lookaside buffer
    终于还是熬不住了,转行了,分享一波刚学到的知识吧,字符串的自带函数.py
    maven exclude 排除文件夹
    5G+AI:IoT下半场的智能化底座
    常见web安全及防护原理
  • 原文地址:https://blog.csdn.net/2301_79465388/article/details/137427583