一、什么是2-3-4树

2-3-4树和红黑树一样,也是平衡树。只不过不是二叉树,它的子节点数目可以达到4个。

每个节点存储的数据项可以达到3个。名字中的2,3,4是指节点可能包含的子节点数目。具体而言:

1、若父节点中存有1个数据项,则必有2个子节点。

2、若父节点中存有2个数据项,则必有3个子节点。

3、若父节点中存有3个数据项,则必有4个子节点。

也就是说子节点的数目是父节点中数据项的数目加一。因为以上三个规则,使得除了叶结点外,其他节点必有2到4个子节点,不可能只有一个子节点。所以不叫1-2-3-4树。而且2-3-4树中所有叶结点总是在同一层。

二、如何构建2-3-4树。

首先需要明白二叉树一般不允许出现重复的关键字。分析问题时就不考虑这种情况。

构建原则:

实例:

对于包含1或2个数据项的节点,构造原则相同。

搜索2-3-4树:

与二叉树方法类似,只不过比较过程较为复杂,处于不同的数据段之间转向不同的子树。结合上面的构造方法更好理解。

插入操作:

新的数据项总是插入在叶结点中,在树的最底层。这时可能有两种情况:

1.未遇到满节点

这种情况较为简单,只需找到相应的位置插入数据即可。

如下例:插入数据18

2、遇到满节点情况:

为了保证树的平衡和2-3-4树的结构,需要进行分裂操作。从上到下寻找插入位置时遇到的任何一个满节点都要进行分裂操作。

假设满节点中的数据项为A,B,C。根据节点是不是根又分为两种情况。

(1)满节点不是根

分裂方法:

·创建一个新的节点,与要分裂的节点是兄弟,且放在其右侧。

·把数据项C移动到新节点中。

·将数据项B移到父节点中的相应位置。

·将数据项A保留在原节点中。

·把原节点最右边的两个子节点(原节点为满节点,则一定有四个子节点或者是叶结点)从要分裂的节点上断开,连接到新的节点上。

实例:插入99

(2)满节点是根节点

分裂方法:

·创建一个新的根,作为要分裂节点的父节点。

·再创建一个新的节点,作为要分裂节点的兄弟节点,位于其右侧。

·数据项C移动到兄弟节点中。

·数据项B移动到父节点中。

·数据项A保留在原节点。

·把要分裂节点的最右边的两个子节点断开连接,重新连接到兄弟节点上。

实例:插入41

查找是从上到下的,所以分裂也是从最上方的满节点开始。这也保证了要分裂的节点的父节点一定是不满的,否则应该先分裂父节点。

2-3-4树的完整构造过程:

三、2-3-4树的java实现代码

DataItem类表示节点中存储的数据项的数据类型。

class DataItem
{
public long dData; // 存储的数据类型,可以为其他复杂的对象或自定义对象
//--------------------------------------------------------------
public DataItem(long dd) // 构造函数
{ dData = dd; }
//--------------------------------------------------------------
public void displayItem() // 显示数据
{ System.out.print("/"+dData); }
//--------------------------------------------------------------
} // end class DataItem
////////////////////////////////////////////////////////////////

Node类表示节点中的数据存储格式。包含两个数组类型:childArray和itemArray。childArray有四个数据单元,来存储子节点。itemArray有三个数据单元,用来存储DataItem对象(的引用),代表具体内容,而且插入和移除数据时要保持该数组有序(关键字从小到大)。

Node类提供了三个重要方法:

findItem:依据关键字在当前节点的数据项数组itemArray中查找。

insertItem:把数据项插入到itemArray中,并保持有序

removeItem:根据关键字在itemArray中移除相应的数据项,并保持有序。

