数据结构系列之2-3-4树的插入、查找、删除和遍历完整版源代码实现与分析(dart语言实现)
本文属于原创,转载请注明来源。
在上一篇博文中,详细介绍了2-3树的操作(具体地址:https://www.cnblogs.com/outerspace/p/10861488.html),那么对于更多教科书上更为普遍的2-3-4树,在这里也给出 树的定义、节点的定义、插入、查找、删除和遍历等操作的源代码实现。
关于2-3-4树的文字定义,网上很多,可自行百度,这里不再重复论述。但看了很多博文,关于插入等操作的实现较多,基本上没有实现删除操作的。因此本博文给出完整版的2-3-4树的插入、删除、查找及遍历等源代码。
另,2-3-4树代码的实现,与2-3树非常类似,故关于代码的分析,请参考2-3树(https://www.cnblogs.com/outerspace/p/10861488.html)的介绍,这里仅给出简要说明。
如果节点允许的最大元素数量超过3个,非叶子节点最大孩子数量超过4个,就成了多叉树,后续有机会分享一下一个关于8叉树的实现。
本代码由dart语言实现,关于dart,一门类似Java、JavaScript的语言,也是现在比较流行的flutter的后台语言,有兴趣的同学可以去dart官网了解:
https://dart.dev
闲话少说,上代码。
// 树的定义 2-3-4树
class QuaternaryTree<E extends Comparable<E>> {
QuaterNode<E> _root;
int _elementsCount; factory QuaternaryTree.of(Iterable<Comparable<E>> elements) {
var tree = QuaternaryTree<E>();
for (var e in elements) tree.insert(e);
return tree;
} QuaternaryTree() : _elementsCount = 0; int get elementsCount => _elementsCount;
QuaterNode<E> get root => _root; // 计算树高
int get height {
var h = 0, c = root;
while (c != null) {
h++;
c = c.isNotLeaf ? c.branches[0] : null;
}
return h;
} bool get isEmpty => _root == null; bool contains(E value) => find(value) != null; // 查找树中是否存在某个值
// 若找到,返回包含此值的树节点,否则返回null
QuaterNode<E> find(E value) {
var c = root;
while (c != null) {
var i = 0;
while (i < c.size && c.items[i].compareTo(value) < 0) i++;
if (i < c.size && c.items[i] == value) break;
c = c.isNotLeaf ? c.branches[i] : null;
}
return c;
} // 插入新的值,如果值已经存在,则不做任何操作
// 插入始终在叶子节点进行
void insert(E value) {
var c = root, i = 0;
while (c != null) {
i = 0;
while (i < c.size && c.items[i].compareTo(value) < 0) i++;
if (i < c.size && c.items[i] == value) return;
if (c.isLeaf) break;
c = c.branches[i];
}
if (c != null) {
c.items.insert(i, value); // 判断插入后是否需要修复
if (c.isOverflow) _fixAfterIns(c);
} else {
_root = QuaterNode([value]);
}
_elementsCount++;
} // 删除指定值
bool delete(E value) {
// 首先查找该值
var d = find(value);
// 若不存在,直接返回
if (d == null) return false; // 查找该值在节点中的索引顺序
var i = d.find(value); // 如果节点不是叶子节点,则用后继节点的值替代该值
// 这样删除操作将转移为删除叶子节点的值
if (d.isNotLeaf) {
var s = _successor(d.branches[i + 1]);
d.items[i] = s.items[0];
d = s;
i = 0;
}
d.items.removeAt(i);
_elementsCount--; // 根据2-3-4树的定义,节点不能为空,因此判断删除后是否需要修复
if (d.items.isEmpty) _fixAfterDel(d);
return true;
} // 遍历树
void traverse(void func(List<E> items)) {
if (!isEmpty) _traverse(_root, func);
} // 插入修复
// 注意,插入修复时,采用节点分裂的形式进行修复;
// 分裂出来的新的父节点需要被上层节点吸收(如果存在上层节点的话)
void _fixAfterIns(QuaterNode<E> c) {
while (c != null && c.isOverflow) {
var t = _split(c);
c = t.parent != null ? _absorb(t) : null;
}
} // 节点分裂,由于分裂->吸收,可能会递归分裂;
// 因此需要注意根节点的更新判断以及子节点的处理;
QuaterNode<E> _split(QuaterNode<E> c) {
var mid = c.size ~/ 2,
l = QuaterNode._internal(c.items.sublist(0, mid)),
nc = QuaterNode._internal(c.items.sublist(mid, mid + 1)),
r = QuaterNode._internal(c.items.sublist(mid + 1));
nc.branches.addAll([l, r]);
l.parent = r.parent = nc; nc.parent = c.parent;
if (c.parent != null) {
var i = 0;
while (c.parent.branches[i] != c) i++;
c.parent.branches[i] = nc;
} else {
_root = nc;
}
if (c.isNotLeaf) {
l.branches
..addAll(c.branches.getRange(0, mid + 1))
..forEach((b) => b.parent = l);
r.branches
..addAll(c.branches.getRange(mid + 1, c.branches.length))
..forEach((b) => b.parent = r);
}
return nc;
} // 上层节点吸收新分裂出来的节点,以保持树的平衡
QuaterNode<E> _absorb(QuaterNode<E> c) {
var i = 0, p = c.parent;
while (p.branches[i] != c) i++;
p.items.insertAll(i, c.items);
p.branches.replaceRange(i, i + 1, c.branches);
c.branches.forEach((b) => b.parent = p);
return p;
} // 查找后继节点
QuaterNode<E> _successor(QuaterNode<E> p) {
while (p.isNotLeaf) p = p.branches[0];
return p;
} // 删除修复
void _fixAfterDel(QuaterNode<E> d) {
if (d == root) {
_root = null;
} else {
var ct = 0;
while (d.size < (1 << ct + 1) - 1 && d.parent != null) {
// 塌缩父节点
_collapse(d.parent);
d = d.parent;
ct++;
} // 如果塌缩到了树的根,则树高减1
// if (d.size < (1 << ct + 1) - 1) ct--;
if (d == root) ct--; // 修剪多余的值
var rest = _prune(d, (1 << ct + 1) - 1); // 重新展开
_expand(d, ct); // 将刚才修剪掉的多余的值重新插入树
for (var e in rest) insert(e);
}
} // 树的塌缩函数,注意递归塌缩
void _collapse(QuaterNode<E> p) {
if (p.isLeaf) return;
for (var i = p.branches.length - 1; i >= 0; i--) {
_collapse(p.branches[i]);
p.items.insertAll(i, p.branches[i].items);
}
p.branches.clear();
} // 修剪,保留满足展开为满二叉树的最小数量的值
List<E> _prune(QuaterNode<E> d, int least) {
var t = d.size ~/ least, rest = <E>[];
if (t < 2) {
rest.addAll(d.items.getRange(least, d.size));
d.items.removeRange(least, d.size);
} else {
// 跳跃修剪,以保证二次插入时分裂的次数较少
var list = <E>[];
for (var i = 0; i < d.size; i++) {
if (i % t == 0 && list.length < least)
list.add(d.items[i]);
else
rest.add(d.items[i]);
}
d.items = list;
}
_elementsCount -= rest.length;
return rest;
} // 递归展开修剪后的节点,ct代表展开的层数或高度
void _expand(QuaterNode<E> p, int ct) {
if (ct == 0) return;
p = _split(p);
for (var b in p.branches) _expand(b, ct - 1);
} void _traverse(QuaterNode<E> r, void f(List<E> items)) {
f(r.items);
for (var b in r.branches) _traverse(b, f);
}
} class QuaterNode<E extends Comparable<E>> {
static final int capacity = 3;
List<E> items;
List<QuaterNode<E>> branches;
QuaterNode<E> parent; factory QuaterNode(List<E> elements) {
if (elements.length > capacity) throw StateError('too many elements.');
return QuaterNode._internal(elements);
} QuaterNode._internal(List<E> elements)
: items = [],
branches = [] {
items.addAll(elements);
} int get size => items.length;
bool get isOverflow => size > capacity;
bool get isLeaf => branches.isEmpty;
bool get isNotLeaf => !isLeaf; bool contains(E value) => items.contains(value);
int find(E value) => items.indexOf(value); String toString() => items.toString();
}
-end-
数据结构系列之2-3-4树的插入、查找、删除和遍历完整版源代码实现与分析(dart语言实现)的更多相关文章
- 数据结构系列之2-3树的插入、查找、删除和遍历完整版代码实现(dart语言实现)
弄懂了二叉树以后,再来看2-3树.网上.书上看了一堆文章和讲解,大部分是概念,很少有代码实现,尤其是删除操作的代码实现.当然,因为2-3树的特性,插入和删除都是比较复杂的,因此经过思考,独创了删除时分 ...
- B树和B+树的插入、删除图文详解
简介:本文主要介绍了B树和B+树的插入.删除操作.写这篇博客的目的是发现没有相关博客以举例的方式详细介绍B+树的相关操作,由于自身对某些细节也感到很迷惑,通过查阅相关资料,对B+树的操作有所顿悟,写下 ...
- B树和B+树的插入、删除图文详解(good)
B树和B+树的插入.删除图文详解 1. B树 1. B树的定义 B树也称B-树,它是一颗多路平衡查找树.我们描述一颗B树时需要指定它的阶数,阶数表示了一个结点最多有多少个孩子结点,一般用字母m表示阶数 ...
- AVL树的插入与删除
AVL 树要在插入和删除结点后保持平衡,旋转操作必不可少.关键是理解什么时候应该左旋.右旋和双旋.在Youtube上看到一位老师的视频对这个概念讲解得非常清楚,再结合算法书和网络的博文,记录如下. 1 ...
- AVL 树的插入、删除、旋转归纳
参考链接: http://blog.csdn.net/gabriel1026/article/details/6311339 1126号注:先前有一个概念搞混了: 节点的深度 Depth 是指从根 ...
- B+树的插入、删除(附源代码)
B+ Tree Index B+树的插入 B+树的删除 完整测试代码 Basic B+树和B树类似(有关B树:http://www.cnblogs.com/YuNanlong/p/6354029.ht ...
- [数据结构与算法分析(Mark Allen Weiss)]二叉树的插入与删除 @ Python
二叉树的插入与删除,来自Mark Allen Weiss的<数据结构与算法分析>. # Definition for a binary tree node class TreeNode: ...
- MySQL B+树 的插入与删除
一.MySQL Index 的插入 有如下B+树,其高度为2,每页可存放4条记录,扇出为5.所有记录都在叶子节点上, 并且是顺序存放,如果用户从最左边的叶子节点开始顺序遍历,可以得到所有简直的顺序 排 ...
- 转:B树和B+树的插入、删除图文详解
如需转载,请注明出处 http://www.cnblogs.com/nullzx/ 1. B树 1. B树的定义 B树也称B-树,它是一颗多路平衡查找树.我们描述一颗B树时需要指定它的阶数,阶数表示了 ...
随机推荐
- LVM介绍及相关操作
一.逻辑卷管理器介绍 逻辑卷管理器(英语:Logical Volume Manager,缩写为LVM),又译为逻辑卷宗管理器.逻辑扇区管理器.逻辑磁盘管理器,是Linux核心所提供的逻辑卷管理(Log ...
- mybatis javabean字段与数据库字段的映射
结论:未作映射的字段没有值,但是数据库中实际是有值的,说明如果带下划线的字段未作映射,返回值是不会有值的,只有映射了的字段以及不带下划线的字段(默认映射)才有返回值 1.bean属性 public c ...
- gdb命令小结
GDB命令小结 gdb <filename> : 调试指定程序文件 r : run 的简写,运行被调试程序, 如果此前没有下过断点,则执行完整个程序:如果有断点, 则程序暂停在第一个可用断 ...
- java面向对象3-继承(继承、抽象类、抽象接口)
4.继承 面向对象概念-类与对象的关系 封装:指隐藏对象的属性和实现细节,仅对外提供公共访问方式,private-构造方法/构造器-this关键字-static关键字(javadoc制作工具类) -代 ...
- MYSQL<五>
-- ########## 01.LIMIT的使用和分页 ########## INSERT INTO studentinfo VALUES(NULL, '刘备', '男', 35), (NULL, ...
- postman—使用newman来执行postman脚本
我们知道postman是基于javascript语言编写的,而导出的json格式的postman脚本也无法直接在服务器运行,它需要在newman中执行(可以把newman看做postman脚本的运行环 ...
- vue - 登录验证与权限控制
描述具体问题 需求 业务系统通常需要登录才能访问受限资源,在用户未登录情况下访问受限资源需要重定向到登录页面: 多个业务系统之间要实现单点登录,即在一个系统或应用已登录的情况下,再访问另一个系统时不需 ...
- UNIX标准C - 进程之间的通信
一.基本概念 进程间通信IPC:进程之间交换数据的过程叫进程间通信 进程间同性的方式: 简单的进程间的通信: 命令行:父进程通过exec函数创建子进程是可以附加一些数据 环境变量表:父进程通过exec ...
- hdu 1166 线段树 区间求和 +单点更新 CD模板
题目链接 敌兵布阵 Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others)Total S ...
- JMS学习十一(ActiveMQ Consumer高级特性之独有消费者(Exclusive Consumer))
一.简介 Queue中的消息是按照顺序被分发到consumers的.然而,当你有多个consumers同时从相同的queue中提取消息时, 你将失去这个保证.因为这些消息是被多个线程并发的处理.有的时 ...