从libuv源码中学习二叉堆_uv_timer_stop

               uint64_t repeat) {

… …
heap_insert(timer_heap(handle->loop),
(struct heap_node*) &handle->heap_node,
timer_less_than);
… …
}


当调用`uv_timer_stop`的时候,libuv都会删除一条定时器信息:



int uv_timer_stop(uv_timer_t* handle) {
if (!uv__is_active(handle))
return 0;

heap_remove(timer_heap(handle->loop),
(struct heap_node*) &handle->heap_node,
timer_less_than);
uv__handle_stop(handle);

return 0;
}


**为什么用最小二叉堆呢?**  
 因为它永远把最小值放在了根节点,而这里的最小值就是定时器最先到时间点的那一组,所以为了查询效率,采用了这么一种算法:



void uv__run_timers(uv_loop_t* loop) {
… …

for (;😉 {
heap_node = heap_min(timer_heap(loop));
if (heap_node == NULL)
break;

handle = container\_of(heap_node, uv_timer_t, heap_node);
if (handle->timeout > loop->time)
  break;

... ...

}
}


libuv的最小二叉堆的实现源码在这里:[heap-inl.h]( )


接下去,我们开始从libuv的源码中学习最小二叉堆的知识,为了让大家不至于那么陌生,将C语言实现版本转换为Js版本,我们会一遍讲解理论,一边代码实现。


### 2、二叉堆的基本概念


首先我们得知道二叉堆的定义:二叉堆是一棵完全二叉树,且任意一个结点的键值总是小于或等于其子结点的键值。


那么什么是完全二叉树(complete binary tree)呢?我们先来看一下关于树的数据结构都有哪些?


![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91c2VyLWdvbGQtY2RuLnhpdHUuaW8vMjAyMC8yLzcvMTcwMWZjOWI2OTYwMmQ1Mw?x-oss-process=image/format,png)


#### 2.1、完全二叉树


定义是:



> 
> 对于一个树高为`h`的二叉树,如果其第0层至第`h-1`层的节点都满。如果最下面一层节点不满,则所有的节点在左边的连续排列,空位都在右边。这样的二叉树就是一棵完全二叉树。  
>  如下图所示:
> 
> 
> 


![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91c2VyLWdvbGQtY2RuLnhpdHUuaW8vMjAyMC8yLzcvMTcwMWZjOWY3YjYwM2ZkMQ?x-oss-process=image/format,png)


正因为完全二叉树的独特性质,因此其数据可以使用数组来存储,而不需要使用特有的对象去链接左节点和右节点。因为其左右节点的位置和其父节点位置有这样的一个计算关系:


k表示父节点的索引位置



left = 2 * k + 1
right = 2 * k + 2


#### 2.2、最小(大)二叉堆


知道了完全二叉树,那么二叉堆的这种神奇的数据结构就是多了一个硬性条件:**任意一个结点的键值总是小于(大于)或等于其子结点的键值。** 因为其存储结构不是使用左右节点互相链接的形式,而是使用简单的数组,所以称之为”堆“,但是基于完全二叉树,因此又带上了”二叉“两字。


那么有了上面的特征,当我们插入或者删除某个值的时候,为了保持二叉堆的特性,于是又出现了一些二叉堆**稳定**的调整算法(也叫堆化),具体在下面讲解。


### 3、二叉堆的基本操作


搞懂二叉堆的插入和删除操作,我们先得掌握两个基本操作:一个是从顶向下调整堆(bubble down),一个自底向上调整堆(bubble up),二者的调整分别用于二叉堆的删除和插入。


#### 3.1、自顶向下调整(堆化)


这个操作其实就是根据父节点的位置,往下寻找符合条件的子节点,不断地交换直到找到节点大于父节点,示意图如下:


![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91c2VyLWdvbGQtY2RuLnhpdHUuaW8vMjAyMC8yLzcvMTcwMWZjYjU1MGZhOGUyMg?x-oss-process=image/format,png)


实现代码如下:



// 当前节点i的堆化过程
max_heapify(i) {
const leftIndex = 2 * i + 1 // 左节点
const rightIndex = 2 * i + 2 // 右节点
let maxIndex = i // 当前节点i

  // 如果有子节点数据大于本节点那么就进行交换
  if (leftIndex < this.heapSize && this.list[leftIndex] > this.list[maxIndex]) {
      maxIndex = leftIndex
  }
  if (rightIndex < this.heapSize && this.list[rightIndex] > this.list[maxIndex]) {
      maxIndex = rightIndex
  }
  if (i !== maxIndex) {
      swap(this.list, maxIndex, i) // maxIndex子节点与当前节点位置交换
      // 自顶向下调整
      this.max\_heapify(maxIndex) // 自顶向下递归依次对子节点建堆
  }

}


#### 3.2、自底向上调整(建堆)


这种调整是当插入一个新值的时候,为了保证二叉堆的特性,需要从该新插入的子节点中一步步与父节点判断,不断交换位置,直到整个二叉堆满足特性。示意图如下:


![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91c2VyLWdvbGQtY2RuLnhpdHUuaW8vMjAyMC8yLzcvMTcwMWZjYzA5N2UzYzA4Nw?x-oss-process=image/format,png)


这里有一个核心问题:**倒数第一个分支节点的序号是多少呢?**


![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91c2VyLWdvbGQtY2RuLnhpdHUuaW8vMjAyMC8yLzcvMTcwMWZjYThmYzY0YTFkZA?x-oss-process=image/format,png)


代码实现如下:



//建堆
build() {
let i = Math.floor(this.heapSize / 2) - 1
while (i >= 0) {
// 自底向上调整, 从倒数第一个分支节点开始,自底向上调整,直到所有的节点堆化完毕
this.max_heapify(i–)
}
}


### 4、插入和删除


有了上面的两种操作,插入的删除的实现就顺理成章了。只需要这么调用上面的两个操作:


#### 4.1 插入操作



//增加一个元素
insert(item) {
this.list.push(item);
this.heapSize++
this.build();
}


#### 4.2 删除操作


这里的删除都是删除根节点,然后再把最后一个节点的数拿到根节点,之后再自上而下调整整个二叉堆。



//提取最大堆第一个节点并恢复堆为最大堆
extract() {
if (this.heapSize === 0) return null
const item = this.list[0]
swap(this.list, 0, this.heapSize - 1)
this.heapSize–
this.max_heapify(0)
return item
}


完整代码展示如下:



/**
* 数组元素交换
* @param {*} A
* @param {*} i

这里分享一份由字节前端面试官整理的「2021大厂前端面试手册」,内容囊括Html、CSS、Javascript、Vue、HTTP、浏览器面试题、数据结构与算法。全部整理在下方文档中,共计111道

HTML

  • HTML5有哪些新特性?

  • Doctype作⽤? 严格模式与混杂模式如何区分?它们有何意义?

  • 如何实现浏览器内多个标签页之间的通信?

  • ⾏内元素有哪些?块级元素有哪些? 空(void)元素有那些?⾏内元 素和块级元素有什么区别?

  • 简述⼀下src与href的区别?

  • cookies,sessionStorage,localStorage 的区别?

  • HTML5 的离线储存的使用和原理?

  • 怎样处理 移动端 1px 被 渲染成 2px 问题?

  • iframe 的优缺点?

  • Canvas 和 SVG 图形的区别是什么?

JavaScript

  • 问:0.1 + 0.2 === 0.3 嘛?为什么?

  • JS 数据类型

  • 写代码:实现函数能够深度克隆基本类型

  • 事件流

  • 事件是如何实现的?

  • new 一个函数发生了什么

  • 什么是作用域?

  • JS 隐式转换,显示转换

  • 了解 this 嘛,bind,call,apply 具体指什么

  • 手写 bind、apply、call

  • setTimeout(fn, 0)多久才执行,Event Loop

  • 手写题:Promise 原理

  • 说一下原型链和原型链的继承吧

  • 数组能够调用的函数有那些?

  • PWA使用过吗?serviceWorker的使用原理是啥?

  • ES6 之前使用 prototype 实现继承

  • 箭头函数和普通函数有啥区别?箭头函数能当构造函数吗?

  • 事件循环机制 (Event Loop)

  • 7
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值