前面已经说了,2叉树比以前知道的数据结构,如LinkedList和Order Array都要有更高的效率,LinkedList在insert的时候效率很高但是search效率很低,Order Array在search的时候效率很高但是在insert的时候效率怀念低,而2叉树却对这两种数据结构各取优点。。。。。
但是二叉树在某些特殊的情况下,会退变成LinkedList,设想对一个二叉树插入一段有序的数列,那么这个二叉树就会变成只有左子树或者只有右子树的LinkedList。。。这种情况是需要避免的~~~所以引入了红黑树的概念。
红黑树只是在二叉树的基础上通过下面4个原则来约束的:
1. Every node is either red or black.
2. The root is always black.
3. If a node is red, its children must be black (although the converse isn't necessarily
true).
4. Every path from the root to a leaf, or to a null child, must contain the same number of
black nodes.
我真佩服发明红黑树的人,就通过这4个规则就避免的二叉树的不平横情况。。。。
当然红黑树的实现我觉得对我来说就是一个挑战,虽然仅仅需要在二叉树的基础上对insert()和delete()方法进行修改,但是复杂性我觉得可以比的上写3个二叉树了,虽然前几天我都在看红黑树是怎么实现的,但是看到一半我还是放弃了,首先,我觉得自己去实现它完全没那个必要,在实现的过程里面我也没发现有什么好的编程思想,就是照着几个规则去编写就是了,还有就是完全实现它确实太麻烦了。。。。不过我确实佩服那些发明红黑树的人。数学功底吓人那。。。。。
在我前天看了2-3-4树的时候,我发现我不去花精力研究红黑树是怎么实现的是个明智的选择。。。2-3-4树可以通过很简便的编程方法就能实现,而且效率也不比红黑树低多少。。。。现在就来详细说下我对2-3-4树的理解吧。。
2-3-4树,它的名字就告诉我们很多的东西了。2-3-4树中每个结点最多有3个数据项,最多有4个孩子,而他的数据项与孩子有以下规定,就是:
? A node with one data item always has two children
? A node with two data items always has three children
? A node with three data items always has four children
public class DataItem
{
private int key;
private double data;
public DataItem(int key,double data)
{
this.key = key;
this.data = data;
}
public void displayItem
{
System.out.println("/"+data);
}
}
public class Node
{
public static final int ORDER = 4;
private int itemNum;
private Node parent;
private Node[] childArray = new Node[ORDER-1];
private DataItem[] items = new DataItem[ORDER];
.......
public int findItem(int key)
{
for(int j=0;j<ORDER-1;j++)
{
if(items[j] == null)
break;
if(items[j].key==key)
return j;
}
return -1;
}//如果找到就返回这个数据项的位置,如果没找到,就返回-1
public int insertItem(DataItem newItem)
{
//assume the node is not full
itemNum++;
for(int j=ORDER-2;j>=0;j--)
{
if(items[j]!=null)
{
if(newItem.key<items.key)
items[j+1] = items[j];
else
{
item[j+1] = newItem;
return j+1;
}
}
}
items[0] = newItem;
return 0;
}//从最后一个数据项开始查找插入点,如果不是null就找到第1个比要插入的数据项小的,然后插在它后面,如果是null就向前递推找到不是null的再进行比较,如果全是null即这个结点没有数据项,就插在第1个位置
public DataItem removeItem()
{
DataItem temp = items[itemNum-1];
items[itemNum-1] = null;
itemNum--;
return temp;
}//每次删除数据项的时候都是删除最后一个数据项。。。
public void connectChild(int childNum,Node child)
{
childArray[childNum] = child;
if(child!=null)
child.parent = this;
}
public Node disconnectChild(int childNum)
{
Node temp = childArray[childNum];
childArray[childNum] = null;
return temp;
}
}//end Node
public class Tree234
{
private Node root = new Node();
public int find(int key)
{
Node currentNode = root;
int childNumber;
while(true)
{
if(childNumber = currentNode.find(key)!=-1)
return childNumber;//判断要找的dataitem是不是在这个结点中,要是在,就返回位置
else if(currentNode.isLeaf())
return -1;//如果不在这个结点,而这个结点又已经是叶子了,就返回-1,代表找不到
else
currentNode = getNextChild(currentNode,key);//若不在这个结点,就把相应子结点传给当前结点
}
}
public void insert(DataItem newItem)//新结点都是插入到叶子
{
Node curNode = root;
while(true)
{
if(curNode.isFull())
{
split(curNode);//如果当前结点满了,就spilit,也就是把这个结点拆成两个
curNode = curNode.parent;当前结点回到父结点
curNode = getNextChild(curNode,newItem.key);//从父结点找到正确的子结点
}
else if(curNode.isLeaf())
break;//如果找到了叶子就跳出循环
else
curNode = getNextChild(curNode,newItem.key);//如果还没找到叶子就继续寻找。。
}//注意要先判断是否这个结点是否满了,然后在判断是否到了叶子,再往下找,3个顺序不能变
curNode.insertItem(newItem);//在叶子上插入新结点
}
//下面就是最麻烦的split方法,该方法是将已经满了的结点进行拆分,拆分分两种情况1.拆分一般结点2.拆分root
public void split(Node curNode)
{
Node itemB,itemc,child2,child3,parent;
int itemIndex;
itemC = curNode.removeItem();
itemB = curNode.removeItem();
child2 = curNode.disconnectChild(2);
child3 = curNode.disconnectChild(3);
Node newRight = new Node();
if(curNode == root)
{
root = new Node();
parent = root;
root.connectChild(0,curNode);
}拆分root时,要再新建一个结点作为root,在把当前结点连到新的root的0
else
parent = curNode.getParent();
itemIndex = parent.insertItem(itemB);
int n = parent.getItemNum();
for(int j=n-1;j>itemIndex;j--)
{
Node temp = parent.disconnectChild(j);
parent.connectChild(j+1,temp);
}//若B插入到parent的itemIndex位置,那么原来的parent的孩子,只要是大于itenIndex,都要向右移
parent.connectChild(itemIndex+1,newRight);//itemIndex+1孩子指向新建的右结点
newRight.insertItem(itemC);
newRight.connectChild(0,child2);
newRight.connectChild(1,child3);//将当前结点的,2/3孩子传给新的右结点的0/1孩子。
}
public Node getNextChild(Node curNode,int key)
{
int itemNum = curNode.getNumItems();
for(int j=0;j<itemNum;j++)
{
if(key<curNode.items[j])
return curNode.getChild(j);
}
return curNode.getChild(j);
}
public void displayTree()
{
.....
}
}