这是我参与2022首次更文挑战的第9天,活动详情查看:2022首次更文挑战
前言
之前写过一篇文章给大家介绍了js中的堆排序,[路飞]_js中的堆排序, 同时也介绍了一下堆排序和优先队列的关系,总而言之就一句话,堆排序是一种排序方式,优先队列是基于堆排序实现的一种数据结构。上次我在leetcode上通过默认的插件库去实现字母排序那道题的时候,发生了这样子令人迷惑的行为: 最大堆和最小堆的优先队列都能用,但是自定义排序规则的优先队列却用不了,于是我不服气呀,决定今天手撕一篇优先队列,最终代码可以直接作为工具类引入使用。(PS: 看官老爷们可以直接复制最终代码就成为你自己的工具类了。)
简介
首先简单介绍一下优先队列是干嘛的 -- 优先队列也是一种队列结构,但是它不同于普通队列的先进先出和栈的先进后出,它是根据自己的优先级来觉得弹出的顺序的,也意味着你可以今天说按身高排序出列,明天可以说按体重顺序排序出列,后天可以说按男女顺序排序出列。总之一句话,怎么开心怎么来。但是有个前提条件,你要告诉它,排序的规则是什么的,比如说按身高从低到高进行排序。这个是创建优先队列的时候需要传递的一个参数,以函数的形式传递参数,也是函数柯里化的一种表现。
然后它需要实现几个方法:
- constructor() 初始化方法,传入排序的规则,后续整个队列都按这种规则来进行排序;
- enQueue() 入队,插入队列末尾,每次跟父节点对比,看看需不需要跟父节点交换位置往上冒泡,这是一个递归的过程,直到冒泡到根节点或者不需要交换位置;
- deQueue() 出队,把当前头元素返回,同时把当前最后一个元素作为头元素,然后让当前头元素跟左右子节点做比较,取左右子节点的最值进行节点的交换,直到冒泡到最后一层节点或者不需要交换位置;
- size() 获取当前优先队列中元素的总个数;
- isEmpty() 判断当前优先队列是否为空;
- front() 获取第一个元素;
- clear() 清空当前队列中的所有元素。
实现
这里直接用es6的class写法来实现一版,图省事。否则的话用函数式编程这些方法的挂载都得通过PriorityQueue.prototype.xxx来挂上去,看上去没这么舒服。
初始化
class PriorityQueue {
constructor(sortBy) {
// 先把排序方式定下来, 默认值是按从小到大排序
this.sortBy = sortBy || ((a, b) => a - b);
// 一开始队列的样子,js中用数组直接表示
this.queue = [];
}
// 入队 -- 插入元素
enQueue() {
}
// 出队 -- 获取最值
deQueue() {
}
// 获取最值 -- 不移除
front() {
return this.queue[0];
}
// 获取队列中的元素总数
size() {
return this.queue.length;
}
// 判断当前优先队列是否为空
isEmpty() {
return this.size() === 0;
}
// 清空当前队列中的所有元素
clear() {
this.queue = [];
}
}
最终代码
class PriorityQueue {
constructor(sortBy) {
// 先把排序方式定下来, 默认值是按从小到大排序
this.sortBy = sortBy || ((a, b) => a - b);
// 一开始队列的样子,js中用数组直接表示
this.queue = [];
}
// 入队 -- 插入元素
enQueue(node) {
this.queue.push(node);
this.size++;
this.heapifyFromBottom();
}
// 出队 -- 获取最值
deQueue() {
const top = this.front();
this.queue[0] = this.queue.pop();
this.heapifyFromTop();
return top;
}
// 获取最值 -- 不移除
front() {
return this.queue[0];
}
// 获取队列中的元素总数
size() {
return this.queue.length;
}
// 判断当前优先队列是否为空
isEmpty() {
return this.size() === 0;
}
// 清空当前队列中的所有元素
clear() {
this.queue = [];
}
// 从上往下递进 -- 取出节点时
heapifyFromTop(i = 0) {
let left = i * 2 + 1;
let right = i * 2 + 2;
let maxIndex = i;
// 左节点是最值的话
if (left <= this.size() && this.sortBy(arr[left], arr[maxIndex]) > 0) {
maxIndex = left;
}
// 右节点是最值的话
if (right <= this.size() && this.sortBy(arr[right], arr[maxIndex]) > 0) {
maxIndex = right;
}
// 如果当前节点不是最值,那么当前节点的值要往下传递,让下面的最值冒泡上来
if (maxIndex !== i) {
this.swap(i, maxIndex);
this.heapifyFromTop(maxIndex);
}
}
// 从下往上冒泡 -- 插入节点时
heapifyFromBottom(i = this.size() - 1) {
let parent = Math.floor((i - 1) / 2);
// 看看是否需要进行下一轮冒泡
if (parent >= 0 && this.sortBy(arr[parent], arr[i]) > 0) {
this.swap(i, parent);
this.heapifyFromBottom(parent);
}
}
// 交换节点的值
swap(i, j) {
const temp = this.queue[i];
this.queue[i] = arr[j];
this.queue[j] = temp;
}
}
初始化完发现我们要实现的方法实际上就两个,一个是入队(插入节点), 一个是出队(删除头节点)。而且我们知道优先队列是基于堆排序实现的,那么我们直接把堆排序的方法加进来。
看懂了的小伙伴可以点个关注、咱们下道题目见。如无意外以后文章都会以这种形式,有好的建议欢迎评论区留言。