typescript 实现优先队列(PriorityQueue)

本文参考:https://labuladong.github.io/algo/2/21/62/

2022.7.24更新

新增remove(删除)、update(更新)函数

// 优先队列
class PriorityQueue<T> {
  // 队列本体,二叉堆,注意arr[0]未使用
  private readonly arr: T[]
  // 队列内容长度
  public size: number
  // 判断函数
  private readonly judge: { (a: T, b: T): boolean }

  constructor(judge: { (a: T, b: T): boolean } = (a, b) => a > b) {
    this.arr = []
    this.size = 0
    // 默认函数为大顶堆
    this.judge = judge
  }

  // 队尾插入元素
  push(val: T): void {
    // 队列长度+1
    this.size++

    // 先插入到队尾
    this.arr[this.size] = val
    // 从队尾开始上浮元素
    this.float(this.size)
  }

  // 移除队首元素
  poll(): T {
    // 将队首元素交换到队尾
    let t = this.swiper(1, this.size)
    // 删除队尾元素
    this.arr.length--
    this.size--

    // 从队首开始下沉
    this.sink(1)
    return t
  }

  // 获取队首元素
  peek(): T {
    return this.arr[1]
  }

  /**
   * 删除元素(默认使用findIndex从队列中查找)
   * @param val 要删除的值
   * @param fun 该函数用于找到要删除的位置,默认以值相等代表找到
   * @return {boolean} true为删除成功
   */
  remove(val: T, fun: (v: T) => boolean = (v) => v === val): boolean {
    let index = -1
    // 找到第一个符合条件的值的下标,注意下标0没值,需要从1开始
    for (let i = 1; i < this.arr.length; i++) {
      if (fun(this.arr[i])) {
        index = i
        break
      }
    }
    if (index === -1) return false

    // 将待删除元素与队尾元素交换
    this.swiper(index, this.size)
    // 删除队尾元素
    this.arr.length--
    this.size--

    // 因为是队尾换上来的,所以需要下沉
    this.sink(index)
    return true
  }

  /**
   * 更新元素(默认使用findIndex从队列中查找)
   * @param oldVal 要删去的旧值
   * @param newVal 要更新的新值
   * @param fun 该函数用于找到要删除的位置,默认以值相等代表找到
   * @return {boolean} true为更新成功
   */
  update(oldVal: T, newVal: T, fun: (v: T) => boolean = (v) => v === oldVal): boolean {
    // 先删去旧的
    let removeRes: boolean = this.remove(oldVal, fun)
    if (!removeRes) return false

    // 再添加新的
    this.push(newVal)
    return true
  }

  // 判空
  empty(): boolean {
    return !this.size
  }

  // 核心操作:上浮
  float(index: number): void {
    // 父节点
    let pIndex: number = parseInt(index / 2 + '')
    // 父节点存在且不满足判断规则
    if (pIndex > 0 && !this.judge(this.arr[pIndex], this.arr[index])) {
      // 交换父子节点
      this.swiper(pIndex, index)
      // 继续上浮
      this.float(pIndex)
    }
  }

  // 核心操作:下沉
  sink(index: number): void {
    let leftIndex = index * 2
    let rightIndex = leftIndex + 1
    let acceptIndex = index

    // 如果左节点存在且不满足条件,假装先让左节点为根
    if (leftIndex <= this.size && !this.judge(this.arr[acceptIndex], this.arr[leftIndex]))
      acceptIndex = leftIndex

    // 右节点存在且不满足条件
    // 如果acceptIndex在右节点上面不满足条件,则让右节点为根
    if (rightIndex <= this.size && !this.judge(this.arr[acceptIndex], this.arr[rightIndex]))
      acceptIndex = rightIndex

    // 如果acceptIndex没被替换,说明左右节点均满足条件,递归结束
    if (acceptIndex !== index) {
      this.swiper(index, acceptIndex)
      this.sink(acceptIndex)
    }
  }

  // 交换
  swiper(i: number, j: number): T {
    let t: T = this.arr[i]
    this.arr[i] = this.arr[j]
    this.arr[j] = t
    return t
  }
}

/* 测试样例
~function test() {
  let arr = [5, 1, 3, 6, 7, 2, 4, 4]
  let pQueue = new PriorityQueue<number>()

  for (let number of arr) {
    // 测试尾部插入
    pQueue.push(number)
  }

  // 测试删除
  pQueue.remove(5, (v) => v === 5)// 如果实现了fun,val可填null
  pQueue.remove(3)
  // 测试更新
  pQueue.update(7, 9)

  while (!pQueue.empty()) {
    // 测试头部移除
    let a = pQueue.poll()
    console.log(a)
  }
}()
 */
posted @ 2022-06-24 12:00  ぃ往事深处少年蓝べ  阅读(336)  评论(0编辑  收藏  举报