2-3 查找樹及其Java實現

 

 

2-3 查找樹

定義(來源:wiki)

2–3樹是一種樹型數據結構,內部節點(存在子節點的節點)要麼有2個孩子和1個數據元素,要麼有3個孩子和2個數據元素,葉子節點沒有孩子,而且有1個或2個數據元素。html

 

2個結點
2個結點
3個結點

 

  • 定義java

    若是一個內部節點擁有一個數據元素、兩個子節點,則此節點爲2節點node

    若是一個內部節點擁有兩個數據元素、三個子節點,則此節點爲3節點算法

    當且僅當如下敘述中有一條成立時,T爲2–3樹:緩存

    • T爲空。即T不包含任何節點。
    • T爲擁有數據元素a的2節點。若T的左孩子爲L、右孩子爲R,則
      • LR是等高的非空2–3樹;
      • a大於L中的全部數據元素;
      • a小於等於R中的全部數據元素。
    • T爲擁有數據元素ab的3節點,其中a < b。若T的左孩子爲L、中孩子爲M、右孩子爲R,則
      • LM、和R是等高的非空2–3樹;
      • a大於L中的全部數據元素,而且小於等於M中的全部數據元素;
      • b大於M中的全部數據元素,而且小於等於R中的全部數據元素。

查找

首先咱們說一下查找數據結構

2-3查找樹的查找和二叉樹很相似,無非就是進行比較而後選擇下一個查找的方向。 (這幾張圖不知道來源,知道的呲我一聲)svg

 

2-3樹查找
2-3樹查找

 

插入

2-3查找樹的插入函數

咱們能夠思考一下,爲何要兩個結點。在前面能夠知道,二叉查找樹變成鏈表的緣由就是由於新插入的結點沒有選擇的」權利」,當咱們插入一個元素的時候,實際上它的位置已經肯定了, 咱們並不能對它進行操做。那麼2-3查找樹是怎麼作到賦予「權利」的呢?祕密即是這個多出來結點,他能夠緩存新插入的結點。(具體咱們將在插入的時候講)this

前面咱們知道,2-3查找樹分爲2結點3結點,so,插入就分爲了2結點插入和3結點插入。spa

**2-結點插入:**向2-結點插入一個新的結點和向而插入插入一個結點很相似,可是咱們並非將結點「吊」在結點的末尾,由於這樣就沒辦法保持樹的平衡。咱們能夠將2-結點替換成3-結點便可,將其中的鍵插入這個3-結點便可。(至關於緩存了這個結點)

 


 

 

3-結點插入: 3結點插入比較麻煩,emm能夠說是特別麻煩,它分爲3種狀況。

  1. 向一棵只含有3-結點的樹插入新鍵。

    假如2-3樹只有一個3-結點,那麼當咱們插入一個新的結點的時候,咱們先假設結點變成了4-結點,而後使得中間的結點爲根結點,左邊的結點爲其左結點,右邊的結點爲其右結點,而後構成一棵2-3樹,樹的高度加1

     


     

     

  2. 向父結點爲2-結點的3-結點中插入新鍵。

    和上面的狀況相似,咱們將新的節點插入3-結點使之成爲4-結點,而後將結點中的中間結點」升「到其父節點(2-結點)中的合適的位置,使其父節點成爲一個3-節點,而後將左右節點分別掛在這個3-結點的恰當位置,樹的高度不發生改變

 


 

 

  1. 向父節點爲3-結點的3-結點中插入新鍵。

    這種狀況有點相似遞歸:當咱們的結點爲3-結點的時候,咱們插入新的結點會將中間的元素」升「父節點,而後父節點爲4-結點,右將中間的結點」升「到其父結點的父結點,……如此進行遞歸操做,直到遇到的結點再也不是3-結點。

 


 

 

JAVA代碼實現2-3樹

接下來就是最難的操做來了,實現這個算法,2-3查找樹的算法比較麻煩,因此咱們不得不將問題分割,分割求解能將問題變得簡單。參考博客

接下來就是最難的操做來了,實現這個算法,2-3查找樹的算法比較麻煩,因此咱們不得不將問題分割,分割求解能將問題變得簡單。參考博客

首先咱們定義數據結構,做用在註釋已經寫的很清楚了。

public class Tree23<Key extends Comparable<Key>,Value> {
        /** * 保存key和value的鍵值對 * @param <Key> * @param <Value> */
    private class Data<Key extends Comparable<Key>,Value>{
        private Key key;
        private Value value;

        public Data(Key key, Value value) {
            this.key = key;
            this.value = value;
        }
        public void displayData(){
            System.out.println("/" + key+"---"+value);
        }
    }

    /** * 保存樹結點的類 * @param <Key> * @param <Value> */
    private class Node23<Key extends Comparable<Key>,Value>{