class Node
{
private static final int ORDER = 4;
private int numItems;//节点中实际存储的数据项数目,其值一定不大于3
private Node parent;
private Node childArray[] = new Node[ORDER];//子节点数组
private DataItem itemArray[] = new DataItem[ORDER-1];//存储数据项数组
//-------------------------------------------------------------
// 把参数中的节点作为子节点,与当前节点进行连接
public void connectChild(int childNum, Node child)
{
childArray[childNum] = child;
if(child != null)
child.parent = this;//当前节点作为父节点
}
//-------------------------------------------------------------
// 断开参数确定的节点与当前节点的连接,这个节点一定是当前节点的子节点。
public Node disconnectChild(int childNum)
{
Node tempNode = childArray[childNum];
childArray[childNum] = null; //断开连接
return tempNode;//返回要这个子节点
}
//-------------------------------------------------------------
public Node getChild(int childNum)//获取相应的子节点
{ return childArray[childNum]; }
//-------------------------------------------------------------
public Node getParent()//获取父节点
{ return parent; }
//-------------------------------------------------------------
public boolean isLeaf()//是否是叶结点
{ return (childArray[0]==null) ? true : false; }//叶结点没有子节点
//-------------------------------------------------------------
public int getNumItems()//获取实际存储的数据项数目
{ return numItems; }
//-------------------------------------------------------------
public DataItem getItem(int index) // 获取具体的数据项
{ return itemArray[index]; }
//-------------------------------------------------------------
public boolean isFull()//该节点是否已满
{ return (numItems==ORDER-1) ? true : false; }
//-------------------------------------------------------------
public int findItem(long key) // 查找
{
for(int j=0; j<ORDER-1; j++) // 遍历数组
{
if(itemArray[j] == null) // 数组未满,未找到
break;
else if(itemArray[j].dData == key)
return j;
}
return -1;
} // end findItem
//-------------------------------------------------------------
public int insertItem(DataItem newItem)//节点未满的插入
{
numItems++;
long newKey = newItem.dData; // 获得关键字 for(int j=ORDER-2; j>=0; j--) // 因为节点未满,所以从倒数第二项向前查找
{
if(itemArray[j] == null) // 没存数据
continue;
else
{
long itsKey = itemArray[j].dData;//获得关键字
if(newKey < itsKey) //插入位置在其前面,但未必相邻
itemArray[j+1] = itemArray[j]; //当前数据项后移
else
{
itemArray[j+1] = newItem; // 在其后位置插入
return j+1; // 返回插入的位置下标
} // new item
} // end else (not null)
} // end for // shifted all items,
//若上述代码没有执行返回操作,那么这是空节点(只有初始时根是这个情况)
itemArray[0] = newItem; // insert new item
return 0;
} // end insertItem()
//-------------------------------------------------------------
public DataItem removeItem() // 移除数据项,从后向前移除
{
// 假设节点非空
DataItem temp = itemArray[numItems-1]; // 要移除的数据项
itemArray[numItems-1] = null; // 移除
numItems--; // 数据项数目减一
return temp; // 返回要移除的数据项
}
//-------------------------------------------------------------
public void displayNode() // format "/24/56/74/"
{
for(int j=0; j<numItems; j++)
itemArray[j].displayItem(); // "/56"
System.out.println("/"); // final "/"
}
//-------------------------------------------------------------
} // end class Node
////////////////////////////////////////////////////////////////

Tree234类来表示一颗完整的2-3-4树。它只有一个数据项:root,类型为Node。我们操作一棵树,只需要知道它的根就行了。

关键方法

find:根据关键字查找树中是否存在。从根开始,依次调用getNextChild方法来向下查找,在每个节点上都调用Node类中的findItem方法在当前节点中查找。当在底层的叶结点查找完毕,整个查找过程就结束了。若仍未找到,则查找失败,返回-1。

insert:与find方法类似,不断向下查找,直到叶结点,插入数据项。这个过程中遇到满节点会先执行分裂操作,调用split方法,再来插入数据项。

split:按照之前介绍的分裂方法进行分裂。

