Erlang的gb_trees给出了平衡二叉树的实现。

empty()函数给出了一个得到一个空的二叉树的途径。

empty() ->{0, nil}.

空树作为一个含有两个成员的元组,第一个0则代表树中的元素个数,第二个位置则用来存放根节点。

insert()函数则给出了给二叉树添加节点的途径。

insert(Key, Val, {S, T}) when is_integer(S) ->S1 = S+1,{S1, insert_1(Key, Val, T, ?pow(S1, ?p))}.insert_1(Key, Value, {Key1, V, Smaller, Bigger}, S) when Key < Key1 -> case insert_1(Key, Value, Smaller, ?div2(S)) of{T1, H1, S1} ->T = {Key1, V, T1, Bigger},{H2, S2} = count(Bigger),H = ?mul2(erlang:max(H1, H2)),SS = S1 + S2 + 1,P = ?pow(SS, ?p),ifH > P -> balance(T, SS);true ->{T, H, SS}end;T1 ->{Key1, V, T1, Bigger}end;
insert_1(Key, Value, {Key1, V, Smaller, Bigger}, S) when Key > Key1 -> case insert_1(Key, Value, Bigger, ?div2(S)) of{T1, H1, S1} ->T = {Key1, V, Smaller, T1},{H2, S2} = count(Smaller),H = ?mul2(erlang:max(H1, H2)),SS = S1 + S2 + 1,P = ?pow(SS, ?p),ifH > P -> balance(T, SS);true ->{T, H, SS}end;T1 ->{Key1, V, Smaller, T1}end;
insert_1(Key, Value, nil, S) when S =:= 0 ->{{Key, Value, nil, nil}, 1, 1};
insert_1(Key, Value, nil, _S) ->{Key, Value, nil, nil};
insert_1(Key, _, _, _) ->erlang:error({key_exists, Key}).

在使用insert()添加节点的时候,首先给原本元组中表示底下元素的S加一作为S1代表添加进值之后的后节点以及节点下的节点数量。

之后求得S的平方,作为参数通过insert_1()函数添加节点。在第一次添加的情况下爱,第三个参数本身就是nil也就是空节点,那么可以直接将所要添加的节点放到这个位置,构造一个四个元素的元组,前两个就是键值对,而后面则代表左右子节点,当然此时为空。将这个元组返回,所以最后得到的结果为一个两个元素的元组,第一个代表此时所有节点的数量,第二个则是新加入的根节点。

如果已经存在节点的情况下通过insert()函数加入新节点,可能较为复杂。

当新加入的key小于根节点的key,那么得到当前节点的右节点继续递归执行insert_1()函数进行添加,如果是空的,那么直接加入。

同理,当新加入的key大于当前节点的key,那么也是直接当前节点的左节点继续递归执行insert_1()函数进行添加,与之前一样。

但是此时的树是不平衡的,只是一个简单的二叉树。那么什么时候开始树的平衡操作呢?

在insert()正式准备添加节点的时候,计算得到了加入新节点的个数的2的平方,这个数字就是用来作为是否进行二叉树平衡的依据。

-define(pow(A, _), A * A).

每次在进行一次新的insert_1()递归的时候,都会将得到的平方右移一位,也就是说新加入节点的2次方的二进制位数代表了递归次数的上限,位移之后为0,也就是说当前的树可能需要平衡操作,这是返回的元组包含了一个新添加进来的节点,以及H1和S1,分别为1。返回得到的结果,先将新得到节点放到原本操作应该在的位置。

count({_, _, nil, nil}) ->{1, 1};
count({_, _, Sm, Bi}) ->{H1, S1} = count(Sm),{H2, S2} = count(Bi),{?mul2(erlang:max(H1, H2)), S1 + S2 + 1};
count(nil) ->{1, 0}.

count()函数用来计算某个节点下的两个数字,H1的位数则代表当前节点下最大成熟,S1代表当前节点下(包括当前节点)的所有节点数量。

通过conut()函数得到新节点的这两个参数为H2,和S2。将H1与H2最大值再左移一位,H的位数代表当前节点的父节点的最大层数,之后将父节点以及所有节点的个数相加得到SS并求2的次方为P。

如果H大于P,则代表此时的树过于稀疏,需要采用balance()方法将整棵树变成二叉平衡树。

balance(T, S) ->balance_list(to_list_1(T), S).balance_list(L, S) ->{T, []} = balance_list_1(L, S),T.balance_list_1(L, S) when S > 1 ->Sm = S - 1,S2 = Sm div 2,S1 = Sm - S2,{T1, [{K, V} | L1]} = balance_list_1(L, S1),{T2, L2} = balance_list_1(L1, S2),T = {K, V, T1, T2},{T, L2};
balance_list_1([{Key, Val} | L], 1) ->{{Key, Val, nil, nil}, L};
balance_list_1(L, 0) ->{nil, L}.to_list_1(T) -> to_list(T, []).to_list({Key, Value, Small, Big}, L) ->to_list(Small, [{Key, Value} | to_list(Big, L)]);
to_list(nil, L) -> L.

传入的参数为当前新节点的父节点和父节点以及其下面所有节点的数量。

