【第一章_排序】桶排序、冒泡排序、快速排序、去重+排序


排序这种算法,是基础中的基础啊,不会也没关系,不要不好意思,慢慢来就好了,不懂装懂的人才傻呢!

本书的第一章讲了三个排序,各有特点,待我慢慢道来。


桶排序(简)

在这里插入图片描述

主要思想就是,桶的排列是有顺序的,桶的下标代表一个数,桶里的值代表这个数出现了多少次(比方说book[3]=9 代表3这个数出现了九次)。

我们统计完这些数字的出现频率后,从左到右遍历所有的桶,然后输出(桶里的值)遍(桶的下标)即可。

这个东西描述起来真绕,实际操作一下就好理解多了。

const sortArr = [8, 100, 50, 22, 15, 6, 1, 1000, 999, 0, 0];

// 初始化桶
let book = new Array(1001).fill(0);

// 往桶里插旗子
for (let i = 0; i < sortArr.length; i++) {
    book[sortArr[i]]++;
}

let rst = [];
for (let j = 0; j < 1001; j++) {
    // 如果桶里有旗子,就拿出来一个旗子;
    while (book[j] > 0) {
        rst.push(j)
        book[j]--;
    }
}
console.log(rst);

在这里插入图片描述
这是从小到大,如果从大到小呢?
没错,反着读桶即可 for (let j = 1001; j >= 0; j--)


冒泡排序

如果是从小到大排序,大的数是不是在右边?(哦up废话,是不是制杖

那么我们每一趟的目的也就清楚了:把大数冒泡到右面

在这里插入图片描述

第一躺循环,我们把最大的数放到了数组的末尾,此时这个数组中的最大数已经放到右边了。
第二趟循环,我们把第二大的数放到了倒数第二的位置,所以内循环的条件是 j < arr.length - i

i趟循环,把第i大的数放到倒数第i的位置。

const arr = [8, 100, 50, 22, 15, 6, 1, 1000, 999, 0, 0];

for (let i = 0; i < arr.length; i++) {
    for (let j = 0; j < arr.length - i; j++) {
        if (arr[j] <= arr[j - 1]) {
        	// 交换位置
            [arr[j - 1], arr[j]] = [arr[j], arr[j - 1]]
        }
    }
}
console.log(arr);

快速排序

此排序非常重要的一个思路是:把基准数放到正确的位置,基准数我们可以随便取,那什么是正确的位置呢?

正确的位置是,左面的一堆数比这个数小,右边的一堆数比这个数大。

比方说我们要排序:[8, 100, 50, 22, 15, 6, 1, 1000, 999, 0, 0],以8为基准数。
那么给 8 找到正确的位置之后是这样的[6, 0, 0, 1, 8, 15, 22, 1000, 999, 50, 100]

这里引进一下原版书中的图片:
在这里插入图片描述
代码如下

const arr = [8, 100, 50, 22, 15, 6, 1, 1000, 999, 0, 0];

function quickSort(left, right) {
    const base = arr[left];
    // 哨兵节点
    let i = left;
    let j = right;

    // 递归结束的条件
    if (i > j) {
        return;
    }

    while (i < j) {
        // 找到右边第一个不大于base的数,退出此次循环
        while (arr[j] >= base && i < j) {
            j--;
        }

        // 找到左边第一个不小于
        while (arr[i] <= base && i < j) {
            i++;
        }

        // 交换两个数
        [arr[i], arr[j]] = [arr[j], arr[i]];
    }
    
    // 基准数归位
    [arr[left], arr[i]] = [arr[i], arr[left]];
    
    quickSort(left, i - 1);
    quickSort(i + 1, right);
}
quickSort(0, arr.length - 1);
console.log(arr);

一个月之后,回头又重新写了下快排,发现少了点细节,如下图所示
在这里插入图片描述
这两个细节少一个,结果都不对的。

又过去了几个月,我发现比较简洁的快排写法,非常的骚啊~

/*1*/ quickSort = (array) => {
/*2*/   if (array.length <= 1) { return array }
/*3*/   let [pivot, ...rest] = array
/*4*/   let small = rest.filter(i => i <= pivot)    // 把所小于他的数打包成数组
/*5*/   let big = rest.filter(i => i > pivot)
/*6*/   return [...quickSort(small), pivot, ...quickSort(big)]
/*7*/ }
console.log(quickSort([2,35,5,1,6,123]));

去重+排序

这一章的最后留了一道题目:数组去重+排序

书中有两种解法,我分别拿 Javascript 实现一遍

第一种:桶排序 (稍微那么变形)

const arr = [20, 40, 32, 67, 40, 20, 89, 300, 400, 15];
const book = new Array(401).fill(0);
for (let i = 0; i < arr.length; i++) {
    book[arr[i]]++;
}
let rst = [];
for (let i = 0; i < 401; i++) {
    if (book[i]) {
        rst.push(i);
    }
}
console.log(rst);

由此 get 到了桶排序的新用途:去重

另一种方法是先排序,然后在去重

const arr = [20, 40, 32, 67, 40, 20, 89, 300, 400, 15];
// 冒泡
for (let i = 0; i < arr.length; i++) {
    for (let j = 0; j < arr.length - i; j++) {
        if (arr[j] <= arr[j - 1]) {
            [arr[j - 1], arr[j]] = [arr[j], arr[j - 1]]
        }
    }
}
let rst = [arr[0]];	// 先把第一个数放进去
for (let i = 1; i < arr.length; i++) {
    if (arr[i] != arr[i - 1]) {
        rst.push(arr[i]);
    }
}
console.log(rst);

这个复杂度就比桶排序的高了,因为有两个for循环嘛


总结

  • 简版的桶排序,如果要排一个很大的数,比方说[0,99999],那么我们就要开十万个桶,太浪费空间了啊哥。占用空间很大,不过速度快,是以空间换时间的典型例子。
  • 快排据说研究了16年,的确很厉害
  • 冒泡也是很经典的算法了,上面的动图也比较直观的展现出全过程~

时间复杂度:

  • 桶排序: O ( N + M ) O(N+M) O(N+M)
  • 冒泡排序: O ( N ) 2 O(N)^2 O(N)2
  • 桶排序: O ( N l o g N ) O(NlogN) O(NlogN)

最近会啃一啃算法,喜欢的话不妨支持一下,点个三连吧!关注不迷路~

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值