先来介绍一下普通队列和优先队列:
普通队列:先进先出,后进后出。
优先队列:出队顺序和入队顺序无关;和优先级有关。
优先队列
为什么要使用优先队列?举个例子:
在N个元素中选出前M个元素。
- 排序的时间复杂度:NlogN
- 优先队列的时间复杂度:NlogM
可见优先队列的速度是比普通队列快的。
堆
堆的数据结构是二叉树。就像下面这两张图: 堆中某个节点的值,总是不大于父节点的值,这样的二叉树结构,叫做完全二叉树。
所以,堆总是一棵完全二叉树。
最大值在顶的,叫做最大堆。最小值在顶的叫做最小堆。
特点:
- 某个节点的值,总是不大于父节点的值。
- 最后一行可能不会是满的,但是一定会在左侧。
堆这种数据结构,依然是用数组来存储的,节点是有规律的,看下面这张图:
- 左面的节点从顶开始,往下,都是乘以二。
- 右边的节点从顶开始,往下,都是乘2加1。
这是将顶点62的位置规定为1了,如果是完全符合数组的结构,62的位置应该是0。
这样规律就会发生改变。一定要注意。
最大堆添加元素
假设我们现在有一个最大堆,就是上图那个数组["",62,41,30,28,16,22,13,19,17,15]
我们现在要往这个最大堆中添加一个元素52,插入到正确的位置。
思考:
- 既然是数组,肯定是先将52放入到数组的末尾了。
- 那52的下标就是11。
- 放入之后这个堆就不符合最大堆的定义,因为52比16大。
- 既然比16大就要和16交换位置。所以要找到16的索引。
- 找索引的办法就是通过我们之前说的,52索引是奇数11,(11-1)/2就是16的索引。
- 找到之后交换位置就可以了。
看下面的代码:
let arr = ["", 62, 41, 30, 28, 16, 22, 13, 19, 17, 15]
function main(arr, num) {
arr.push(num)
let maxIndex = arr.length - 1
let k = Math.floor(maxIndex / 2)
// 考虑边界值
while (k > 1 && arr[k] < arr[maxIndex]) {
// 交换位置
[arr[k], arr[maxIndex]] = [arr[maxIndex], arr[k]]
maxIndex = k
k = Math.floor(maxIndex / 2)
}
return arr
}
console.log(main(arr, 52))
最大堆取出一个元素(Shift Down)
现在我们的数组是:[ '', 62, 52, 30, 28, 41, 22, 13, 19, 17, 15, 16 ]
依然满足最大堆条件,从堆中取出一个元素,让它依然满足最大堆。
条件:
- 从堆中取出一个元素,只能取根节点的元素,也就是62这个元素。
思考:
- 当我们从最大堆中出去根节点的元素。
- 我们的最大堆中就相当于少了一个元素,如何填补这个元素?
- 将最后一位的元素直接放在第一位,然后它下面的两个节点进行比较。
- 谁大就与谁交换位置。这样就保证了,换上来的数比16和30都大。满足最大堆性质。
- 然后继续不断比较。
// shift down
let arr = ['', 62, 52, 30, 28, 41, 22, 13, 19, 17, 15, 16];
function shiftDown(arr, k) {
arr.splice(1, 1)
let array = arr.splice(arr.length - 1, 1)
arr.splice(1, 0, array[0])
let count = arr.length
// ['', 16, 52, 30, 28, 41, 22, 13, 19, 17, 15]
while (k * 2 <= count) {
let j = k * 2
// 可能和左孩子交换位置
// 判断是否存在右孩子
// 右孩子与左孩子比较如果右边大于左
if (j + 1 <= count && arr[j + 1] > arr[j]) {
j += 1
}
if (arr[k] >= arr[j]) {
break
}
[arr[k], arr[j]] = [arr[j], arr[k]]
k = j
}
return arr
}
console.log(shiftDown(arr, 1));
堆排序
我们知道了什么是最大堆,也知道如何添加一个元素后排好序,还知道取出一个元素后如何排好序。 那,如何让一个最大堆,完成从小到大的排序,或者从大到小的排序呢?
思考:
- Shift Down这步,如果将每次取出来的最大值,return 出去。
- 剩下的排序,重复 return 出去这步操作。
- 就解决了这样的问题。
- 如果添加了一个新的值,在数组中,就shift up。
- 再shift down。
// heapSort1 堆排序
let arr = ['', 62, 52, 30, 28, 41, 22, 13, 19, 17, 15, 16];
// 交换位置函数
function swap(arr, leftIndex, rightIndex) {
[arr[leftIndex], arr[rightIndex]] = [arr[rightIndex], arr[leftIndex]]
}
// 将最大堆 shiftDown
// return 每次取出的最大值 spliceArray
// 剩下的排序,之后继续取出最大值
function shiftDown(arr, k) {
let array = arr.splice(arr.length - 1, 1);
let spliceArray = arr.splice(1, 1)
arr.splice(1, 0, array[0]);
while (k * 2 <= arr.length) {
let j = k * 2;
if (j + 1 <= arr.length && arr[j + 1] > arr[j]) {
j += 1;
};
// 小心这里有 undefined, 所以加入了!arr[j]判断
if (!arr[j] || arr[k] >= arr[j]) {
break;
}
swap(arr, k, j)
k = j;
};
return spliceArray;
}
function heapSort1(arr) {
let array = []
for (let i = 1; i < arr.length; i++) {
// 遍历的方式,取出最大值,将剩下的排序,直到结束
array.push(...shiftDown(arr, 1))
}
arr.splice(0, 1)
array = [...array, ...arr]
return array;
}
console.log(heapSort1(arr)) // [62, 52, 41, 30, 28, 22, 19, 16, 17, 15, 13]
总结:
这只是一个最大堆的排序方式,如果不是最大堆,你可能会选择快速排序或者插入排序等。
但是接下来要思考,任意数组,如何生成一个最大堆?
要思考这个的原因是:数据最大表示这个数据优先级最高,取出这个数据之后,接下来的优先级如何确定?是不是还是需要用到shift down?
是不是觉得,算法原来不是没有用处的,而是你没用到,或者不知道怎么用?
让我们一起继续学习吧。。。。
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!