首先将当前的树,也就是元组通过to_list_1()转化为列表,结果其实就是将当前二叉树的先序遍历的结果。

之后通过balance_list_1()函数开始转化为二叉平衡树。

参数S为节点下的数量,减一得到Sm,除以2得到S2,S1为Sm减去S2的结果作为当前节点左边的节点个数。而后继续递归balance_list_1(),直到S为1,此时该节点,也就是当前树先序遍历之后的第一个节点就是叶子节点。之后将下一个节点取出,这个节点就是上一个节点的父节点,继续将列表的下一个节点通过balance_list_1()进行递归,由于列表是前序遍历的结果,这样把上述T1,T2分别作为当前节点的左右节点,继续上一层递归,由此平衡二叉树的平衡完成。

balance()完成之后,返回的三个成员的元组不止含有当前父节点,还有层数H以及节点数SS便于上层节点的平衡。

erlang的gb_trees源码解析相关推荐

  1. erlang的dict源码解析(1)

    Erlang的dict模块功能类似于java的hashmap. 通过dict:new()构建新的dict. new() ->Empty = mk_seg(?seg_size),#dict{emp ...

  2. erlang的dict源码解析(2)

    dict:filter()可以通过传入一个断言作为参数,来对目标dict达到筛选的目的. filter(F, D) -> filter_dict(F, D).filter_dict(F, #di ...

  3. erlang下lists模块sort(排序)方法源码解析(二)

    上接erlang下lists模块sort(排序)方法源码解析(一),到目前为止,list列表已经被分割成N个列表,而且每个列表的元素是有序的(从大到小) 下面我们重点来看看mergel和rmergel ...

  4. [源码解析] 从TimeoutException看Flink的心跳机制

    [源码解析] 从TimeoutException看Flink的心跳机制 文章目录 [源码解析] 从TimeoutException看Flink的心跳机制 0x00 摘要 0x01 缘由 0x02 背景 ...

  5. 谷歌BERT预训练源码解析(二):模型构建

    版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明. 本文链接:https://blog.csdn.net/weixin_39470744/arti ...

  6. 谷歌BERT预训练源码解析(三):训练过程

    目录 前言 源码解析 主函数 自定义模型 遮蔽词预测 下一句预测 规范化数据集 前言 本部分介绍BERT训练过程,BERT模型训练过程是在自己的TPU上进行的,这部分我没做过研究所以不做深入探讨.BE ...

  7. 谷歌BERT预训练源码解析(一):训练数据生成

    目录 预训练源码结构简介 输入输出 源码解析 参数 主函数 创建训练实例 下一句预测&实例生成 随机遮蔽 输出 结果一览 预训练源码结构简介 关于BERT,简单来说,它是一个基于Transfo ...

  8. Gin源码解析和例子——中间件(middleware)

    在<Gin源码解析和例子--路由>一文中,我们已经初识中间件.本文将继续探讨这个技术.(转载请指明出于breaksoftware的csdn博客) Gin的中间件,本质是一个匿名回调函数.这 ...

  9. Colly源码解析——结合例子分析底层实现

    通过<Colly源码解析--框架>分析,我们可以知道Colly执行的主要流程.本文将结合http://go-colly.org上的例子分析一些高级设置的底层实现.(转载请指明出于break ...

最新文章

  1. PHP对表单提交特殊字符的过滤和处理
  2. 数据中心UPS电池故障引起火灾导致澳大利亚医院系统业务中断
  3. 【云炬大学生创业基础笔记】第1章第2节关于什么是创客的讨论
  4. ​年底大会火爆,看“瑶台”如何搭建一场高质量沉浸式大会
  5. InnoDB和MyISAM有哪些不同
  6. vscode kite插件_微软发布 VS Code Python 插件 7 月更新
  7. 如何查询oracle最近报警信息,教你怎样用Oracle方便地查看报警日志错误
  8. 应用程序框架实战二十二 : DDD分层架构之仓储(层超类型基础篇)
  9. connect mysql (4),mysql用法4
  10. 使用opencv和python实现图像的智能处理_机器学习:使用opencv和python进行智能图像处理...
  11. Python模拟入栈出栈操作
  12. YISplashScreen
  13. FPGA学习记录(7)<巴特沃斯低通IIR滤波器FPGA实现>
  14. 回波损耗和电压驻波比
  15. VScode配置PHP运行环境
  16. ZoomIt v4.5
  17. hudson.plugins.git.GitException
  18. FFmpeg进行音频的解码和播放
  19. Python爬知乎妹子都爱取啥名
  20. java camel exchange类_Exchange服务器之camel 基本概念

热门文章

  1. Android的简介
  2. 【转载】VMware vSphere中三种磁盘规格的解释说明
  3. python snmp采集交换机信息_网管交换机与非网管交换机的利弊介绍
  4. Linux:rsync error: remote command not found (code 127) at io.c(226) [sender=3.1.2
  5. 谷歌为何会选用TypeScript?
  6. [转载] Linux进程基础
  7. 追踪多省网络故障:域名解析瘫痪后的连锁反应
  8. Pravega应用实战:为什么云原生特性对流处理很重要?
  9. 爱培科963方案GPS升级ROM过程以及SDK开发
  10. java获取两张图片的相似度