🥳前端算法面试之桶排序-每日一练

879 阅读4分钟

前言

桶排序是将原来的 n 条数据分成 m 份。每一份看成是一个桶。桶之间是有序的,只要桶内排好序,就可以按照桶的顺序依次取出每个数据。这样取出来的数据就是有序的了

举个例子,现在有一万份订单,订单的金额在 1 元到 1 万元之间。现在需要对订单的金额排序,由低到高,请问如何才能在 logn 的复杂下实现呢

利用桶排序的话,可以将一万份订单,分成 100 份,第一份的金额区间是 0-100 元,第二份的金额区间是 100-200 元,依次类推,第一百份的金额在 9900-10000 之间

然后将桶内的数据通过快速排序的方法排好序。读取数据的时候,就可以从前往后读,这样的数据,就是天然有序的了。这样分需要额外的空间存储的支持,但是分的越细,桶排序的速度越快。

想象一下,我们吧一万份订单直接分成一万份,也就是一万个桶,每个桶内部所存储的金额是相同,然后每个桶之间所存储的金额差是一元。这样所有订单的金额都有了对应的桶了。现在只要把所有订单全部遍历一遍,将订单放到对应金额的桶里面。不就完成了订单的排序吗?

对的,只要遍历一遍订单,就能排好序,那桶排序的复杂度不就是 O( n ) 么?

下面做一个定量分析

复杂度分析

  1. k = n/mn 条数据,一共分成 m 份,每份有 k 条数据。
  2. 每个桶的内部使用快速排序的算法,时间复杂度就是k * logk。而且有 m 个桶,整体复杂度就是 m * k * logk,即 nlog(n/m)
  3. 当桶分的越多,log(n/m)就越小,整体的复杂度也就越接近 n

这就是桶排序时间复杂度接近于n 的证明了

代码实现

用代码实现桶,首先想到的就是散列表,不过散列函数不再是取余,而是散列函数的值等于自变量本身,即 key = hash(key). 解决冲突的方式是链表,也即用链表的物理存储方式来表示一个桶

下面看代码:

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);
};

这段代码定义了两个函数:_tongSorttongSort

_tongSort 函数用于实现桶排序算法, 它的输入参数是一个数组 array 和一个哈希数组 hashArray。函数的实现方式是遍历数组中的每个元素, 将这个元素放到对应的桶中,这里的桶是用链表的方式表示的。桶没有初始化,就创建一个桶,如果已经有了桶,并且桶中已经有了其他的元素,就使用头插法插链表中。

tongSort 函数是同排序算法的入口函数,它的输入参数是一个数组 array 和一个最大值 maxValue。函数的实现方式是创建一个长度为 maxValue + 1 的哈希数组 hashArray,然后将 _tongSort 函数的返回值作为 hashArray 的值。最后返回 hashArray

桶排序比较特殊,他需要预先知道所排序元素的最大值,这样才能生成对应的 hashArray。这在实际排序场景是没有问题的。并且所排序的元素对象的值不能太大,否则不能使用。桶排序仅支持正整数,小数也不能支持。不过对于不满足条件的数,可以做些处理,比如小数可以放大变成整数,负数可以用移码的策略变成正数等

正是因为桶排序的限制如此的多,所以语言排序 API 的底层实现通常是快速排序,或者是归并排序

测试代码:

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排序更合适呢哈哈

有什么问题可以评论区留言哦。我每天都会分享一篇算法小练习,喜欢就点个赞+关个注吧