排序这种算法,是基础中的基础啊,不会也没关系,不要不好意思,慢慢来就好了,不懂装懂的人才傻呢!
本书的第一章讲了三个排序,各有特点,待我慢慢道来。
桶排序(简)
主要思想就是,桶的排列是有顺序的,桶的下标代表一个数,桶里的值代表这个数出现了多少次(比方说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)
最近会啃一啃算法,喜欢的话不妨支持一下,点个三连吧!关注不迷路~