[前端]_三分钟理解计数排序(附带js代码)

467 阅读3分钟

「这是我参与11月更文挑战的第1天,活动详情查看:2021最后一次更文挑战

排序方式千千万,到底哪个才是最牛逼的?

我想我今天终于找到了答案:没有最牛逼的算法, 只有最适合的场景。

今天给大家带来的计数排序,时间复杂度上是O(n + k); 其中k代表着数组最大值 - 最小值的差;

这意味着计数排序适合什么时候使用呢?当然是数值越接近的数组越适合用计数排序。

我们直接来看个例子, [1, 2, 2, 2, 3, 3, 5, 4, 2, 3, 4, 3]

像咱们拿到这个数组,想对它进行排序的时候,想要快,第一是想到能不能统计每个元素出现的次数,然后对去重后的数组进行排序。 如: map = { 1: 1, 2: 4, 3: 4, 4: 2, 5: 1}, 然后对键值 1-5进行排序就只需要比对五个数字了。但是很遗憾,查阅了MDN中对Object和Map的解释中发现键的顺序并不能自动帮我们排好。而是会以插入的顺序记录。这意味着我们要对键值再做一轮排序,太复杂了不看了不看了。

image.png

那有没有其他方法呢?突然联想到数组的索引,是已经排好序的了。而且是不会改变的,这就是计数排序的原理。

我们可以通过一个for循环。把每个元素出现的次数记录在它对应的索引上,但是一开始我们并不知道应该要创建一个多大的数组, 于是我们又继续去找到数组的最大值,创建数组,记录每个元素出现的次数,相关代码如下:

    // for (let i = 0; i < arr.length; i++) {
    //     if (arr[i] > maxValue) {
    //         maxValue = arr[i];
    //     }
    // }
    let maxValue = Math.max(...arr); // es6的骚操作, 跟上面的代码意思一样

    // 因为数组是从0开始的,我们要记录第n个元素需要创建长度为n+1的数组
    let array = new Array(maxValue + 1).fill(0);
    
    // 记录出现的次数
    for (let i = 0; i < arr.length; i++) {
        array[arr[i]] = (array[arr[i]] || 0) + 1;
    }

然后遍历一下我们创建的这个数组去取值即完成了排序

    let result = [];
    for (let i = 0; i < array.length; i++) {
        let cur = array[i];

        while (cur) {
            result.push(i);
            cur--;
        }
    }

相应的流程图是这样子的

countingSort.gif

这就是计数排序的原理,懂了之后你会发现: 就这?

实际上每个算法本身并不难,难的是你怎么把一个大的问题通过分治的思想转换成若干个小问题。然后在合适的场景用上合适的算法,像计数排序如果你拿到就用, 在[1, 5, 100, 7, 23]这样的数组中,你会发现性能不止没有提高反而大大降低了。

完整代码如下:

function countSort(arr) {
    let maxValue = Math.max(...arr); // es6的骚操作

    // 因为数组是从0开始的,我们要记录第n个元素需要创建长度为n+1的数组
    let array = new Array(maxValue + 1).fill(0);

    for (let i = 0; i < arr.length; i++) {
        array[arr[i]] = (array[arr[i]] || 0) + 1;
    }

    let result = [];
    for (let i = 0; i < array.length; i++) {
        let cur = array[i];

        while (cur) {
            result.push(i);
            cur--;
        }
    }

    return result;
}

今日的算法解析就到这吧,喜欢的朋友点个赞谢谢!有什么好的建议欢迎在评论区留言讨论,大家一起学算法,从小白向前冒泡,卷死他们!