        public void displayNode() {
            for(int i = 0; i < itemNum; i++){
                itemDatas[i].displayData();
            }
            System.out.println("/");
        }

        private static final int N = 3;
        // 該結點的父節點
        private Node23 parent;
        // 子節點,子節點有3個,分別是左子節點,中間子節點和右子節點
        private Node23[] chirldNodes = new Node23[N];
        // 表明結點保存的數據(爲一個或者兩個)
        private Data[] itemDatas = new Data[N - 1];
        // 結點保存的數據個數
        private int itemNum = 0;

        /** * 判斷是不是葉子結點 * @return */
        private boolean isLeaf(){
            // 假如不是葉子結點。必有左子樹(能夠想想爲何?)
            return chirldNodes[0] == null;
        }

        /** * 判斷結點儲存數據是否滿了 * (也就是是否存了兩個鍵值對) * @return */
        private boolean isFull(){
            return itemNum == N-1;
        }

        /** * 返回該節點的父節點 * @return */
        private Node23 getParent(){
            return this.parent;
        }

        /** * 將子節點鏈接 * @param index 鏈接的位置(左子樹,中子樹,仍是右子樹) * @param child */
        private void connectChild(int index,Node23 child){
            chirldNodes[index] = child;
            if (child != null){
                child.parent = this;
            }
        }

        /** * 解除該節點和某個結點之間的鏈接 * @param index 解除連接的位置 * @return */
        private Node23 disconnectChild(int index){
            Node23 temp = chirldNodes[index];
            chirldNodes[index] = null;
            return temp;
        }

        /** * 獲取結點左或右的鍵值對 * @param index 0爲左,1爲右 * @return */
        private Data getData(int index){
            return itemDatas[index];
        }

        /** * 得到某個位置的子樹 * @param index 0爲左指數,1爲中子樹,2爲右子樹 * @return */
        private Node23 getChild(int index){
            return chirldNodes[index];
        }

        /** * @return 返回結點中鍵值對的數量,空則返回-1 */
        public int getItemNum(){
            return itemNum;
         }

        /** * 尋找key在結點的位置 * @param key * @return 結點沒有key則放回-1 */
        private int findItem(Key key){
            for (int i = 0; i < itemNum; i++) {
                if (itemDatas[i] == null){
                    break;
                }else if (itemDatas[i].key.compareTo(key) == 0){
                    return i;
                }
            }
            return -1;
        }

        /** * 向結點插入鍵值對:前提是結點未滿 * @param data * @return 返回插入的位置 0或則1 */
        private int insertData(Data data){
            itemNum ++;
            for (int i = N -2; i >= 0 ; i--) {
                if (itemDatas[i] == null){
                    continue;
                }else{
                    if (data.key.compareTo(itemDatas[i].key)<0){
                        itemDatas[i+1] = itemDatas[i];
                    }else{
                        itemDatas[i+1] = data;
                        return i+1;
                    }
                }
            }
            itemDatas[0] = data;
            return 0;
        }

        /** * 移除最後一個鍵值對(也就是有右邊的鍵值對則移右邊的,沒有則移左邊的) * @return 返回被移除的鍵值對 */
        private Data removeItem(){
            Data temp = itemDatas[itemNum - 1];
            itemDatas[itemNum - 1] = null;
            itemNum --;
            return temp;
        }
    }
    /** * 根節點 */
    private Node23 root = new Node23();
    ……接下來就是一堆方法了
}

主要是兩個方法:find查找方法和Insert插入方法:看註釋

/** *查找含有key的鍵值對 * @param key * @return 返回鍵值對中的value */
public Value find(Key key) {
    Node23 curNode = root;
    int childNum;
    while (true) {
        if ((childNum = curNode.findItem(key)) != -1) {
            return (Value) curNode.itemDatas[childNum].value;
        }
        // 假如到了葉子節點尚未找到,則樹中不包含key
        else if (curNode.isLeaf()) {
            return null;
        } else {
            curNode = getNextChild(curNode,key);
        }
    }
}

/** * 在key的條件下得到結點的子節點(可能爲左子結點,中間子節點,右子節點) * @param node * @param key * @return 返回子節點,若結點包含key,則返回傳參結點 */
private Node23 getNextChild(Node23 node,Key key){
    for (int i = 0; i < node.getItemNum(); i++) {
        if (node.getData(i).key.compareTo(key)>0){
            return node.getChild(i);
        }
        else if (node.getData(i).key.compareTo(key) == 0){
            return node;
        }
    }
    return node.getChild(node.getItemNum());
}

