数据结构与算法 - 快速排序

115 阅读3分钟

介绍

虽然叫它快速排序,但实际上,它的实现思路和上一章讲的冒泡排序并没有多大差别。
一句话总结思路:给定一个数组,对于数组中的第一个数,如果你能把比它大的数都放在他后面,比它小的数放在它前面,那么你就能给这整个数组排序。

排序数组中的一个数

如存在数组[6, 1, 5, 2, 4, 9, 8],我需要把6放在正确的位置,比它大的数放在它后面,比它小的数放在它前面,下面是代码实现:

代码

function sortOne(source) {
    if (source.length <= 1) return source;
    
    let maxThanSelf = [];
    let others = []
    for (let i = 1; i < source.length; i++) {
        if (source[i] > source[0]) {
            maxThanSelf.push(source[i]);
        } else {
            others.push(source[i]);
        }
    }
    
    return [...others, source[0], ...maxThanSelf];
}

代码的结构极其简单,找到比它大的数和其它数【等于或者小于它】,然后将它们组合在一起。
注意:这里的代码虽然很好理解,但是很浪费性能,你会发现中间创建了很多新数组,但这实际是不需要的,强烈建议去了解一下双指针版本【网上的代码快排代码会告诉你用双指针要怎么写】

下一步

  • 还是以[6, 1, 5, 2, 4, 9, 8]为例,把6排好后,数组会变为[1, 5, 2, 4, 6, 9, 8],或者说,我们可以把数组分为3部分,分别是[1, 5, 2, 4]【比6小】, 6, [9, 8]【比6大】。
  • [1, 5, 2, 4]为例,我们同样可以通过刚才的方式,把1放在正确的位置,又会出现3个部分:[],1,[5, 2, 4]。此时原数组将存在5个部分,分别是[],1,[5, 2, 4],6,[9, 8]
  • 可以发现,只要我们对数组长度大于1的部分,重复这个操作,我们就可以把数组细分,直到分为长度为1或长度为0,最后只要把所有部分再按照顺序连接起来,整个数组就排好了!

代码

其实我们只需要修改sortOne的一行代码即可

function sort(source) {
    if (source.length <= 1) return source;
    
    let maxThanSelf = [];
    let others = []
    for (let i = 1; i < source.length; i++) {
        if (source[i] > source[0]) {
            maxThanSelf.push(source[i]);
        } else {
            others.push(source[i]);
        }
    }
    
    // 只需要在这里,递归的去排序就行
    return [...sort(others), source[0], ...sort(maxThanSelf)];
}

注意: 这里和sortOne存在一样的问题,会频繁的创建新数组,修改方法和sortOne一样,将sortOne部分改成双指针版本,交换数据,而不是创建新数组
ps:

  • 当然,这里选择的是排好第一个数,实际上你可以选取数组中的任意一个数

时间复杂度

  • 对于数据量n,把一个数的位置排好,复杂度是n
  • 排好一次后,数组被拆成3份,对这3份数据【实际上是2份,比它大(假设数量是m)和比它小(假设数量是k)的部分】,再分别重复上述操作,加起来的复杂度同样是n【m + k】
  • 那我们需要拆分多少次呢?
    • 最好的情况,第一个数的位置非常好,每次都能将数组平分,那我们需要拆log(以2为底)n次,如1024个数据,需要拆10次【理论上】
    • 最坏的情况,第一个数的位置非常糟糕,那拆了等于没拆【每次拆只拆了一个数据】,需要拆n-1次

所以,快速排序的算法复杂度是,最好O(nlogn),最差O(n2)

下一章

归并排序