234树也是一种平衡树,与红黑树同构,后来由效率更高的红黑树取代,但编程容易。
特点:非叶节点的子节点数比它含有的数据项多1;不允许节点只有一个子节点,可以有2,3,4个;新的数据总是插入在叶节点里,在树的最底层;插入的时候遇到满的节点就将其分裂。
234树的查找效率为M*log4(N),M为每个节点数据项的平均数,以2计算,则为2*log4(N),与红黑树都是O(logN),但是存储效率以每个节点平均2个数据项计算,234树大约有2/7的空间浪费了,对于java中存储引用而非对象的方式,差别不大,但是对于存储对象的语言来说,存储空间差异还是很大的。
//摘自Java数据结构与算法第二版
//234树
//存储一个数值
class DataItem {
int data;
public DataItem(int data) {
this.data = data;
}
public void display() {
System.out.println("/" + data);
}
}
// 主要是一个包含数值的对象的数组,一个子节点数组
class Node {
private static final int N = 4;
private int numItems;// 当前节点数据个数
private Node parent;
private Node[] nodeArray = new Node[N];// 当前节点的子节点数组
private DataItem[] itemArray = new DataItem[N - 1];// 当前节点的数值数组
// 指定位置连接子节点
public void connectChild(int index, Node child) {
nodeArray[index] = child;
if (child != null)
child.parent = this;
}
// 解绑指定位置子节点
public Node disconnectChild(int index) {
Node temp = nodeArray[index];
nodeArray[index] = null;
return temp;
}
public Node getChild(int index) {
return nodeArray[index];
}
public Node getParent() {
return parent;
}
public int getNumItems() {
return numItems;
}
public DataItem getItem(int index) {
return itemArray[index];
}
// 根据key在当前节点找索引index
public int findItem(int key) {
for (int i = 0; i < numItems; i++) {
if (itemArray[i] == null)
break;
else if (itemArray[i].data == key)
return i;
}
return -1;// 找不到返回-1
}
// 假设不满时插入,后面通过对满的节点分裂,保证了这一点
public int insertItem(DataItem item) {
numItems++;
int key = item.data;
for (int i = N - 2; i >= 0; i--) {
if (itemArray[i] == null)
continue;
else {
int curkey = itemArray[i].data;
if (key < curkey) {
itemArray[i + 1] = itemArray[i];
} else {
itemArray[i + 1] = item;
return i + 1;
}
}
}
itemArray[0] = item;
return 0;
}
public DataItem removeItem() {
DataItem temp = itemArray[numItems - 1];
itemArray[numItems - 1] = null;
numItems--;
return temp;
}
public void displayNode() {
for (int i = 0; i < numItems; i++)
itemArray[i].display();
System.out.println("/");// 斜线分隔方便显示
}
public boolean isLeaf() {
return nodeArray[0] == null;
}
public boolean isFull() {
return numItems == N - 1;
}
}
public class Tree234 {
private Node root = new Node();
public int find(int key) {
Node curNode = root;
int childNum;
while (true) {
if ((childNum = curNode.findItem(key)) != -1)
return childNum;
else if (curNode.isLeaf())
return -1;
else
curNode = getNextChild(curNode, key);
}
}
private Node getNextChild(Node curNode, int key) {
int nums = curNode.getNumItems();
for (int i = 0; i < nums; i++) {
if (key < curNode.getItem(i).data)
return curNode.getChild(i);
}
return curNode.getChild(nums);
}
public void insert(int data) {
Node curNode = root;
DataItem item = new DataItem(data);
while (true) {
// 分裂步骤:将第三个数值和相应子节点移到新的节点,新节点作为当前节点的右兄弟节点
// 将第二个数值插入父节点,调整父节点的子节点,将新节点连到父节点上
if (curNode.isFull()) {// 满节点进行分裂
split(curNode);
curNode = curNode.getParent();
curNode = getNextChild(curNode, data);// 这2步分裂后回退再次找插入位置
} else if (curNode.isLeaf())
break;
else
curNode = getNextChild(curNode, data);
}
curNode.insertItem(item);
}
private void split(Node node) {
DataItem itemB, itemC;// 对应第二、三个数值
Node parent, child2, child3;// 对应后2个子节点
int itemIndex;
itemC = node.removeItem();
itemB = node.removeItem();
child2 = node.disconnectChild(2);
child3 = node.disconnectChild(3);
Node newRight = new Node();// 新的兄弟节点
if (node == root) {// 根节点满了需要新建根节点
root = new Node();
parent = root;
root.connectChild(0, node);
} else
parent = node.getParent();
itemIndex = parent.insertItem(itemB);
int num = parent.getNumItems();
// 插入itemB后,父节点的相应子节点位置也要右移
for (int j = num - 1; j > itemIndex; j--) {
Node temp = parent.disconnectChild(j);
parent.connectChild(j + 1, temp);
}
parent.connectChild(itemIndex + 1, newRight);
newRight.insertItem(itemC);
newRight.connectChild(0, child2);
newRight.connectChild(1, child3);
}
public void displayTree() {
fineDisplay(root, 0, 0);
}
private void fineDisplay(Node node, int level, int childNum) {
System.out.println("level=" + level + " child=" + childNum + " ");
node.displayNode();
int nums = node.getNumItems();
for (int i = 0; i < nums + 1; i++) {// child比数据个数多1
Node child = node.getChild(i);
if (child != null)
fineDisplay(child, level + 1, i);
else
return;
}
}
//从小到大遍历
public void sort(Node node) {
int nums = node.getNumItems();
for (int i = 0; i < nums; i++) {
Node child = node.getChild(i);
if(child.isLeaf()){
child.displayNode();
System.out.println(node.getItem(i).data);
}
else sort(child);
}
Node last=node.getChild(nums);
if(last.isLeaf())
last.displayNode();
else sort(last);
}
public static void main(String[] args) {
Tree234 tree = new Tree234();
Random rand=new Random();
Set
set=new HashSet<>();//重复键好像有问题
for(int i=0;i<20;i++){
set.add(rand.nextInt(100));
}
Iterator it=set.iterator();
while(it.hasNext())
tree.insert(it.next());
tree.displayTree();
tree.sort(tree.root);
}
}