树——2-3-4树、B树(B+树、B-树)

目录

    • 2-3-4树
    • B树

2-3-4树

2-3-4树的特点:

  • 它是平衡树;
  • 每个节点最多可以存三个数据项;
  • 不存在空节点;
  • 叶节点可以有数据项没有子节点;
  • 插入数据项的时候数据总是插入在叶节点中,这点很重要;
  • 对于非叶节点来说:
    有一个数据项的节点总是有两个子节点;
    有两个数据项的节点总是有三个子节点;
    有三个数据项的节点总是有四个子节点;
    L:表示子节点个数;
    D:表示数据项个数;
    针对非叶节点:L = D+1;
    子节点个数=数据项个数+1;

插入数据规则:

  • 对于一个节点来说:右边的数据项总比左边的数据项大;
  • 对于父子节点来说:第一个子节点的所有数据项均比父节点的第一个数据项小,第二个字节点的所有数据项均比父节点的第一个数据项大,比父节点的第二个数据项小等等;
  • 插入的时候节点数据已经满了,就需要拆分;

效率:
和红黑树、二叉树的查找效率差不多;

代码:

/**
 * @ClassName DataItem
 * @Descriptionn 数据项类
 * @Author lzq
 * @Date 2019/6/20 14:43
 * @Version 1.0
 **/
public class DataItem {
    public long data;

    public DataItem(long dd) {
        this.data = dd;
    }

    public void show() {
        System.out.print("/"+data);
    }
}
/**
 * @ClassName TreeNode
 * @Description 2-3-4树节点类
 * @Author lzq
 * @Date 2019/6/20 14:45
 * @Version 1.0
 **/
public class TreeNode {
    private static final int ORDER = 4;  //最大字节点个数
    private int numitems;  //当前节点中数据项的个数
    private TreeNode parent;  //记录父节点
    private TreeNode[] childArray = new TreeNode[ORDER];  //字节点的引用集合
    private DataItem[] itemsArray = new DataItem[ORDER-1];  //数据项集合

    /**
     * 取子节点
     * @param child
     * @return
     */
    public TreeNode getChild(int child) {
        return childArray[child];
    }

    /**
     * 获取父节点
     * @return
     */
    public TreeNode getParent() {
        return parent;
    }

    /**
     * 判断节点是否是叶节点
     * @return
     */
    public boolean isLeaf() {
        //只需要判断子节点集合的第一个是否为空即可
        return childArray[0] == null;
    }

    /**
     * 获取数据项个数
     * @return
     */
    public int getNumitems() {
        return numitems;
    }

    /**
     * 获取指定数据项
     * @param index
     * @return
     */
    public DataItem getItem(int index) {
        return itemsArray[index];
    }

    /**
     * 判断节点数据项是否已满
     * @return
     */
    public boolean isFull() {
        return numitems == (ORDER-1);
    }

    /**
     * 连接一个子节点
     * @param childNum 需要添加数据项的位置
     * @param child 需要添加的子节点
     */
    public void connectChild(int childNum,TreeNode child) {
        childArray[childNum] = child;
        if(child != null) {
            child.parent = this;
        }
    }

    /**
     * 拆分子节点
     * @param childNum
     * @return
     */
    public TreeNode disConnectChild(int childNum){
        TreeNode temp = childArray[childNum];
        childArray[childNum] = null;
        return temp;
    }

    /**
     * 从当前节点中找一个数据项的位置
     * @param key
     * @return
     */
    public int findItem(long key) {
        for (int i = 0; i < ORDER-1; i++) {
            if(itemsArray[i] == null) { //如果第一个数据项都为空,就不用找了
                break;
            }else if(itemsArray[i].data == key){
                return i;  //找到了对应位置,直接返回
            }
        }
        return -1;  //没找到
    }

    /**
     * 插入数据项
     * @param newIteam
     * @return
     */
    public int insertItem(DataItem newIteam) {
        numitems++;
        long newKey = newIteam.data; //拿到新增加的数据项
        for (int i = ORDER-2; i >= 0;i--) {
            if(itemsArray[i] == null) {
                continue;
            }else {
                long temp = itemsArray[i].data;  //拿到原来的数据项
                if(newKey < temp) {
                    itemsArray[i+1] = itemsArray[i];  //往后挪动
                }else {
                    itemsArray[i+1] = newIteam;
                    return i+1;
                }
            }
        }
        itemsArray[0] = newIteam;
        return 0;
    }

    /**
     * 删除一个数据项
     * 删除的是最大的那个
     * @return
     */
    public DataItem removeIteam() {
        DataItem temp = itemsArray[numitems-1];  //拿到最后一个数据项(即要删除的数据项)
        itemsArray[numitems-1] = null;
        numitems--;
        return temp;
    }

    /**
     * 打印节点
     */
    public void show() {
        for (int i = 0; i < numitems; i++) {
            itemsArray[i].show();
        }
        System.out.println("/");
    }
}
/**
 * @ClassName Tree234
 * @Description 2-3-4树
 * @Author lzq
 * @Date 2019/6/20 15:13
 * @Version 1.0
 **/
public class Tree234 {
    private TreeNode root = new TreeNode(); //根节点

    /**
     * 查找一个值
     * @param key
     * @return
     */
    public int find(long key) {
        TreeNode cur = root;  //从根开始找
        int childNumber;

        while (true) {
            if((childNumber = cur.findItem(key)) != -1) {  //找到了
                return childNumber;
            }else if(cur.isLeaf()) { //当前节点是叶子节点
                return -1;  //没找到
            }else {
                cur = getNextChild(cur,key); //当前节点没找到,去它的下一个子节点
            }
        }
    }

