排序算法 下
以下被称为分布式排序的算法,原始数组中的数据会分发到多个中间结构(桶),再合起来放回原始数组。最著名的分布式算法有计数排序、桶排序和基数排序,这三种算法非常相似。
计数排序
计数排序是一种用来排序整数的优秀算法,时间复杂度为O(n + k),其中k是临时计数数组的大小,但是,他需要更多的内存来存放临时变量。
- 先创建一个排序数据中最大值 + 1创建一个数组(关于这部分我刚开始也不是很明白,js的数组不是动态的吗!何必多此一举去找排序数据的最大值,然后创建一个数组呢???后来想了一下数组的扩容是不是要数据迁移,那样的确不如提前创建大一点,然后找了一下资料: 探究JS V8引擎下的“数组”底层实现),感兴趣的可以瞧一瞧。
- 然后开始计数,其实就是以下标代表数据,然后以值作为数据出现的次数(分布式中的第一步)
- 循环计数的数组,然后将其回归到原数组上(分布式中的第二步)
export function sortArray(arr: Array<number>) {
if (arr.length < 2){
return;
}
let len = findMaxNumber(arr) + 1;
let countArr = new Array(len).fill(0);
for (let i = 0; i < arr.length; i++) {
countArr[arr[i]]++;
}
for (let i = 0,index = 0; i < countArr.length; i++) {
for (let j = 0; j < countArr[i]; j++) {
arr[index++] = i;
}
}
}
/**
* 找最大数
* @param arr
*/
function findMaxNumber(arr: Array<number>):number {
let max:number = 0;
for (let i = 0; i < arr.length; i++) {
if(arr[i] > max){
max = arr[i];
}
}
return max;
}
let arr = [54,8,45,7, 1,2,45,9,8,452,35,754,127,6,21,124,454]
sortArray(arr)
// @ts-ignore
console.log(arr)
可以看出这种排序很适合那种有多个重复数据的整数数组,但是数据中有一个特别大的时候,计数数组将会占用很大的内存
还有这里创建的数组是 最大值 + 1,因为数组的下标是0开始的,所以 +1确保最大值也能加入数据
桶排序
桶排序
也称之为箱排序
,也是分布式排序算法中的一种,它将元素分为不同的桶(较小的数组),然后使用一个简单的排序算法排序(比如说插入排序),然后合并数组为结果对于桶排序算法,我们需要指定需要多少个桶来排序各个元素,默认情况下,我们会使用5个桶(这里我认为是每个桶的容量为5,而且看这个作者命名
bucketSize
,也觉得就是桶容量,不过确定了一个桶的容量,自然就确定了桶的个数,后面我还是以桶容量来说明该参数)。桶排序在所有元素平分到各个桶时的表现最好。如果元素非常稀疏,则使用更多的桶会更好。如果元素非常密集,则使用较少的桶会比较好,因此我们允许bucketSize
以参数的形式传递
-
第一步: 创建桶
创建桶看似很简单,实际上还是有点难度的,需要做到几点
- 算出桶的个数:
Math.floor((最大值 - 最小值 ) / 单个桶的容量) + 1
- 然后创建一个
桶的个数
对应大小的数组,内部填充[]
,注意这里不能使用fill
填充,fill
填充的数组是同一个引用,所以会导致失败(这里创建了一个二维数组) - 将排序元素塞到对应的桶里去:
(当前值 - 最小值) / 单个桶的容量
,其实这个塞入的过程就已经对桶内数据进行了一次粗略排序
- 算出桶的个数:
-
第二步: 对桶排序
传回的是一个二维数组,桶是有序的(0号桶的内容会都小于1号桶的内容)
使用
插入排序
对桶内数据排序组合为新的数组返回
import {sortArray as insertSort} from "./InsertSort"
export function sortArray(arr: Array<number>, bucketSize: number = 5) {
if (arr.length < 2) {
return arr;
}
// 创建桶
let buckets = createBuckets(arr, bucketSize);
// 桶排序并返回数组
return sortBuckets(buckets);
}
/**
* 创建桶
* @param arr
* @param bucketSize 单个桶的容量
*/
function createBuckets(arr: Array<number>, bucketSize: number):any[][] {
let minNum = arr[0];
let maxNum = arr[0];
// 循环查找最大值最小值
for (let i = 1; i < arr.length; i++) {
if (maxNum < arr[i]) {
maxNum = arr[i];
} else if (arr[i] < minNum) {
minNum = arr[i];
}
}
// 计算桶数
const bucketCount = Math.floor((maxNum - minNum) / bucketSize) + 1;
// 创建并初始化装桶的数组,这里就是一个二元数组了
const buckets = [];
//这个for循环不要使用fill替换
for (let i = 0; i < bucketCount; i++) {
buckets[i] = [];
}
for (let i = 0; i < arr.length; i++) {
// 计算下标的过程其实是对数据进行了一次简单的排序,然后桶就会呈现出有序性
const index = Math.floor((arr[i] - minNum) / bucketSize)
buckets[index].push(arr[i]);
}
return buckets;
}
/**
* 对桶进行排序
* @param buckets
*/
function sortBuckets(buckets: any[][]) {
let result = [];
for (let i = 0; i < buckets.length; i++) {
let element = buckets[i];
if(element !== null){
insertSort(element)
result.push(...element);
}
}
return result;
}
let arr = [54,8,45,7, 1,2,45,9,8,452,35,754,127,6,21,124,454]
let sortArr = sortArray(arr)
// @ts-ignore
console.log(sortArr)
还是想强调一下,填充桶数组的时候不能使用'fill'