《C算法》笔记12:2-3-4树
来源:互联网 发布:计划表软件 编辑:程序博客网 时间:2024/04/29 11:01
今天李善平突然在课上讲起人工智能,把蔡登的课程计划批判了一番。机器学习已经远远把70年代的计算机技术抛在后头,但是从数学角度来讲实际上应当是另外一个分支。他那个时候的人工智能课程(90年代初),肯定要讲一阶谓词,讲A*,而现在呢?都在讲数理统计,最大似然,最小二乘,梯度下降。红黑树的前身,2-3-4树,虽然在书中没有明确指出,想必也是60年代斯坦福计算机系的产物。
百度百科
2-3-4 树把数据存储在叫做元素的单独单元中。它们组合成节点。
每个节点都是下列之一:
2-节点,就是说,它包含 1 个元素和 2 个儿子;
3-节点,就是说,它包含 2 个元素和 3 个儿子;
4-节点,就是说,它包含 3 个元素和 4 个儿子。
(4节点的例子:小于1的元素沿着最左路径往下递归插入,1-5之间的元素在第二条路径插入,5-10,10以上的元素在后面两条路径)
每个儿子都是(可能为空)一个子 2-3-4 树。根节点是其中没有父亲的那个节点;它在遍历树的时候充当起点,因为从它可以到达所有的其他节点。叶子节点是有至少一个空儿子的节点。
同B树一样,2-3-4 树是有序的:每个元素必须大于或等于它左边的和它的左子树中的任何其他元素。每个儿子因此成为了由它的左和右元素界定的一个区间。
性质
1、除了第一次,永远在叶子节点插入,而叶子节点一定是有数据非空的。
2、为了保障性质1,路径上的所有4-节点请分裂成三个2-节点,并将中间的2-节点插入父节点。
由上两条可得
3、所有叶子节点到根的距离相等。
第三条性质是2-3-4树能达到平衡的原因,它可以从前面两条推导得出,也是红黑树的根本性质。因为红黑树采用了一种比较巧妙的方式将2-3-4树加以改造,这也造成红黑树插入效率能达到1.02Log(n)。
代码
num代表元素个数
item代表元素
child代表儿子,永远比元素多一个
leaf代表叶子节点
typedef struct BSTNode* BLink;struct BSTNode{ int num; // 1, 2, 3 Item item[3]; BLink child[4]; bool leaf;
新节点的写法:
BLink _new_node(Item item, int num, BLink c0, BLink c1, BLink c2, BLink c3){ BLink new_node = (BLink)malloc(sizeof(BSTNode)); new_node->item[0] = item, new_node->num = num; new_node->child[0] = c0, new_node->child[1] = c1, new_node->child[2] = c2, new_node->child[3] = c3; new_node->leaf = (c0 == NULL); return new_node;}
将一个4-节点分裂成三个2-节点:
BLink _split(BLink h){ BLink l, r; l = _new_node(h->item[0], 1, h->child[0], h->child[1], 0, 0); r = _new_node(h->item[2], 1, h->child[2], h->child[3], 0, 0); h->item[0] = h->item[1], h->num = 1; h->child[0] = l, h->child[1] = r, h->leaf = false; return h;}
假如第k个儿子节点发生分裂,那么插入到父节点
BLink _merge(BLink h, int k){ BLink l = h->child[k]->child[0], r = h->child[k]->child[1]; for(int i = h->num - 1; i >= k; -- i) { h->item[i + 1] = h->item[i]; h->child[i + 2] = h->child[i + 1]; } h->item[k] = h->child[k]->item[0]; h->child[k] = l, h->child[k + 1] = r; ++ h->num; return h;}
插入:使用了类似插入排序的做法
假如遇见叶子节点,那么大胆的插入吧!
假如在插入完毕后,发现子节点变成了4-节点,那么将其分裂-插入父节点。具体见代码。
BLink _insert(BLink h, Item item){ if(h == NULL) { return _new_node(item, 1, 0, 0, 0, 0); } int v = KEY(item); if(h->leaf) { int i; for(i = h->num; i > 0; -- i) { if(v < h->item[i - 1].key) { h->item[i] = h->item[i - 1]; } else break; } h->item[i] = item, h->num ++; } else { int i = h->num - 1; for(; i >= 0; -- i) { if(v < h->item[i].key) { h->item[i + 1] = h->item[i]; } else { break; } } h->child[i + 1] = _insert(h->child[i + 1], item); for(int i = 0; i <= h->num; ++ i) { if(h->child[i] && h->child[i]->num == 3) { h->child[i] = _split(h->child[i]); h = _merge(h, i); break; } } } return h;}
总结
2-3-4树不需要旋转。但是分裂插入很麻烦!非常麻烦!very麻烦!(和左小右大的二叉树比起来)我还没写删除操作,就已经码了100多行。在递归调用上,和后来的红黑树相比也会发现有一些区别,原因是分裂的代码不能放在函数的开始。为什么?写过就知道。而且写法复杂是小事情,最关键的地方是,它失去了二叉平衡树的美感,那种统一的操作,又额外添加了插入移动的开销。
- 《C算法》笔记12:2-3-4树
- 《C算法》笔记7:词法解析树
- 算法笔记--c++STL
- C语言-数据结构-算法-笔记
- 算法竞赛学习笔记(c++)
- C / C++算法学习笔记(2)-二叉查找树
- C / C++算法学习笔记(4)-交换法
- Linux C学习笔记-排序算法4-快速排序
- C语言笔记之排序算法(4)
- c语言算法实现-笔记-递归中的树
- C / C++算法学习笔记(3)-冒泡法
- Linux C学习笔记-排序算法3-选择排序
- 算法:C语言实现(第3章 学习笔记)
- 笔记3——C 数组的排序算法
- Linux C学习笔记-排序算法2-冒泡排序
- 【算法】数据结构与算法分析学习笔记——第四章AVL树C语言实现
- 算法:C语言实现笔记(1)
- c++primer学习笔记 - 容器和算法
- 支持向量机SVM(二)
- ubuntu14.04 修改键盘映射 CapsLock->Ctrl
- swift 2.0 元组
- Isomorphic Strings
- 视频大集合
- 《C算法》笔记12:2-3-4树
- 隐藏具体实现
- Binary Tree Paths
- Vollery框架请求网络去除Header中的IF-MODIFIED-SINCE头标签
- linux常用命令
- hadoop1.2.1在linux中配置安装独立运行Standalone Operation,伪分布Pseudo-Distributed Operation,集群配置三种配置和测试
- arcgis 地理数据库解析(一)
- 最小费用最大流
- 【特种兵Excel教程】如何用Excel制作堆积柱形图表?