[路飞]_比较🐂的一个排序

928 阅读2分钟

排序到底怎样才算牛

或许有人觉得堆排牛,有人觉得快排牛,因为他们的平均时间复杂度在O(nlogn) 但其实他们其实各有千秋。 堆排基本是稳定在O(nlogn),而快排最坏的情况会到O(n方),数据量很大的情况下,快排会略好一点,其实将两者结合在一起,就可以实现一个可能较牛的排序算法。 大概思路是在快排中搞一个计数的变量,来记录递归次数,在递归次数到达一个临界值的时候,在转换到堆排。 由此可见 单一排序算法 都不可能很牛,结合起来,才会很牛

我认为的最牛的排序

插入排序+快速排序+堆排 当然其实快排才是最大的那个骨架,那就一步一步把他变得🐂吧

朴素的快排怎么变得不朴素

基本原理 别的地方到处是,我直接show code

function quick_sort_v1(arr, l, r)
        {
        if (r-l<=0) {
            
            return;
        }
        let x = l;let y = r; let base = arr[l];
        //x为头指针,y为尾指针
        while (x < y) {
            //尾指针向前找
            while (x < y && arr[y] >= base) --y;
            if (x < y) arr[x++] = arr[y];
            //头指针向后找
            while (x < y && arr[x] <= base) ++x;
            if (x < y) arr[y--] = arr[x];
        }
        //头尾指针重合
        arr[x] = base;
        quick_sort_v1(arr, l, x - 1);
        quick_sort_v1(arr, x + 1, r);
        return ;
    }

好了 现在的事情 是让这个朴素的快排变得不朴素。那么有哪些优化方法呢?

  • 单边递归(节省空间复杂度)
  • 无监督partition
  • base值三点取中(随机取也可)

单边递归

上面快排的代码里会有两行递归的语句,快排左边,快排右边。这样有啥不好,其实速度上跟不用单边递归基本上一样,但会消耗很多的空间。单边递归实质上就是,一边用循环实现,一边用递归实现,这样就会省下很多的空间。 这样一听就觉得单边递归很好啊,但是平常基本上好像没见过,因为可读性太差了。

无监督partition

partition相信了解过快排思想的基本上都知道,但是无监督是啥? 说人话实质上就是省去边界判断。 为什么要省去边界判断呢?cpu执行一个顺序程序(没有if等分支语句)那是行云流水,非常快的,但是遇到一个if判断的分支,cpu就会突然刹车减速,变慢很多。从而允许时间会变多。如果边界判断很少,那程序肯定会变快。

base三点取中

上面朴素快排中 base值是默认取最左边的。如果左边一直都是快排区间最小或最大的那个值,那其实挺尴尬的,时间复杂度会接近n方。三点取中是在arr[l],arr[(l+r)/2],arr[r]中挑中间那个,反正不可能是最坏的那个,再坏也不会到O(n方)这个地步(正常的待排序数据的情况下)

朴素快排+单边递归+无监督+三点取中=🐂的快排

show the code

function quick_sort_v2(arr,  l,  r) {
    while (r - l >= 0) {
        let x = l;
        let y = r;
        let base = medium_number(arr, l, r)//三点取中这个函数就不展开了
        do {
            while (arr[x] < base) x++;
            while (arr[y] > base) y--;
            if (x <= y) {
                swap(arr[x], arr[y]);//两值交换也不展开了吧
                x++;
                y--;
            }
        } while (x <= y)
        quick_sort_v2(arr, l, y)
        l = x;
    }
    return ;
}

🐂的快排+无监督插入排序=较🐂的排序

什么??插排这种老生常谈的排序还能再优化,大概是有点吃惊的。实质上插排的边界判断是可以避免的 杨村长写的插排【逃

QQ20211102-0.png 实质上,大部分人的插排都是大抵如此这么写。但是免不了会有图中while里判断preIndex(前驱索引)越界的问题。 怎么实现无监督呢,其实也很容易,先让第一个强行成为最小值,在让图中的i从2开始就行了,cpu就可以畅通无阻的进行排序,不考虑越界了 下面是无监督插排的code

function unguard_final_insert_sort(arr, l,  r) {
            for(let cx=l+1;cx<=r;cx++)//为什么叫cx 因为最近在“预习汇编”,发现cx里面放循环的次数
            {
                if(arr[cx]<arr[l])
                {
                    let temp=arr[l]
                    arr[l]=arr[cx]
                    arr[cx]=temp
                }
            }

            let preIndex,current
   for(let i=l+2;i<=r;i++)
   {
       preIndex=i-1
       current=arr[i]
       while(preIndex>=l&&arr[preIndex]>current)
       {
           arr[preIndex+1]=arr[preIndex];
        preIndex--;       }
        arr[preIndex+1]=current;
        
   }
    return ;
}

回到正题,为什么说无监督插排加上🐂的快排是一个较🐂的排序呢。因为快排处理乱序的速度是很快的,但是随着数据进行一次又一次的partition,数据会逐渐相对有序,快排就显得浪费表情,所以当分的区很小的时候,其实就可以用unguard_insert_sort来排序了。 结合后的代码如下:

function quick_sort_v2(arr,  l,  r) {
    while (r - l >= 16) {
        let x = l;
        let y = r;
        let base = medium_number(arr, l, r)//三点取中这个函数就不展开了
        do {
            while (arr[x] < base) x++;
            while (arr[y] > base) y--;
            if (x <= y) {
                swap(arr[x], arr[y]);//两值交换也不展开了吧
                x++;
                y--;
            }
        } while (x <= y)
        quick_sort_v2(arr, l, y)
        l = x;
    }
    if (l < r) unguarded_final_insert_sort(arr, l, r);
    return ;
}

无监督插排+堆排+🐂的快排=我认为🐂的排序

这个就留给大家实现吧,哎嘿,其实刚开始就给大家说了思路。 今天就这样结束