前言
桶排序是将原来的 n 条数据分成 m 份。每一份看成是一个桶。桶之间是有序的,只要桶内排好序,就可以按照桶的顺序依次取出每个数据。这样取出来的数据就是有序的了
举个例子,现在有一万份订单,订单的金额在 1 元到 1 万元之间。现在需要对订单的金额排序,由低到高,请问如何才能在 logn 的复杂下实现呢
利用桶排序的话,可以将一万份订单,分成 100 份,第一份的金额区间是 0-100 元,第二份的金额区间是 100-200 元,依次类推,第一百份的金额在 9900-10000 之间
然后将桶内的数据通过快速排序的方法排好序。读取数据的时候,就可以从前往后读,这样的数据,就是天然有序的了。这样分需要额外的空间存储的支持,但是分的越细,桶排序的速度越快。
想象一下,我们吧一万份订单直接分成一万份,也就是一万个桶,每个桶内部所存储的金额是相同,然后每个桶之间所存储的金额差是一元。这样所有订单的金额都有了对应的桶了。现在只要把所有订单全部遍历一遍,将订单放到对应金额的桶里面。不就完成了订单的排序吗?
对的,只要遍历一遍订单,就能排好序,那桶排序的复杂度不就是 O( n ) 么?
下面做一个定量分析
复杂度分析
k = n/m
,n
条数据,一共分成m
份,每份有k
条数据。- 每个桶的内部使用快速排序的算法,时间复杂度就是
k * logk
。而且有m
个桶,整体复杂度就是m * k * logk
,即nlog(n/m)
- 当桶分的越多,
log(n/m)
就越小,整体的复杂度也就越接近n
这就是桶排序时间复杂度接近于n
的证明了
代码实现
用代码实现桶,首先想到的就是散列表,不过散列函数不再是取余,而是散列函数的值等于自变量本身,即 key = hash(key)
. 解决冲突的方式是链表,也即用链表的物理存储方式来表示一个桶
下面看代码:
javascript
const _tongSort = (array, hashArray) => {
for (let i = 0; i < array.length; i++) {
const hashIndex = array[i];
const temp = { value: hashIndex, next: null };
if (!hashArray[hashIndex]) {
hashArray[hashIndex] = temp;
} else {
temp.next = hashArray[hashIndex];
hashArray[hashIndex] = temp;
}
}
return hashArray;
};
const tongSort = (array, maxValue) => {
const hashArray = Array(maxValue + 1).fill(null);
return _tongSort(array, hashArray);
};
这段代码定义了两个函数:_tongSort
和 tongSort
。
_tongSort
函数用于实现桶排序算法, 它的输入参数是一个数组 array
和一个哈希数组 hashArray
。函数的实现方式是遍历数组中的每个元素, 将这个元素放到对应的桶中,这里的桶是用链表的方式表示的。桶没有初始化,就创建一个桶,如果已经有了桶,并且桶中已经有了其他的元素,就使用头插法插链表中。
tongSort
函数是同排序算法的入口函数,它的输入参数是一个数组 array
和一个最大值 maxValue
。函数的实现方式是创建一个长度为 maxValue + 1
的哈希数组 hashArray
,然后将 _tongSort
函数的返回值作为 hashArray
的值。最后返回 hashArray
。
桶排序比较特殊,他需要预先知道所排序元素的最大值,这样才能生成对应的 hashArray
。这在实际排序场景是没有问题的。并且所排序的元素对象的值不能太大,否则不能使用。桶排序仅支持正整数,小数也不能支持。不过对于不满足条件的数,可以做些处理,比如小数可以放大变成整数,负数可以用移码的策略变成正数等
正是因为桶排序的限制如此的多,所以语言排序 API 的底层实现通常是快速排序,或者是归并排序
测试代码:
javascript
const data = [32, 45, 12, 34, 35, 42, 324, 123, 21, 23];
const sortedArray = tongSort(data, 324);
const printSortedArray = (hashArray) => {
for (let i = 0; i < hashArray.length; i++) {
if (hashArray[i]) {
let temp = hashArray[i];
while (temp) {
console.log(temp.value);
temp = temp.next;
}
}
}
};
printSortedArray(sortedArray);
// 12
// 21
// 23
// 32
// 34
// 35
// 42
// 45
// 123
// 324
在调用sortedArray
之后,得到了一个 hashArray
,这个数组还不能直接使用,需要一个辅助函数。上面代码中使用了打印的辅助函数printSortedArray
。可以看到打印结果是没有问题的。
想了解更多关于 hash 列表的内容,可以看这篇文章:🥳每日一练-删除数组中重复的元素-JS简易版
总结
这篇文章分享了桶排序,桶排序的实现,复杂度分析,以及使用场景,使用限制等等,如果用hash表来实现的话,桶排序是不是叫hash排序更合适呢哈哈
有什么问题可以评论区留言哦。我每天都会分享一篇算法小练习,喜欢就点个赞+关个注吧