class Tree234
{
private Node root = new Node(); // 创建树的根
//-------------------------------------------------------------
//获取查找的下一个节点
public Node getNextChild(Node theNode, long theValue)
{
int j;
// 假设这个节点不是叶结点
int numItems = theNode.getNumItems();//获得当前节点的数据项数目
for(j=0; j<numItems; j++)
{
if( theValue < theNode.getItem(j).dData )
return theNode.getChild(j); // 返回相应的节点
} // end for
return theNode.getChild(j); // 此时j=numItems
}
//-------------------------------------------------------------
public int find(long key)
{
Node curNode = root;
int childNumber;
while(true)
{
if(( childNumber=curNode.findItem(key) ) != -1)//每次循环这句一定执行
return childNumber; // found it
else if( curNode.isLeaf() )//叶结点上也没找到
return -1; // can't find it
else // 不是叶结点,则继续向下查找
curNode = getNextChild(curNode, key);
} // end while
}
//-------------------------------------------------------------
// 插入数据项
public void insert(long dValue)
{
Node curNode = root;//当前节点标志
DataItem tempItem = new DataItem(dValue);//插入数据项封装 while(true)
{
if( curNode.isFull() ) // 是满节点
{
split(curNode); // 分裂
curNode = curNode.getParent(); // 回到分裂出的父节点上
// 继续向下查找
curNode = getNextChild(curNode, dValue);
} // end if(node is full)
//后面的操作中节点都未满,否则先执行上面的代码
else if( curNode.isLeaf() ) // 是叶结点,非满
break; // 跳出,直接插入 else
curNode = getNextChild(curNode, dValue);//向下查找
} // end while curNode.insertItem(tempItem); // 此时节点一定不满,直接插入数据项,
} // end insert()
//-------------------------------------------------------------
public void split(Node thisNode) // 分裂
{
// 操作中节点一定是满节点,否则不会执行该操作
DataItem itemB, itemC;
Node parent, child2, child3;
int itemIndex; itemC = thisNode.removeItem(); // 移除最右边的两个数据项,并保存为B和C
itemB = thisNode.removeItem(); //
child2 = thisNode.disconnectChild(2); // //断开最右边两个子节点的链接
child3 = thisNode.disconnectChild(3); // Node newRight = new Node(); //新建一个节点,作为当前节点的兄弟节点 if(thisNode==root) // 是根
{
root = new Node(); // 新建一个根
parent = root; // 把新根设为父节点
root.connectChild(0, thisNode); // 连接父节点和子节点
}
else // 不是根
parent = thisNode.getParent(); // 获取父节点 itemIndex = parent.insertItem(itemB); // 把B插入父节点中,返回插入位置
int n = parent.getNumItems(); // 获得总数据项数目 for(int j=n-1; j>itemIndex; j--) //从后向前移除
{
Node temp = parent.disconnectChild(j); // 断开连接
parent.connectChild(j+1, temp); // 连接到新的位置
} parent.connectChild(itemIndex+1, newRight);//连接到新位置 // 处理兄弟节点
newRight.insertItem(itemC); // 将C放入兄弟节点中
newRight.connectChild(0, child2); // 把子节点中最右边的两个连接到兄弟节点上
newRight.connectChild(1, child3); //
} // end split()
//-------------------------------------------------------------
// gets appropriate child of node during search for value public void displayTree()
{
recDisplayTree(root, 0, 0);
}
//-------------------------------------------------------------
private void recDisplayTree(Node thisNode, int level,
int childNumber)
{
System.out.print("level="+level+" child="+childNumber+" ");
thisNode.displayNode(); // display this node // call ourselves for each child of this node
int numItems = thisNode.getNumItems();
for(int j=0; j<numItems+1; j++)
{
Node nextNode = thisNode.getChild(j);
if(nextNode != null)
recDisplayTree(nextNode, level+1, j);
else
return;
}
} // end recDisplayTree()
//-------------------------------------------------------------\
} // end class Tree234
////////////////////////////////////////////////////////////////

Tree234App类,实现具体操作的main函数

import java.io.*;

class Tree234App
{
public static void main(String[] args) throws IOException
{
long value;
Tree234 theTree = new Tree234(); theTree.insert(50);
theTree.insert(40);
theTree.insert(60);
theTree.insert(30);
theTree.insert(70); while(true)
{
System.out.print("Enter first letter of ");
System.out.print("show, insert, or find: ");
char choice = getChar();
switch(choice)
{
case 's':
theTree.displayTree();
break;
case 'i':
System.out.print("Enter value to insert: ");
value = getInt();
theTree.insert(value);
break;
case 'f':
System.out.print("Enter value to find: ");
value = getInt();
int found = theTree.find(value);
if(found != -1)
System.out.println("Found "+value);
else
System.out.println("Could not find "+value);
break;
default:
System.out.print("Invalid entry\n");
} // end switch
} // end while
} // end main()
//--------------------------------------------------------------
public static String getString() throws IOException
{
InputStreamReader isr = new InputStreamReader(System.in);
BufferedReader br = new BufferedReader(isr);
String s = br.readLine();
return s;
}
//--------------------------------------------------------------
public static char getChar() throws IOException
{
String s = getString();
return s.charAt(0);
} //-------------------------------------------------------------
public static int getInt() throws IOException
{
String s = getString();
return Integer.parseInt(s);
}
//-------------------------------------------------------------
} // end class Tree234App
////////////////////////////////////////////////////////////////

插入数据10,20,30,40,50,60,70后形成的2-3-4树为

心得:

* 1、首先分析一个大操作分为几个部分,先进行什么操作,再进行什么操作,把操作的顺序和操作的类别搞清楚。

 * 2、抽象出每个小的操作过程,不考虑具体实现,封装成函数名称。

 * 3、对操作过程进行具体分析,从上到下,对每一种可能情况进行具体分析,这可能会涉及更具体的操作,可以根据情况直接实现。,或者再一次进行函数的封装。

 * 4、编写具体函数从下到上,先分析小的操作实现,一步一步到大的操作上去。

