文章目录
- 数组排序
- 选择排序
- 快速排序
- 冒泡排序
- 插入排序
- 桶排序
- sort 排序
数组排序
下面的所有排序,如果有对解释不好理解的,可以直接选择代入数组,自己梳理一下逻辑就会明白了。
也可以去Typescript的Playground 然后把代码拷贝进去自己跑一下。
这里说一个知识点:
if (array[j] > array[j + 1]) {
temp = array[j];
array[j] = array[j + 1];
array[j + 1] = temp;
}
大家都见过这样的代码吧,典型的值的交换,这里给大家提供一种简单方法:
if (array[j] > array[j + 1]) {
[array[j], array[j + 1]] = [array[j + 1], array[j]]
}
# 利用es6的解构赋值,不用定义空变量存储,直接交换。
# 当然,我下面的代码里,还是使用的是上面的旧的方法,好理解一点。
选择排序
选择排序:
- 首先从原始数组中找到最小的元素
- 然后把该元素放到数组的最前面,然后再从剩下的元素里寻找最小元素,放到之前最小元素的后面
- 以此类推,直到排序完毕。
选择排序示例:
const arr = [1, 2, 4, 3, 6, 5, 8, 7, 9];
let index: number, temp: number;
for (let i = 0; i < arr.length - 1; i++) {
index = i;
for (let j = i + 1; j < arr.length; j++) {
if (arr[j] < arr[index]) {
index = j
}
}
temp = arr[i];
arr[i] = arr[index];
arr[index] = temp;
}
console.log(arr);
# 输出结果:
# [1,2,3,4,5,6,7,8,9]
第二层循环其实就是在找最小值的位置下标,每次循环碰见比记录的下标【index】小的值就记录更小的值的下标 【index = j】,然后拿小的值继续向后比较。
快速排序
快速排序:
- 首先找一个基准值(一般是数组中的中间元素 length / 2 所在)
- 找到基准元素后,拿数组中的元素与基准值比较,小于基准值放到左边,大于基准值的放到右边。
- 按照顺序,每个值与基准值比较后,会形成左右两个子集,基准的值左边都是小于基准值的元素,右边则相反。
- 然后在基准值的左右两边子集中再各选一个基准值,然后在重复上述比较。
- 直到所有子集中只剩下一个元素,则排序完成。
快速排序示例:
const array = [4, 2, 1, 3, 6, 5, 8, 7, 9];
const sort = (arr: Array<any>): any => {
if (arr.length <= 1) return arr;
const centerIndex = Math.floor(arr.length / 2);
const centerValue = arr.splice(centerIndex, 1)[0]; // 基准值
let left = [];
let right = [];
for (let i = 0; i < arr.length; i++) {
if (arr[i] < centerValue) {
left.push(arr[i]);
} else {
right.push(arr[i]);
}
}
return sort(left).concat([centerValue], sort(right));
};
const res = sort(array);
console.log(res);
# 输出结果:
# [1, 2, 3, 4, 5, 6, 7, 8, 9]
快速排序可能听起来很麻烦,但是实现起来还是比较简单的
不要被我写的 sort 所影响,上面的 sort 只是我定义的一个变量名字,和 数组的sort方法 是风马牛不相及的。
冒泡排序
冒泡排序:
- 比较数组中相邻的两个数,如果前者大于后者,则两个数交换位置。
- 一轮过后,就可以选出最大的一个数,放到最后面。
- 然后一直这样比较,直到排序完成。
冒泡排序示例:
const array = [4, 2, 1, 3, 6, 5, 8, 7, 9];
let temp: any;
for (let i = 0; i < array.length; i++) {
for (let j = 0; j < array.length - i; j++) {
if (array[j] > array[j + 1]) {
temp = array[j];
array[j] = array[j + 1];
array[j + 1] = temp;
}
}
}
console.log(array);
# 输出结果:
# [1, 2, 3, 4, 5, 6, 7, 8, 9]
外层循环控制循环次数,内层循环对相邻的两个值进行比较。之所以内层循环让 length - i,主要是为了减少不必要的循环次数。
插入排序
插入排序:
- 第一元素开始,该元素可以认为已经被排序。
- 取出第二个元素,在已经排序的序列中从后向前扫描。
- 如果当前排序的元素大于已排序的该元素,这将该元素向后移动一个位置。
- 直到遇到 已排序的元素小于或等于当前排序的元素。
- 将元素插入到当前位置中。
- 循环 2 - 5 步,直到结束。
举个例子:
数组为:[4, 2, 1, 3]
- 第一遍,当 4 是已经排序的元素,取出 2 与 已经排序的 4 比较,4 > 2 所以把 4 向后移动一个位置,又因为 4 是 0号元素,所以 2 插入4 所在的位置,4 移动到 2所在的位置。
- 得到数组:[2, 4, 1, 3]
- 第二遍:2 ,4 是已经排序的元素,取出 1 与已经排序的元素比较,4 > 1 所以 4 向后移动一个位置,1 继续向前比较,因为 2 > 1 所以 2 向后移动一个位置 又因为 2 是 0号元素,所以,1 插入到 0号元素的位置
- 得到数组:[1, 2, 4, 3]
- 第三遍:1, 2, 4是已经排序的元素,取出 3 与已经排序的元素比较,4 > 3 所以 4 向后移动一个位置,3 继续向前比较,因为 2 < 3 ,所以将 3 插入到原先4所在的位置。
插入排序示例:
const array = [4, 2, 1, 3, 6, 5, 8, 7, 9];
let preIndex: number, current;
for (let i = 1; i < array.length; i++) {
preIndex = i - 1;
current = array[i];
while (preIndex >= 0 && current < array[preIndex]) {
array[preIndex + 1] = array[preIndex];
preIndex--;
}
array[preIndex + 1] = current;
}
console.log(array);
# 输出结果:
# [1, 2, 3, 4, 5, 6, 7, 8, 9]
有前面的例子做铺垫,想必理解这个插入排序应该不是很麻烦了
桶排序
桶排序:
- 设置一个固定数量的空桶。
- 将数据放到对应的空桶中。
- 定义一个新数组,将这些不为空的桶进行排序。
- 得到结果。
注意:
- 桶排序什么时候最快?当输入的数据可以均匀分布到每一个桶中的时候。
- 桶排序什么时候最慢?当输入的数据分配到了一个桶中的时候。
首先来看不定桶长度的桶排序:
- 这里的不定长,指定的是根据传入的数据来动态的设置桶的数量。
const array = [4, 2, 11, 3, 16, 5, 8, 17, 9];
const bucketSort = (arr: Array<any>) => {
if (arr.length < 2) return;
// 定义桶
let bucket: any[] = [];
arr.forEach((item) => {
if (bucket[item] !== undefined) {
bucket[item]++;
} else {
bucket[item] = 1;
}
});
// 第一输出位置
// 存放排序后的数据
const newArr: any[] = [];
bucket.forEach((item, index) => {
if (item !== undefined) {
for (let i = 0; i < item; i++) {
newArr.push(index);
}
}
});
// 第二输出位置
arr.splice(0, arr.length, ...newArr);
return arr;
}
const res = bucketSort(array);
console.log(res);
# 输出结果:
# [2, 3, 4, 5, 8, 9, 11, 16, 17]
解析:
- 如果我们在 第一输出位置 输出一下 bucket ,我们就会得到一个有意思的数组:
bucket [, , 1, 1, 1, 1, , , 1, 1, , 1, , , , , 1, 1]
# 这个bucket数组的长度是 18,其实就是我们传入的数组的最大值。
# 这也就是我们刚开始说的,桶的长度不固定,由给的数组中的最大数据确定。
不要蒙,我解释一下 bucket 的那一段就明白了为啥 bucket的长度刚好是我们传入的数组的最大值:
- 首先遍历传入的数组 ,
arr.foreach 中 item 就是我们传入数组中的每一项的值
。 - 然后,如果
bucket 中 item 位置有值
,则让bucket[item] ++
,否则bucket[item] = 1
; - 举例:
- 传入的数据第一项是 4
bucket[4] 很明显示 undefined 的,所以 bucket[4] = 1
,此时我们得到的数组是这样的:
[, , , , 1]
- 如果
下一项还是 4 ,那么就让 bucket[item] ++
, 此时得到的数组是这样的:
[, , , , 2]
- 明白了吧,bucket 数组中的值,就代表着我们有几个相同的此项,bucket 数组中每一个不为空值的下标,其实就是我们传入的数组中的每一个值。
说的再简单一点:
传入的数组中的每一项,都变成了 bucket 数组的下标。
每一项重复了多少次,就是这一下标的值。
- 接下来第二输出位置输出的就是拼接后的数据了。
大家可以自行从脑海里抽象一下: 数组的结构 下标在上 值在下 —— 桶 的形象了:
然后我们再来看固定桶长度的排序:
- 固定桶长度的代码解释起来比较麻烦,我先给大家说一下思想。
- 思想 【以固定的5个桶为例】:
- 首先我们要找到我们传入的数据的最大值和最小值,然后由
最大值和最小值和桶数
得出每一个桶能存放的数据范围。
(max - min + 1) / 5
# max 最大值,min最小值 5 为桶数
- 然后开始遍历数据,先找到这个数组应该存放在那个桶里
Math.floor((cur - min) / range)
# cur当前值 min最小值 range范围(上一步求出的范围)
- 当向同一个序列的桶中第二次插入数据时,判断桶中已存在的数字与新插入的数字的大小,按从左到右,从小到大的顺序插入 。
- 全部的数据都装入到这 5 个桶中后,按照序列,从小到达合并桶。
- 得到排序后的数据。
固定桶长度排序代码示例:
const array = [4, 4, 2, 11, 3, 16, 5, 8, 17, 9];
/**
* arr 传入数组
* count 桶的数量
*/
const bucketSort = (arr: Array<number>, bucketCount: number): any => {
if (arr.length <= 1) return arr;
// 如果没有传桶数则设置默认桶数
const count = bucketCount ?? 10;
// 数组最大值和最小值
const max = Math.max(...arr);
const min = Math.min(...arr);
// 根据传入的桶的数量计算每一个桶的范围
const range = (max - min + 1) / count;
// 定义桶数组
let bucket: any[] = [];
// 遍历数组,插入桶中
arr.forEach(item => {
const num = Math.floor((item - min) / range);
// 如果 num 桶中是否存在值
if (bucket[num]) {
// 按照从小到大的顺序排列 [这里其实就是插入排序]
let bLength = bucket[num].length - 1;
while (bLength >= 0 && bucket[num][bLength] > item) {
// 每一项都向后移动
bucket[num][bLength + 1] = bucket[num][bLength];
bLength--;
}
bucket[num][bLength + 1] = item;
} else {
// 构建一个二维数组
bucket[num] = [];
bucket[num].push(item);
}
});
// 拼接数据
let result: any[] = [], n = 0;
while (n < count) {
if (bucket[n]) {
result = result.concat(bucket[n]);
}
n++;
}
return result;
}
// 调用
const res = bucketSort(array, 5);
console.log(res);
# 输出结果
# [2, 3, 4, 4, 5, 8, 9, 11, 16, 17]
大家可以结合我前面说的思想,来对应代码理解一下。中间往桶中插入数据时,其实就是利用了插入排序来保证从小到大的排序。
那么现在,我们可以抽象一下这个 固定桶数量的的 桶的抽象:
区别:
如果大家认认真真看到这里的话,想必对 定长的桶 和 不定长的桶 的区别,心里应该有一点数了:
- 定长的桶:
- 数据均匀分布到每一个桶中
- 桶数组下标即为传入的数组的每一项值。
- 桶数组的值即为传入的数组的每一项的重复次数。
- 不定长的桶:
- 数据按照所处的范围分布到固定数量某一桶中。
- 桶数组的下标代表的当前的桶号。
- 桶数组的值即为传入的数组的每一项的值,且可能会有多个。
sort 排序
sort 排序就不多说了,老生长谈的话题了,这里就不多说了,可以去看W3school - JavaScript 数组排序
当然,要是你能把 sort 排序玩出花来,请把代码评论到下面,我们共勉。嘿嘿。
以上就是我要说的一些排序了。完结·撒花
结语[废话整一句]:其实做一个数组排序,写这么麻烦的代码完全没啥必要,数组的sort方法用来排序完全够用且够简洁,但是怎么说呢,嘿嘿,思想很重要,逻辑理解很重要,锻炼思维很重要。嘿嘿。