2-3-4 Tree
2-3-4 Tree
简介
2-3-4 Tree又叫2-4 Tree,属于一种平衡查找树,其高度满足:<=$\log_2 x N$,关于性能问题,以后会专门出个小专题来讨论。
– 以下出自[维基百科]
- 2-节点,就是说,他包含1个元素和2个子节点
- 3-节点,就是说,他包含2个元素和3个子节点
- 4-节点,就是说,他包含3个元素和4个子节点
从上面我们可以看出一个非叶子节点的子节点的个数总是比它的元素个数少一个。
2-4Tree是一棵查找树,因此它的元素的排列也是具有一定的顺序。具体可自行查阅。本文主要给出2-4Tree相关操作的主要算法。
为了行文方便,下面统一将2-3-4 Tree成为2-4 Tree。
操作算法分析
本文所用代码取自倪升武的博客,本人只是对其代码进行了一些注解和说明。在此对倪升武 表示感谢!
2-4Tree操作节点的定义代码
这里我们将节点内部的值包装进DataItem类,首先是DataItem类:
// 数据项
class DataItem
{public long dData;public DataItem(long data){dData = data;}public void displayItem(){System.out.print("/" + dData);}
}
然后是2-4Tree的节点定义类:
// 节点
class Node2
{private static final int ORDER = 4;private int numItems; // 表示该节点存有多少个数据项private Node2 parent; //父节点private Node2 childArray[] = new Node2[ORDER]; // 存储子节点的数组,最多4个子节点private DataItem itemArray[] = new DataItem[ORDER - 1];// 该节点中存放数据项的数组,每个节点最多存放3个数据项// 将child节点连接到节点的childNum的位置上public void connectChild(int childNum, Node2 child){childArray[childNum] = child;if (child != null){child.parent = this;}}// 断开与子节点的连接,并返回该子节点public Node2 disconnectChild(int childNum){Node2 tempNode = childArray[childNum];childArray[childNum] = null;return tempNode;}//得到childNum位置上的子节点public Node2 getChild(int childNum){return childArray[childNum];}public Node2 getParent(){return parent;}//判断是否是叶子节点,依据:因为对于非叶子节点,总是满足子节点个数=父节点元素个数+1;在满足这种条件的情况下,子节点的排列总是从childArray[0]开始。public boolean isLeaf(){return (childArray[0] == null);}//得到节点内部元素的个数public int getNumItems(){return numItems;}//得到指定位置的元素public DataItem getItem(int index){return itemArray[index];}//"满"的判断public boolean isFull(){return (numItems == ORDER - 1);}//在节点内部寻找值为key的元素public int findItem(long key){for (int j = 0; j < ORDER - 1; j++)//循环次数为4次{if (itemArray[j] == null)//因为2-3-4树节点内部元素排列总是连续的,即2个元素之间不存在null{break;//满足条件,跳出循环} else if (itemArray[j].dData == key){return j;//找到即返回}}return -1;//未在上述代码返回,说明并未找到,此时返回-1}//节点插入元素(DataItem)public int insertItem(DataItem newItem){//这里我们不考虑节点内元素已满的情况,因为在对树调用这个方法之前,我们会对节点是否满进行判断。numItems++;long newKey = newItem.dData;for (int j = ORDER - 2; j >= 0; j--)//因为没有满,因此元素个数最多为3,因此循环次数定位3,能否优化?(**跟博主沟通过,博主也同意此处可以将循环次数减少至2,因为方法开头我们已经假定元素未满,而且null位置总是位于最后,因此可以不必再判定最后一个元素**){ if (itemArray[j] == null){ // item is nullcontinue; // get left one cell} else{ // not nulllong itsKey = itemArray[j].dData; // get its keyif (newKey < itsKey)//如果新元素值小于itsKey,则将itsKey右移{ itemArray[j + 1] = itemArray[j]; // shift it right} else{itemArray[j + 1] = newItem; // insert new itemreturn j + 1; // 返回添加的新元素的位置}}}//下面主要是考虑该节点为空值节点,即itemArray数组中全部为null的情况。//显然此时应该插入到itemkey[0]的位置itemArray[0] = newItem;return 0;}//移除节点内的值(从itemKey的右边开始移除一个值,并返回移除的值)public DataItem removeItem(){// assumes node not emptyDataItem tempItem = itemArray[numItems - 1]; // save itemitemArray[numItems - 1] = null; // disconnect itnumItems--;return tempItem;}//显示元素数组public void displayNode(){for (int i = 0; i < numItems; i++){itemArray[i].displayItem();}System.out.println("/");}
}
分裂
如果一个节点的元素个数已经达到3个,那么我们称该节点已“满”,在插入新节点的过程中,如果遇到这种节点,我们需要对其进行分裂操作,分裂过程也是保证2-4Tree平衡的一种方式,正是这种分裂过程才保证了树的平衡。(建议先看下面较完整的代码)
public void split(Node2 currentNode)//我感觉这是核心方法,{//假定节点已经满了,因为满节点才能分裂//分裂时,将中间值插到父节点中,右值分离出来独立成一个节点连接到父节点上DataItem itemB, itemC; // 存储要分裂节点的后两个DataItem(即中值和右值)Node2 parent, child2, child3; // 存储要分裂节点的父节点和后两个childint itemIndex;itemC = currentNode.removeItem();//移除本节点的右值,并用itemC保存这个值(右值)itemB = currentNode.removeItem(); // 继续移除本节点的中值,并用itemB保存这个值(中值)child2 = currentNode.disconnectChild(2);//因为中值和右值的移除,因此需要移除与这2个值有关的2个子节点child3 = currentNode.disconnectChild(3); // 同上// nodeNode2 newRight = new Node2(); // make a new nodeif (currentNode == root)//如果root节点“满”,那么将新节点作为root节点{root = new Node2(); // make a new rootparent = root; // root is our parentroot.connectChild(0, currentNode);// connect currentNode to parent} else//如果该满节点不是root节点,那么得到其父节点,便于后面将分裂出的值存到该父节点{parent = currentNode.getParent();}// deal with parentitemIndex = parent.insertItem(itemB); // 中值插入,并返回插入的位置int n = parent.getNumItems(); // 得到父节点中值的个数,便于后面对添加的子节点的操作for (int j = n - 1; j > itemIndex; j--)//因为父节点中有值的插入,因此需要调整与值有关的子节点的连接位置{//调整方法,插入值后方的子节点均后移一位,这样最后就会空出来新插入值与其后方值之间子节点的“线”,然后在此处插入我们分离出来的右值Node2 temp = parent.disconnectChild(j);//parent.connectChild(j + 1, temp);}parent.connectChild(itemIndex + 1, newRight);// deal with newRightnewRight.insertItem(itemC);//在新节点中插入之前我们移除的右值newRight.connectChild(0, child2);//连接与移除的右值有关的子节点newRight.connectChild(1, child3);}
完整代码注解
public class Tree234
{private Node2 root = new Node2();public int find(long key){Node2 currentNode = root;int childNumber;while (true){if ((childNumber = currentNode.findItem(key)) != -1)//在节点中查找,如果返回的位置值不为-1,说明找到了,然后返回位置值{return childNumber;} else if (currentNode.isLeaf())//如果是叶子节点,说明无后继节点了,因此不需要进行下面的移后变换了{return -1;} else{currentNode = getNextChild(currentNode, key);//如果节点中找不到值key,那么定位到他的子节点中寻找。}}}// insert a DataItempublic void insert(long data){Node2 currentNode = root;DataItem tempItem = new DataItem(data);while (true){if (currentNode.isFull())//如果节点是满节点{split(currentNode); // if node is full, split itcurrentNode = currentNode.getParent(); // 父节点被我们改变了,因此需要回退重新判定插入位置currentNode = getNextChild(currentNode, data); // 判定插入位置} else if (currentNode.isLeaf())//如果是叶子节点,那么该节点就是我们需要将值插入的节点{ // if node if leafbreak; // go insert} else//既不是叶子节点,也不是满节点{currentNode = getNextChild(currentNode, data);//通过值来判定currentNode节点是否需要移位}}currentNode.insertItem(tempItem);//以上代码都是进行寻找插入节点的位置,这里找到之后,进行插入操作。}// display treepublic void displayTree(){recDisplayTree(root, 0, 0);}public Node2 getNextChild(Node2 currentNode, long key){int j;// assumes node is not empty, not full and not leaf//假定节点非空且未满且不是叶子节点(因此后面使用该方法时,会对使用该方法的条件加以限制)int numItems = currentNode.getNumItems();//得到元素的个数for (j = 0; j < numItems; j++)//进行循环比较{if (key < currentNode.getItem(j).dData)//如果key小,则返回j位置处的子节点{return currentNode.getChild(j);}}return currentNode.getChild(j);//循环完毕,则移到尾位置的子节点}public void split(Node2 currentNode)//我感觉这是核心方法,{//假定节点已经满了,因为满节点才能分裂//分裂时,将中间值插到父节点中,右值分离出来独立成一个节点连接到父节点上DataItem itemB, itemC; // 存储要分裂节点的后两个DataItem(即中值和右值)Node2 parent, child2, child3; // 存储要分裂节点的父节点和后两个childint itemIndex;itemC = currentNode.removeItem();//移除本节点的右值,并用itemC保存这个值(右值)itemB = currentNode.removeItem(); // 继续移除本节点的中值,并用itemB保存这个值(中值)child2 = currentNode.disconnectChild(2);//因为中值和右值的移除,因此需要移除与这2个值有关的2个子节点child3 = currentNode.disconnectChild(3); // 同上// nodeNode2 newRight = new Node2(); // make a new nodeif (currentNode == root)//如果root节点“满”,那么将新节点作为root节点{root = new Node2(); // make a new rootparent = root; // root is our parentroot.connectChild(0, currentNode);// connect currentNode to parent} else//如果该满节点不是root节点,那么得到其父节点,便于后面将分裂出的值存到该父节点{parent = currentNode.getParent();}// deal with parentitemIndex = parent.insertItem(itemB); // 中值插入,并返回插入的位置int n = parent.getNumItems(); // 得到父节点中值的个数,便于后面对添加的子节点的操作for (int j = n - 1; j > itemIndex; j--)//因为父节点中有值的插入,因此需要调整与值有关的子节点的连接位置{//调整方法,插入值后方的子节点均后移一位,这样最后就会空出来新插入值与其后方值之间子节点的“线”,然后在此处插入我们分离出来的右值Node2 temp = parent.disconnectChild(j);//parent.connectChild(j + 1, temp);}parent.connectChild(itemIndex + 1, newRight);// deal with newRightnewRight.insertItem(itemC);//在新节点中插入之前我们移除的右值newRight.connectChild(0, child2);//连接与移除的右值有关的子节点newRight.connectChild(1, child3);}public void recDisplayTree(Node2 thisNode, int level, int childNumber){System.out.print("level = " + level + " child = " + childNumber + " ");thisNode.displayNode();// call ourselves for each child of this nodeint numItems = thisNode.getNumItems();for (int j = 0; j < numItems + 1; j++){Node2 nextNode = thisNode.getChild(j);if (nextNode != null){recDisplayTree(nextNode, level + 1, j);} elsecontinue;}}
}
如果有不当之处,敬请各位朋友指正!
2-3-4 Tree相关推荐
- 107. Binary Tree Level Order Traversal II
题目 Given a binary tree, return the bottom-up level order traversal of its nodes' values. (ie, from l ...
- 102. Binary Tree Level Order Traversal
题目 Binary Tree Level Order Traversal 层次遍历二叉树 链接 Given a binary tree, return the level order traversa ...
- Python---哈夫曼树---Huffman Tree
今天要讲的是天才哈夫曼的哈夫曼编码,这是树形数据结构的一个典型应用. !!!敲黑板!!!哈夫曼树的构建以及编码方式将是我们的学习重点. 老方式,代码+解释,手把手教你Python完成哈夫曼编码的全过程 ...
- [Java]LeetCode297. 二叉树的序列化与反序列化 | Serialize and Deserialize Binary Tree
★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★ ➤微信公众号:山青咏芝(shanqingyongzhi) ➤博客园地址:山青咏芝(https://www.cnblog ...
- Code Forces Bear and Forgotten Tree 3 639B
B. Bear and Forgotten Tree 3 time limit per test2 seconds memory limit per test256 megabytes inputst ...
- Codeforces Round #417:E. FountainsSagheer and Apple Tree(树上博弈)
Codeforces Round #417:E. FountainsSagheer and Apple Tree(树上博弈) 标签: codeforces 2017-06-02 11:41 29人阅读 ...
- datagrid底部显示水平滚动_DevExpress WPF v19.1:Data Grid/Tree List等控件功能增强
行业领先的.NET界面控件DevExpress 日前正式发布v19.1版本,本站将以连载的形式介绍各版本新增内容.在本系列文章中将为大家介绍DevExpress WPF v19.1中新增的一些控件及部 ...
- java easyui tree例子_EasyUI Tree的简单使用
此前写过zTree插件的demo,没有记录下来,这次记录一下EasyUI的Tree. 实现效果:获取数据库表的数据,以树结构的形式展示出来. 树结构数据分为同步加载和异步加载,同步加载就是初始化加载时 ...
- windows用 tree命令查看目录文件夹结构
windows用 tree命令查看目录文件夹结构 ## 查看帮助 tree --helptree --dirsfirst --filelimit 6 -h -t –dirsfirst 目录优先展示 – ...
- PCL :K-d tree 2 结构理解
K-d tree 基础思路:(先看之前的KNN思想,更容易理解) 导语:kd 树是一种二叉树数据结构,可以用来进行高效的 kNN 计算.kd 树算法偏于复杂,本篇将先介绍以二叉树的形式来记录和索引空间 ...
最新文章
- 子网划分为什么全0全1子网号不能使用?CIDR为什么能使用全0全1子网号?
- Wine cannot find the ncurses library (libncurses.so.5)
- 信息学奥赛一本通(1320:【例6.2】均分纸牌(Noip2002))
- 12010.linux应用程序之spi
- Oracle DBA课程系列笔记(4)
- 开张第一天,一年之际在于春
- maven打包失败:自定义项目工具类打包给其他微服务使用
- http协议的状态码——400,401,403,404,500,502,503,301,302等常见网页错误代码
- HDU1426(DFS)
- 1.2 编程语言选择 | 排行榜、对比、现状,java c++语言对比,哪个工资高、难度更高,mysql数据库对比,java入门怎么学
- 计算机应用能力考试湖南成绩查询,湖南计算机等级考试成绩查询入口
- 系统垂直越权与水平越权漏洞修复记录
- CALIPSO Quality Statements Lidar Level 2 Cloud and Aerosol Layer Products
- 1.4、云计算HCIA虚拟化存储基础知识
- Macbook删除苹果系统,装单系统win10系统
- Reog Ponorogo是爪哇族人在印尼的一个部落的传统舞蹈
- CSDN如何快速提升等级
- HTC Android系统 自定义ROM定制图文教程
- 流程引擎之Activiti简介
- Tailwind Background