排序到底怎样才算牛
或许有人觉得堆排牛,有人觉得快排牛,因为他们的平均时间复杂度在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 ;
}
🐂的快排+无监督插入排序=较🐂的排序
什么??插排这种老生常谈的排序还能再优化,大概是有点吃惊的。实质上插排的边界判断是可以避免的 杨村长写的插排【逃
实质上,大部分人的插排都是大抵如此这么写。但是免不了会有图中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 ;
}
无监督插排+堆排+🐂的快排=我认为🐂的排序
这个就留给大家实现吧,哎嘿,其实刚开始就给大家说了思路。 今天就这样结束