    /**
     * 寻找下一个子节点
     * @param cur
     * @param key
     * @return
     */
    private TreeNode getNextChild(TreeNode cur, long key) {
        int i;
        int numItems = cur.getNumitems();  //取数据项的个数
        for (i = 0; i < numItems; i++) {
            if(key < cur.getItem(i).data) {
                return cur.getChild(i);
            }
        }
        return cur.getChild(i);
    }

    /**
     * 新增数据项
     * @param value
     */
    public void insert(long value) {
        TreeNode cur = root;
        DataItem temp = new DataItem(value);  //数据项

        //找到要插入数据项的节点位置
        while (true) {
            if(cur.isFull()) {  //如果当前节点满了,需要先拆分再查找
                split(cur);  //拆分节点
                cur = cur.getParent();  //拆分之后从它的父节点重新查找
                cur = getNextChild(cur,value); //寻找下一个子节点
            }else if(cur.isLeaf()) {  //如果当前节点是叶子节点而且没有满
                break;
            }else {
                cur = getNextChild(cur,value);
            }
        }
        cur.insertItem(temp);  //将当前数据项插进去
    }

    /**
     * 拆分节点
     * 最复杂的方法
     * @param thisNode
     */
    private void split(TreeNode thisNode) {
        DataItem itemB,itemC;
        TreeNode parent,child2,child3;
        int itemIndex;

        itemC = thisNode.removeIteam();  //因为删除的数据就是节点中最大的
        itemB = thisNode.removeIteam();  //中间的数据项
        child2 = thisNode.disConnectChild(2);
        child3 = thisNode.disConnectChild(3);

        TreeNode newRight = new TreeNode();

        if(thisNode == root) {  //如果拆分的节点是根节点
            root = new TreeNode();
            parent = root;
            root.connectChild(0,thisNode);
        }else {
            parent = thisNode.getParent();
        }

        itemIndex = parent.insertItem(itemB);
        int n = parent.getNumitems();

        for (int i = n-1;i > itemIndex;i--) {
            TreeNode temp = parent.disConnectChild(i);
            parent.connectChild(i+1,temp);
        }

        parent.connectChild(itemIndex+1,newRight);
        newRight.insertItem(itemC);
        newRight.connectChild(0,child2);
        newRight.connectChild(1,child3);
    }



    public void show() {
        print(root,0,0);
    }

    /**
     * 打印节点
     * @param node 当前节点
     * @param level 层数
     * @param childNum 当前节点是第几个子节点
     */
    private void print(TreeNode node,int level,int childNum) {
        System.out.print("层数="+level+"\t当前节点是第"+childNum+"个子节点\t");
        node.show();

        int numItems = node.getNumitems(); //取子节点个数
        for (int i = 0; i < numItems+1; i++) {
            TreeNode next = node.getChild(i);
            if(next != null) {
                print(next,level+1,i);  //向下层递归
            }else {  //没有下一个节点
                return;
            }
        }
    }
}

测试类:

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

/**
 * @ClassName TestDemo
 * @Description 2-3-4树
 * @Author lzq
 * @Date 2019/6/20 15:47
 * @Version 1.0
 **/
public class TestDemo {
    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.println("--------------------------");
            System.out.println("输入打印(s)、增加(i)或者查找(f)");
            char choice = getChar();
            switch (choice) {
                case 's':
                    theTree.show();
                    break;
                case 'i':
                    System.out.println("输入要插入的值");
                    value = getInt();
                    theTree.insert(value);
                    break;
                case 'f':
                    System.out.println("输入要查找的数据项");
                    value = getInt();
                    int found = theTree.find(value);
                    if(found != -1) {
                        System.out.println("查找到"+value);
                    }else {
                        System.out.println("没有找到数据"+value);
                    }
                    break;
                default:
                    System.out.println("无效输入");
                    break;
            }
        }
    }

    public static String getString() throws IOException{
        InputStreamReader isr = new InputStreamReader(System.in);
        BufferedReader br = new BufferedReader(isr);
        return br.readLine();
    }

    public static char getChar() throws IOException{
        return getString().charAt(0);
    }

    public static int getInt() throws IOException {
        return Integer.parseInt(getString());
    }
}

运行结果:

--------------------------
输入打印(s)、增加(i)或者查找(f)
s
层数=0	当前节点是第0个子节点	/50/
层数=1	当前节点是第0个子节点	/30/40/
层数=1	当前节点是第1个子节点	/60/70/
--------------------------
输入打印(s)、增加(i)或者查找(f)
i
输入要插入的值
35
--------------------------
输入打印(s)、增加(i)或者查找(f)
s
层数=0	当前节点是第0个子节点	/50/
层数=1	当前节点是第0个子节点	/30/35/40/
层数=1	当前节点是第1个子节点	/60/70/
--------------------------
输入打印(s)、增加(i)或者查找(f)
i
输入要插入的值
45
--------------------------
输入打印(s)、增加(i)或者查找(f)
s
层数=0	当前节点是第0个子节点	/35/50/
层数=1	当前节点是第0个子节点	/30/
层数=1	当前节点是第1个子节点	/40/45/
层数=1	当前节点是第2个子节点	/60/70/
--------------------------
输入打印(s)、增加(i)或者查找(f)
f
输入要查找的数据项
40
查找到40
--------------------------
输入打印(s)、增加(i)或者查找(f)
f
输入要查找的数据项
78
没有找到数据78
--------------------------
输入打印(s)、增加(i)或者查找(f)

B树

2-3-4树中一个节点最多只能有4个孩子,那如果每个孩子数再多一些呢,这就是B树;

B树、B-树、B+树、B*树之间的关系

你可能感兴趣的:(数据结构)