/** * 最重要的插入函數 * @param key * @param value */
public void insert(Key key,Value value){
    Data data = new Data(key,value);
    Node23 curNode = root;
    // 一直找到葉節點
    while(true){
        if (curNode.isLeaf()){
            break;
        }else{
            curNode = getNextChild(curNode,key);
            for (int i = 0; i < curNode.getItemNum(); i++) {
                // 假如key在node中則進行更新
                if (curNode.getData(i).key.compareTo(key) == 0){
                    curNode.getData(i).value =value;
                    return;
                }
            }
        }
    }

    // 若插入key的結點已經滿了,即3-結點插入
    if (curNode.isFull()){
        split(curNode,data);
    }
    // 2-結點插入
    else {
        // 直接插入便可
        curNode.insertData(data);
    }
}

/** * 這個函數是裂變函數,主要是裂變結點。 * 這個函數有點複雜,咱們要把握住原理就行了 * @param node 被裂變的結點 * @param data 要被保存的鍵值對 */
private void split(Node23 node, Data data) {
    Node23 parent = node.getParent();
    // newNode用來保存最大的鍵值對
    Node23 newNode = new Node23();
    // newNode2用來保存中間key的鍵值對
    Node23 newNode2 = new Node23();
    Data mid;

    if (data.key.compareTo(node.getData(0).key)<0){
        newNode.insertData(node.removeItem());
        mid = node.removeItem();
        node.insertData(data);
    }else if (data.key.compareTo(node.getData(1).key)<0){
        newNode.insertData(node.removeItem());
        mid = data;
    }else{
        mid = node.removeItem();
        newNode.insertData(data);
    }
    if (node == root){
        root = newNode2;
    }
    /** * 將newNode2和node以及newNode鏈接起來 * 其中node鏈接到newNode2的左子樹,newNode * 鏈接到newNode2的右子樹 */
    newNode2.insertData(mid);
    newNode2.connectChild(0,node);
    newNode2.connectChild(1,newNode);
    /** * 將結點的父節點和newNode2結點鏈接起來 */
    connectNode(parent,newNode2);
}

/** * 連接node和parent * @param parent * @param node node中只含有一個鍵值對結點 */
private void connectNode(Node23 parent, Node23 node) {
    Data data = node.getData(0);
    if (node == root){
        return;
    }
    // 假如父節點爲3-結點
    if (parent.isFull()){
        // 爺爺結點(爺爺救葫蘆娃)
        Node23 gParent = parent.getParent();
        Node23 newNode = new Node23();
        Node23 temp1,temp2;
        Data itemData;

        if (data.key.compareTo(parent.getData(0).key)<0){
            temp1 = parent.disconnectChild(1);
            temp2 = parent.disconnectChild(2);
            newNode.connectChild(0,temp1);
            newNode.connectChild(1,temp2);
            newNode.insertData(parent.removeItem());

            itemData = parent.removeItem();
            parent.insertData(itemData);
            parent.connectChild(0,node);
            parent.connectChild(1,newNode);
        }else if(data.key.compareTo(parent.getData(1).key)<0){
            temp1 = parent.disconnectChild(0);
            temp2 = parent.disconnectChild(2);
            Node23 tempNode = new Node23();

            newNode.insertData(parent.removeItem());
            newNode.connectChild(0,newNode.disconnectChild(1));
            newNode.connectChild(1,temp2);

            tempNode.insertData(parent.removeItem());
            tempNode.connectChild(0,temp1);
            tempNode.connectChild(1,node.disconnectChild(0));

            parent.insertData(node.removeItem());
            parent.connectChild(0,tempNode);
            parent.connectChild(1,newNode);
        } else{
            itemData = parent.removeItem();

            newNode.insertData(parent.removeItem());
            newNode.connectChild(0,parent.disconnectChild(0));
            newNode.connectChild(1,parent.disconnectChild(1));
            parent.disconnectChild(2);
            parent.insertData(itemData);
            parent.connectChild(0,newNode);
            parent.connectChild(1,node);
        }
        // 進行遞歸
        connectNode(gParent,parent);
    }
    // 假如父節點爲2結點
    else{
        if (data.key.compareTo(parent.getData(0).key)<0){
            Node23 tempNode = parent.disconnectChild(1);
            parent.connectChild(0,node.disconnectChild(0));
            parent.connectChild(1,node.disconnectChild(1));
            parent.connectChild(2,tempNode);
        }else{
            parent.connectChild(1,node.disconnectChild(0));
            parent.connectChild(2,node.disconnectChild(1));
        }
        parent.insertData(node.getData(0));
    }
}

2-3查找樹的原理很簡單,甚至說代碼實現起來難度都不是很大,可是卻很繁瑣,由於它有不少種狀況,而在紅黑樹中,用巧妙的方法使用了2個結點解決了3個結點的問題。

相關文章
相關標籤/搜索