2-3-4树的java实现的更多相关文章

  1. Trie 树 及Java实现

    来源于英文“retrieval”.   Trie树就是字符树,其核心思想就是空间换时间. 举个简单的例子.   给你100000个长度不超过10的单词.对于每一个单词,我们要判断他出没出现过,如果出现 ...

  2. AVL树之 Java的实现

    AVL树的介绍 AVL树是高度平衡的而二叉树.它的特点是:AVL树中任何节点的两个子树的高度最大差别为1. 上面的两张图片,左边的是AVL树,它的任何节点的两个子树的高度差别都<=1:而右边的不 ...

  3. AVL树的JAVA实现及AVL树的旋转算法

    1,AVL树又称平衡二叉树,它首先是一颗二叉查找树,但在二叉查找树中,某个结点的左右子树高度之差的绝对值可能会超过1,称之为不平衡.而在平衡二叉树中,任何结点的左右子树高度之差的绝对值会小于等于 1. ...

  4. 双数组Trie树(DoubleArrayTrie)Java实现

    http://www.hankcs.com/program/java/%E5%8F%8C%E6%95%B0%E7%BB%84trie%E6%A0%91doublearraytriejava%E5%AE ...

  5. AVL树的Java实现

    AVL树:平衡的二叉搜索树,其子树也是AVL树. 以下是我实现AVL树的源码(使用了泛型): import java.util.Comparator; public class AVLTree< ...

  6. 如何打印一棵树(Java)

    1.有一棵多叉树,将它打印出来. import java.util.LinkedList; /** * 需求:按层打印一棵树 * 说明:树是保存在一个链表中 * created by wangjunf ...

  7. 字典树(前缀树)-Java实现

    字典树 字典树是一种树形结构,优点是利用字符串的公共前缀来节约存储空间.在这提供一个自己写的Java实现,非常简洁. 根节点没有字符路径.除根节点外,每一个节点都被一个字符路径找到. 从根节点到某一节 ...

  8. Trie树的java实现

    leetcode 地址: https://leetcode.com/problems/implement-trie-prefix-tree/description/ 难度:中等 描述:略 解题思路: ...

  9. 2-3 查找树及其Java实现

    2-3 查找树 定义(来源:wiki) 查找 插入 2-3 查找树 定义(来源:wiki) 2–3树是一种树型数据结构,内部节点(存在子节点的节点)要么有2个孩子和1个数据元素,要么有3个孩子和2个数 ...

随机推荐

  1. 【HDOJ6229】Wandering Robots(马尔科夫链,set)

    题意:给定一个n*n的地图,上面有k个障碍点不能走,有一个机器人从(0,0)出发,每次等概率的不动或者往上下左右没有障碍的地方走动,问走无限步后停在图的右下部的概率 n<=1e4,k<=1 ...

  2. transform与position:fixed的那些恩怨--摘抄

    1. 前言 在写这篇文章之前,我理解的fixed元素是这样的:(摘自CSS布局基础) 固定定位与absolute定位类型类似,但它的相对移动的坐标是视图(屏幕内的网页窗口)本身.由于视图本身是固定的, ...

  3. Xcode 如何计算整个项目的代码行数

    参考链接:https://www.cnblogs.com/sunfuyou/p/7921538.html 1.打开终端 2.cd 空格 将工程的文件夹拖到终端上,回车,此时进入到工程的路径 此时已经进 ...

  4. 设计模式原来如此-单例模式(Singleton Pattern)

    简单介绍一下我对Singleton的理解,说的不好请大家多多指点. 单例模式的简单定义就是确保一个类只有一个实例,并提供一个全局访问点. 单例模式有哪些用处呢? 有一些对象其实我们只需要一个,比方说: ...

  5. 微信小程序 本地缓存保持登录状态之wx.setStorageSync()使用技巧

    微信小程序提供了一个如同浏览器cookie本地缓存方法,那就是今天要说的wx.setStorageSync() 注意,该方法是同步请求,还有个异步请求的方法是wx.setStorage(),参考官方文 ...

  6. L1-2. 点赞【求多组数据中出现次数最多的】

    L1-2. 点赞 时间限制 200 ms 内存限制 65536 kB 代码长度限制 8000 B 判题程序 Standard 作者 陈越 微博上有个“点赞”功能,你可以为你喜欢的博文点个赞表示支持.每 ...

  7. mybatis-plus generator template 中的全部属性

    { "date": "2018-10-30", "superServiceImplClassPackage": "com.baom ...

  8. 结构体和类中属性定义需要static地方

    private function Readxxx:Integer;static; public class property XXX:Integer read ReadXXx; Txxx =recor ...

  9. n个平面把空间最多分成几个部分?

    题目: n个平面把空间最多分成几个部分? 解答: 1条直线可以把平面分成2部分,2条直线最多可以把平面分成4部分, 3条直线最多可以把平面分成几部分,4条直线呢?你能不能想出n条直线最多可以把平面分成 ...

  10. 算法之美--3.2.2 MP算法

    这块硬骨头,放在这里半年的时间了,一直没有动,今天周末看看,书上把过程写的比较详细,自己基本也看懂了,但是对代码本身的编写还是比较生疏,要经常复习,估计才能看透,后面有看了kmp;这两者之间的关系也是 ...