React 优先级队列小顶堆的简单实现

这篇文章是博主最近在阅读 React 源码过程中关于优先级队列数据结构实现的一些提炼

我们都知道 React Schedule 中的优先级队列,本身采用小顶堆来保证队列的第一个任务是优先级最高的,并且获取优先级最高的任务的时间复杂度为 O(1)。所以今天写了个简化版的小顶堆的实现,方便大家理解。

首先优先级队列有两种操作,一种是添加任务,另外一种是移除优先级最高的任务(第一个任务)

javascript 复制代码
const heap = [];
// 添加任务
const push = (val) => {
  // 先将任务添加到队列中
  heap.push(val); 
  // 对当前任务节点进行向上调整,具体实现看后面
  siftUp(heap, val, heap.length);
};

// 移除优先级最高的任务
const pop = () => {
  // 拿到最后一个任务放到第一个任务来的位置上来
  const last = heap.pop();
  heap[0] = last;
  // 对当前任务节点进行向下调整,具体实现看后面
  siftDown(heap, last, 0);
};

// 获取优先级最高的任务
const peek = () => {
  return heap.length ? heap[0] : null;
};

当我们往队列中去添加新的任务的时候,那么这个任务是肯定会在队尾。但由于小顶堆的特性,我们需要保证第一个任务优先级要最高,所以需要拿新任务和他的所有父节点去进行对比。

javascript 复制代码
const siftUp = (heap, node, i) => {
  // 需要把节点下标记录下来,
  let index = i - 1;
  while (index > 0) {
    // 拿到 push 进来的节点的父节点
    const parentIndex = index >>> 1; // 等价于 Math.floor((index - 1) / 2)
    const parent = heap[parentIndex];
    if (parent > node) {
      // 如果父节点大于当前节点,说明当前节点需要和父节点交换位置
      heap[parentIndex] = node;
      heap[index] = parent;
      index = parentIndex;
    } else {
      return;
    }
  }
};

移除操作也是同理,移除一个高优先级任务之后,会把队尾的任务提高最顶部来,为了保证小顶堆的特性,所以需要拿这个任务和其子节点进行对比。

javascript 复制代码
const siftDown = (heap, node, i) => {
  let index = i; // 当前需要处理节点的下标
  const idx = heap.length >>> 1; // 拿到最深的非叶子结点的下标,由于小顶堆的特性,不需要到叶子节点去
  while (index < idx) {
    let leftIndex = 2 * index + 1;
    let left = heap[leftIndex];
    let rightIndex = leftIndex + 1;
    let right = heap[rightIndex];

    // 如果当前节点,比左子节点要大,说明需要调整
    if (node > left) {
      // 如果左子节点比右子节点要大,说明右子节点是目前最小的,应该和当前节点交换位置
      if (left > right) {
        heap[rightIndex] = node;
        heap[index] = right;
        index = rightIndex;
      } else {
        // 如果左子节点比右子节点要小,说明左子节点是目前最小的,应该和当前节点交换位置
        heap[leftIndex] = node;
        heap[index] = left;
        index = leftIndex;
      }
    } else if (node > right) {
      // 如果当前节点,比右子节点要大,说明需要交换位置
      heap[rightIndex] = node;
      heap[index] = right;
      index = rightIndex;
    } else {
      return;
    }
  }
};

写完了上面的代码之后,我们可以测一下

javascript 复制代码
push(5);
push(7);
push(10);
push(8);
push(2);
push(6);
console.log(heap); // [ 2, 5, 6, 8, 10, 7 ]

pop();
console.log(heap); // [ 5, 7, 6, 8, 10 ]

完整的代码如下:

javascript 复制代码
const heap = [];

// 添加任务
const push = (val) => {
  // 先将任务添加到队列中
  heap.push(val); 
  // 对当前任务节点进行向上调整,具体实现看后面
  siftUp(heap, val, heap.length);
};

// 移除优先级最高的任务
const pop = () => {
  // 拿到最后一个任务放到第一个任务来的位置上来
  const last = heap.pop();
  heap[0] = last;
  // 对当前任务节点进行向下调整,具体实现看后面
  siftDown(heap, last, 0);
};

// 获取优先级最高的任务
const peek = () => {
  return heap.length ? heap[0] : null;
};

const siftUp = (heap, node, i) => {
  // 需要把节点下标记录下来,
  let index = i - 1;
  while (index > 0) {
    // 拿到 push 进来的节点的父节点
    const parentIndex = index >>> 1; // 等价于 Math.floor((index - 1) / 2)
    const parent = heap[parentIndex];
    if (parent > node) {
      // 如果父节点大于当前节点,说明当前节点需要和父节点交换位置
      heap[parentIndex] = node;
      heap[index] = parent;
      index = parentIndex;
    } else {
      return;
    }
  }
};

const siftDown = (heap, node, i) => {
  let index = i; // 当前需要处理节点的下标
  const idx = heap.length >>> 1; // 拿到最深的非叶子结点的下标
  while (index < idx) {
    let leftIndex = 2 * index + 1;
    let left = heap[leftIndex];
    let rightIndex = leftIndex + 1;
    let right = heap[rightIndex];

    // 如果当前节点,比左子节点要大,说明需要调整
    if (node > left) {
      // 如果左子节点比右子节点要大,说明右子节点是目前最小的,应该和当前节点交换位置
      if (left > right) {
        heap[rightIndex] = node;
        heap[index] = right;
        index = rightIndex;
      } else {
        // 如果左子节点比右子节点要小,说明左子节点是目前最小的,应该和当前节点交换位置
        heap[leftIndex] = node;
        heap[index] = left;
        index = leftIndex;
      }
    } else if (node > right) {
      // 如果当前节点,比右子节点要大,说明需要交换位置
      heap[rightIndex] = node;
      heap[index] = right;
      index = rightIndex;
    } else {
      return;
    }
  }
};

push(5);
push(7);
push(10);
push(8);
push(2);
push(6);
console.log(heap);

pop();
console.log(heap);

参考:React 源码 - 最小堆实现

相关推荐
用户298698530149 分钟前
在 React 中使用 JavaScript 将 Excel 转换为 SVG
前端·javascript·react.js
labixiong41 分钟前
手写Promise--微任务、静态方法、async/await 全搞懂(三)
前端·javascript
铁皮饭盒2 小时前
3行代码搞定页面截图,Bun.WebView真的简单
javascript
kyriewen15 小时前
我手写了一个 EventEmitter,面试官追问了 6 个问题——第 4 个我没答上来
前端·javascript·面试
山河木马16 小时前
矩阵专题2-怎么创建视图矩阵(uViewMatrix)
javascript·webgl·计算机图形学
小林攻城狮16 小时前
使用 Transport 节流解决 Vercel AI SDK 流式渲染卡死问题
前端·react.js
前端缘梦16 小时前
告别 TS 运行时类型漏洞!Zod 完整入门实战教程(前端 / 全栈必备)
前端·react.js·全栈
tangdou36909865517 小时前
AI真好玩系列-2分钟快速了解DeepAgents | Quick Guide to DeepAgents in 2 Minutes
前端·javascript·后端
张元清17 小时前
React useIntersectionObserver Hook:懒加载与可见性检测(2026)
javascript·react.js
彭于晏爱编程18 小时前
纯 JS + Node,一个下午手搓了能读懂公司代码的 AI 助手,老板以为我转行了
前端·javascript