B樹的 JAVA 實現


  因為感覺對 B 樹的理解不是特別深刻,一直想手擼一個 B 樹,這次終於得償所願,文末有完整的 B 樹代碼。

  代碼比較長,大概六百行。

  B 樹的代碼使用了一百組數據進行 插入/刪除 測試,結果正確。

  從生產講,實現一棵 B 樹不會有什么實際意義,但是這些代碼和構建這些代碼的思路,都將成為我們職業素養的一部分。

什么是B樹

  在1970年,Bayer&McCreight發表的論文《ORGANIZATION AND MAINTENANCE OF LARGE ORDERED INDICES 》(大型有序索引的組織和維護)中提出了一種新的數據結構來維護大型索引,這種數據結構在論文中稱為B-Tree。

  B 樹是一種多路查找樹,相比於二叉樹來說,B 樹更適合於建立存儲設備中的文件索引。

  因為對於存儲設備的操作,除算法的時間復雜度外,查找一個數據所需要進行 I/O 操作的次數也是性能的重要影響因素。

  對於傳統的二叉樹來說,其存儲比較松散,樹的深度較深,我們每次查找一個新節點,可能都需要進行一次 I/O 操作將其讀入內存。

  而 B 樹因為數據存儲比較集中,一個節點內存儲的數據更多,樹的深度較淺,遍歷整個 B 樹所需 I/O 操作的次數遠小於二叉樹,同時因為我們的存儲設備普遍適配了局部性原理,對於一個連續存儲的節點來說,完全讀取它所需 I/O 操作的次數也是非常少的。

  從算法時間復雜度上看,因為 B 樹節點中的項都是有序存儲的,我們在一個節點內尋找數據時,使用二分查找可以使時間復雜度落在 O(log2N) 內,與在二叉樹中的查找相同。因此總的來看,B 樹相比於二叉樹更適用於在存儲設備上維護文件索引。

  另外,B 樹是平衡的,其維護平衡性的思路非常典型。對於普通的二叉樹,是自上向下生長的,而對於B樹,是自下而上生長的。

  我們的插入操作都只會將新項插入到葉子節點,葉子節點滿后進行分裂,並將中間節點向上融合。整顆樹高度的增加必然是由根節點的分裂帶來的,而根節點的分裂不會破壞整顆樹的平衡性,因為左右子樹的高度均加一。

  對於一棵完全的二叉排序樹,其根節點必然是整個有序鏈表的中點。這對於 B 樹也是相通的,每次的分裂和向上融合都是向上傳遞了本節點的中點,所有元素組成的鏈表中,中間部分會集中在 B 樹的上層節點中,這也保證了整棵樹的平衡性。

  我們平時使用的紅黑樹就是 2-3樹(B樹的一種)的一個變種,通過為二叉樹節點染色,模仿 2-3 樹的節點合並/分裂等行為,為其在平衡性和性能上找到了一個妥協點。

B樹的定義

  h:代表樹的高度,k 是個自然數,一個B樹要么是空的,要么滿足以下條件:
  1.所有葉子節點到根節點的路徑長度相同,即具有相同的高度;(樹是平衡的)

  2.每個非葉子和根節點(即內部節點)至少有 k+1 個孩子節點,根至少有 2 個孩子;(這是關鍵的部分,因為節點都是分裂而來的,而每次分裂得到的節點至少有 k 個元素,也就有 k+1 個孩子;但根節點在分裂后可能只有一個元素,因為不需要向上融合,中間元素作為新的根節點,因此最少有兩個孩子。而葉子節點沒有孩子。)

  3.每個節點最多有 2k+1 個孩子節點。(規定了節點的最大容量)

  4.每個節點內的鍵都是遞增的

  我們定義一個樹的結構,首先定義其數據項及節點的數據結構:

 /**
     * @Author Nxy
     * @Date 2020/2/23 14:06
     * @Description 內部類,B樹中節點中的元素。K:鍵類型,V:值類型,可以是指向數據的索引,也可以是實體數據
     */
    private class Entry<K, V> {
        private K key;
        private V value;

        public void setKey(K key) {
            this.key = key;
        }

        public K getKey() {
            return this.key;
        }

        public void setValue(V value) {
            this.value = value;
        }

        public V getValue() {
            return this.value;
        }

        @Override
        public String toString() {
            return "key: " + this.key + " , ";
        }
    }

    /**
     * @Author Nxy
     * @Date 2020/2/23 14:12
     * @Description 內部類,封裝搜索結果
     */
    private class SearchResult<V> {
        private boolean isExist;
        private V value;
        private int index;

        //構造方法,將查詢結果封裝入對象
        public SearchResult(boolean isExist, int index, V value) {
            this.isExist = isExist;
            this.index = index;
            this.value = value;
        }

        public boolean isExist() {
            return isExist;
        }

        public V getValue() {
            return value;
        }

        public int getIndex() {
            return index;
        }
    }

    /**
     * @Author Nxy
     * @Date 2020/2/23 14:28
     * @Description 樹的節點
     */
    public class Node<K, V> {
        //節點內的項
        private List<Entry<K, V>> entrys;
        //節點的孩子節點們
        private List<Node<K, V>> sons;
        //是否是葉子節點
        private boolean isLeaf;
        //鍵值比較函數對象,如果采用倒序或者其它排序方式,傳入該對象
        private Comparator<K> kComparator;

        //比較兩個key,如果沒有傳入自定義排序方式則采用默認的升序
        private int compare(K key1, K key2) {
            return this.kComparator == null ? ((Comparable<K>) key2).compareTo(key1) : kComparator.compare(key1, key2);
        }

        //普通構造函數
        Node() {
            this.entrys = new LinkedList<Entry<K, V>>();
            this.sons = new LinkedList<Node<K, V>>();
            this.isLeaf = false;
        }

        //自定義K排序方式的構造函數
        Node(Comparator<K> kComparator) {
            this();
            this.kComparator = kComparator;
        }

        public void setIsLeaf(boolean isLeaf) {
            this.isLeaf = isLeaf;
        }

        public boolean getIsLeaf() {
            return this.isLeaf;
        }

        //返回本節點的項數
        public int nodeSize() {
            return this.entrys.size();
        }

        /**
         * @Author Nxy
         * @Date 2020/2/23 15:19
         * @Param key:待查找元素的key值
         * @Return 查找結果封裝入 SearchResult
         * @Exception
         * @Description 在本節點內查找元素, 本質就是一個有序數組的二分查找
         */
        public SearchResult<V> search(K key) {
            int begin = 0;
            int end = this.nodeSize() - 1;
//            if (end == 0) {
//                return new SearchResult<V>(false, 0, null);
//            }
            int mid = (begin + end) / 2;
            boolean isExist = false;
            int index = 0;
            V value = null;
            //二分查找
            while (begin < end) {
                mid = (begin + end) / 2;
                Entry midEntry = this.entrys.get(mid);
                int compareRe = compare((K) midEntry.getKey(), key);
                //找到了
                if (compareRe == 0) {
                    break;
                } else {
                    if (compareRe > 0) {
                        //在中點右邊
                        begin = mid + 1;
                    } else {
                        end = mid - 1;
                    }
                }
            }
            //二分查找結束,判斷結果;三個元素以上才是正經二分,只有兩個或一個元素屬於邊界條件要着重考慮
            if (begin < end) {
                //找到了
                isExist = true;
                index = mid;
                value = this.entrys.get(mid).getValue();
            } else if (begin == end) {
                K midKey = this.entrys.get(begin).getKey();
                int comRe = compare(midKey, key);
                if (comRe == 0) {
                    isExist = true;
                    index = begin;
                    value = this.entrys.get(mid).getValue();
                } else if (comRe > 0) {
                    isExist = false;
                    index = begin + 1;
                    value = null;
                } else {
                    isExist = false;
                    index = begin;
                    value = null;
                }
            } else {
                isExist = false;
                index = begin;
                value = null;
            }
            return new SearchResult<V>(isExist, index, value);
        }

        //刪除給定索引位置的項
        public Entry<K, V> removeEntry(int index) {
            Entry<K, V> re = this.entrys.get(index);
            this.entrys.remove(index);
            return re;
        }

        //得到index處的項
        public Entry<K, V> entryAt(int index) {
            return this.entrys.get(index);
        }

        //將新項插入指定位置
        private void insertEntry(Entry<K, V> entry, int index) {
            this.entrys.add(index, entry);
        }

        //節點內插入項
        private boolean insertEntry(Entry<K, V> entry) {
            SearchResult<V> result = search(entry.getKey());
            if (result.isExist()) {
                return false;
            } else {
                insertEntry(entry, result.getIndex());
                return true;
            }
        }

        //更新項,如果項存在,更新其值並返回原值,否則直接插入
        public V putEntry(Entry<K, V> entry) {
            SearchResult<V> re = search(entry.getKey());
            if (re.isExist) {
                Entry oldEntry = this.entrys.get(re.getIndex());
                V oldValue = (V) oldEntry.getValue();
                oldEntry.setValue(entry.getValue());
                return oldValue;
            } else {
                insertEntry(entry);
                return null;
            }
        }

        //獲得指定索引的子節點
        public Node childAt(int index) {
            return this.sons.get(index);
        }

        //刪除給定索引的子節點
        public void removeChild(int index) {
            this.sons.remove(index);
        }

        //將新的子節點插入到指定位置
        public void insertChild(Node<K, V> child, int index) {
            this.sons.add(index, child);
        }
    }

  然后定義樹的數據結構:

public class Btree{  
  //度數T,不傳入則默認為 2-3 樹
    private Integer DEFAULT_T = 2;
    //根節點
    private Node<K, V> root;

    private int t = DEFAULT_T;

    //非根節點的最小項數,體現的是除了根節點,其余節點都是分裂而來的!
    private int nodeMinSize = t - 1;
    //節點的最大項數
    private int nodeMaxSize = 2 * t - 1;
    //比較函數對象
    private Comparator<K> kComparator;
}

B樹的操作

  對於一棵樹常用的操作無非是增刪改查,其余諸如旋轉之類的調整樹結構的操作封裝在增刪改查內部。我們看一下 B 樹增刪改查的邏輯。

  B樹的查找

  查找是 B 樹最簡單的操作:

  1.在節點內進行二分查找,如果找到則返回。未找到返回其在節點內插入時的索引。

  2.根據索引遞歸的查找子節點,直到查找到葉子節點仍未找到則表明數據不在樹中。

  實現如下:

    //在以root為根的樹內搜索key項
    private V search(Node<K, V> root, K key) {
        SearchResult<V> re = root.search(key);
        if (re.isExist) {
            return re.value;
        } else {
            //回歸條件
            if (root.isLeaf) {
                return null;
            }
            int index = re.index;
            //遞歸搜索子節點
            return (V) search(root.childAt(index), key);
        }
    }

    public V search(K key) {
        return search(this.root, key);
    }

  B樹的插入

  然后是B樹的插入操作,插入操作需要考慮節點的分裂與向上融合,整個的過程如下:

  1. 首先判斷根節點是否已滿,滿則先進行分裂得到新的根節點。

  2.從根節點向下尋找,找到待插入數據在葉子節點的位置。

  3. 向下尋找時,每經過一個節點,都檢查插入位置子節點的大小是否為 2*t-1 ,因為我們插入節點可能造成下層節點的向上融合,上層節點如果大小為 2*t-1 ,融合新元素后將會有數據超過最大值的危險。

   尋找路徑上,只要有節點的大小為 2*t-1 ,則先進行分裂,再繼續向下查找。

  4. 直到查找到葉子節點,直接插入。(當然也有其它實現方式,比如每次插入后檢查插入的節點是否需要分裂,沿着搜索路徑回溯着向上融合)

  我們先將分裂和向上融合的放法定義出來:

    /**
     * @Author Nxy
     * @Date 2020/2/23 20:49
     * @Param 分裂滿子結點,fatherNode:待分裂節點的父節點,splitNode:待分裂節點,index:待分裂節點在父節點中的索引
     * @Return
     * @Exception
     * @Description 滿子節點的分裂過程:從中間節點斷開,后半部分形成新結點插入父節點。若分裂節點不是葉子節點,將子節點一並分裂到新節點
     */
    private void splitNode(Node<K, V> fatherNode, Node<K, V> splitNode, int index) {
        //分裂產生的新節點
        Node<K, V> newNode = new Node<K, V>(this.kComparator);
        //如果原節點為葉子節點,那么新節點也是
        newNode.setIsLeaf(splitNode.isLeaf);
        //將 t到2*t-2 項遷移到新節點
        for (int i = t; i < this.nodeMaxSize; i++) {
            newNode.entrys.add(splitNode.entrys.get(i));
        }
        //中間節點向上融合到父節點的 index+1
        Entry<K, V> midEntry = splitNode.entrys.get(t - 1);
        for (int i = this.nodeMaxSize - 1; i >= t - 1; i--) {
            //刪除原節點中已遷移的項,刪除時注意從尾部向前刪除
            splitNode.entrys.remove(i);
        }
        //如果分裂的節點不是葉子節點,子節點一並跟隨分裂
        if (!splitNode.getIsLeaf()) {
            for (int i = t; i < this.nodeMaxSize + 1; i++) {
                newNode.sons.add(splitNode.sons.get(i));
            }
            //刪除時注意從尾部向前刪除
            for (int i = this.nodeMaxSize; i >= t; i--) {
                splitNode.sons.remove(i);
            }
        }
        //父節點插入分裂的中間元素,分裂出的新節點加入父節點的 sons
        fatherNode.insertEntry(midEntry);
        fatherNode.insertChild(newNode, index + 1);
    }

  然后定義插入方法:

/**
     * @Author Nxy
     * @Date 2020/2/23 23:53
     * @Param root:當前節點,entry:待插入元素
     * @Return
     * @Exception
     * @Description 插入一個非滿節點:一路向下尋找插入位置。
     * 在尋找的路徑上,如果碰到大小為2t-1的節點,分裂並向上融合。
     * 每次插入都從葉子節點插入,通過分裂將插入動作向上反饋,直到融合到根節點,只有由根節點的分裂
     * 才能增加整棵樹的高度,從而維持樹的平衡。
     * 樹在一開始就是平衡的(只有根),整棵樹的高度增加必須由根節點的分裂引發,從而高度增加后還是平衡的
     * 因為沒次檢查子節點前如果子節點滿了會先分裂,所以除根節點外,其余節點被其子節點向上融合均不會導致節點滿
     * 僅插入一個元素的情況下,每個節點最多經歷一次子節點的分裂
     */
    private boolean insertNotFull(Node<K, V> root, Entry<K, V> entry) {
        if (root.getIsLeaf()) {
            //到達葉子節點,直接插入
            return root.insertEntry(entry);
        }
        SearchResult<V> re = root.search(entry.getKey());
        if (re.isExist) {
            //已存在key,直接返回
            return false;
        }
        int index = re.getIndex();
        Node<K, V> searchChild = root.childAt(index);
        //待查詢子節點已滿,分裂后再判斷該搜索哪個子節點
        if (searchChild.nodeSize() == 2 * t - 1) {
            splitNode(root, searchChild, index);
            if (root.compare(root.entryAt(index).getKey(), entry.getKey()) > 0) {
                searchChild = root.childAt(index + 1);
            }
        }
        return insertNotFull(searchChild, entry);
    }

    //插入一個新節點
    public boolean insertNode(Entry<K, V> entry) {
        //根節點滿了,先分裂根節點
        if (root.nodeSize() == 2 * t - 1) {
            Node<K, V> newRoot = new Node<K, V>();
            newRoot.setIsLeaf(false);
            newRoot.insertChild(root, 0);
            splitNode(newRoot, root, 0);
            this.root = newRoot;
        }
        return insertNotFull(root, entry);
    }

  B樹的刪除

  刪除比較麻煩,因為為了保證刪除后樹的結構依然符合定義,我們需要考慮非常多的情況。過程如下:

  在本節點內查找刪除項,找到了

    如果本節點葉子節點,直接刪除。

    如果不是葉子節點,則為了保證樹結構,不可直接刪除,繼續判斷:

       1.嘗試與子節點互換元素以維護樹的結構

      (首先是嘗試將該數據項與子節點中的數據項互換)

        如果該數據項的左子節點有大於等於 t 個元素,則將左子節點最后一個元素與刪除元素互換位置。

        左子節點元素個數少於 t ,檢查右子節點,如果右子節點元素數大於等於 t ,將右子節點第一個元素與待刪除元素互換位置。

         2. 嘗試與左右子節點合並

       方案 1 沒起作用,說明待刪除元素的左右子節點元素個數都小於 t ,那么將其與待刪除元素合並為一個大節點,長度將小於等於 2t-1 ,依然符合 B 樹的定義。

       那么我們將待刪除元素合並為左子節點最后一個元素,右子節點攜帶其子元素一並合並入左子節點中。遞歸的對合並后節點進行處理。

    在本節點內未找到待刪除元素,去子節點繼續尋找

     如果本節點為葉子節點,無子節點,直接返回待刪除節點不在樹中。

     否則判斷子節點是否可刪除元素,如果子節點元素個數大於等於 t :

       1.直接遞歸的處理子節點

     如果子節點元素個數小於 t ,不可刪除項:

             2.嘗試讓子節點的左右兄弟節點旋轉來勻出一個元素給子節點

     如果左兄弟可以勻出元素,左兄弟最后一個元素 - 本節點以子節點為右子節點的元素 - 子節點 進行右旋,同時將左兄弟最后一個元素的其右子樹加到子節點兒子列表的開頭。

     如果右兄弟可以勻出元素,右兄弟第一個元素 - 本節點以子節點為左子節點的元素-子節點 進行左旋,同時將右兄弟第一個子元素加到子節點兒子列表的結尾。

       3. 嘗試合並子節點與其左/右兄弟節點

     如果子節點有左兄弟節點,本節點中將以子節點為右子節點的元素 - 子節點合並入子節點的左兄弟,子節點的孩子列表追加到左子元素孩子列表的結尾。

     如果子節點有右兄弟節點,本節點中以子節點為左子節點的元素 - 右兄弟節點合並入子節點,右兄弟節點的孩子列表追加到本節點孩子列表的結尾。

 

       以上便是刪除的邏輯過程,看起來情況比較多,但並不復雜。核心便是在刪除時,為了不破壞樹的結構,我們必須將刪除元素交換到葉子節點后才能刪除。而在交換過程中,不能使刪除路徑上節點的元素數小於 t-1(包括葉子節點)。

       下面是實現:

    private Entry<K, V> delete(Node<K, V> root, Entry<K, V> entry) {
        SearchResult<V> re = root.search(entry.getKey());
        if (re.isExist()) {
            //回歸條件,如果是葉子節點中的元素,直接刪除
            if (root.getIsLeaf()) {
                return root.removeEntry(re.getIndex());
            }
            //如果不是葉子節點,判斷應將待刪除節點交換到左子節點還是右子節點
            Node<K, V> leftChild = root.childAt(re.getIndex());
            //如果左子節點包含多於 t-1 個項,轉移到左子節點刪除
            if (leftChild.nodeSize() >= t) {
                //刪除過程為,將待刪除項與其左子節點最后一項互換,並遞歸互換下去,直到將待刪除節點換到葉子節點后刪除
                root.removeEntry(re.getIndex());
                root.insertEntry(leftChild.entryAt(leftChild.nodeSize() - 1), re.getIndex());
                leftChild.removeEntry(leftChild.nodeSize() - 1);
                leftChild.insertEntry(entry);
                return delete(leftChild, entry);
            }
            //左子節點不可刪除項,則同樣邏輯檢查右子節點
            Node<K, V> rightChild = root.childAt(re.getIndex() + 1);
            if (rightChild.nodeSize() >= t) {
                root.removeEntry(re.getIndex());
                root.insertEntry(rightChild.entryAt(0), re.getIndex());
                rightChild.removeEntry(0);
                rightChild.insertEntry(entry);
                return delete(rightChild, entry);
            }
            //如果左右子節點均不能刪除項,將左右子節點合並,並將刪除項放到新節點的合並連接處
            Entry<K, V> deletedEntry = root.removeEntry(re.getIndex());
            leftChild.insertEntry(deletedEntry);
            root.removeChild(re.getIndex() + 1);
            //左右子節點合並
            for (int i = 0; i < rightChild.nodeSize(); i++) {
                leftChild.insertEntry(rightChild.entryAt(i));
            }
            //右子節點存在子節點,則子節點也合並入左子節點子節點集合
            if (!rightChild.getIsLeaf()) {
                for (int i = 0; i < rightChild.sons.size(); i++) {
                    leftChild.insertChild(rightChild.childAt(i), leftChild.sons.size());
                }
            }
            //合並后繼續向左遞歸
            return delete(leftChild, entry);
        } else {//刪除節點不在本節點
            //回歸條件,搜索到葉節點依然沒找到,待刪除節點不在樹中
            if (root.getIsLeaf()) {
                for (int i = 0; i < root.nodeSize(); i++) {
                    System.out.print("++++++++++++++++++++");
                    System.out.print(root.entryAt(i).getKey() + ",");
                    System.out.print("++++++++++++++++++++");
                }
                throw new RuntimeException(entry.key + " is not in this tree!");
            }
            Node<K, V> searchChild = root.childAt(re.index);
            //子節點可刪除項,遞歸刪除
            if (searchChild.nodeSize() >= t) {
                return delete(searchChild, entry);
            }
            //待旋轉節點,子節點項數小於等於 t-1 ,不能刪除項,准備左旋或右旋為其補充項數
            Node<K, V> siblingNode = null;
            int siblingIndex = -1;
            //存在右兄弟
            if (re.getIndex() < root.nodeSize() - 1) {
                Node<K, V> rightBrother = root.childAt(re.getIndex() + 1);
                if (rightBrother.nodeSize() >= t) {
                    siblingNode = rightBrother;
                    siblingIndex = re.getIndex() + 1;
                }
            }
            //不存在右兄弟則嘗試左兄嘚
            if (siblingNode == null) {
                if (re.getIndex() > 0) {
                    //嘗試左兄弟節點
                    Node<K, V> leftBrothr = root.childAt(re.getIndex() - 1);
                    if (leftBrothr.nodeSize() >= t) {
                        siblingNode = leftBrothr;
                        siblingIndex = re.getIndex() - 1;
                    }
                }
            }
            //至少有一個兄弟可以勻出項來
            if (siblingNode != null) {
                //是左兄嘚
                if (siblingIndex < re.getIndex()) {
                    //左節點最后一項右旋
                    searchChild.insertEntry(root.entryAt(siblingIndex), 0);
                    root.removeEntry(siblingIndex);
                    root.insertEntry(siblingNode.entryAt(siblingNode.nodeSize() - 1), siblingIndex);
                    siblingNode.removeEntry(siblingNode.nodeSize() - 1);
                    //子節點跟着右旋
                    if (!siblingNode.getIsLeaf()) {
                        searchChild.insertChild(siblingNode.childAt(siblingNode.sons.size() - 1), 0);
                        siblingNode.removeChild(siblingNode.sons.size() - 1);
                    }
                } else {
                    //是右兄嘚
                    searchChild.insertEntry(root.entryAt(re.getIndex()), searchChild.nodeSize() - 1);
                    root.removeEntry(re.getIndex());
                    root.insertEntry(siblingNode.entryAt(0), re.getIndex());
                    siblingNode.removeEntry(0);
                    if (!siblingNode.getIsLeaf()) {
                        searchChild.insertChild(siblingNode.childAt(0), searchChild.sons.size());
                        siblingNode.removeChild(0);
                    }
                }
                return delete(searchChild, entry);
            }
            //左右兄嘚都勻不出項來,直接由左右兄嘚節點與父項合並為一個節點
            if (re.getIndex() <= root.nodeSize() - 1) {
                Node<K, V> rightSon = root.childAt(re.getIndex() + 1);
                searchChild.insertEntry(root.entryAt(re.getIndex()), searchChild.nodeSize());
                root.removeEntry(re.getIndex());
                root.removeChild(re.getIndex() + 1);
                for (int i = 0; i < rightSon.nodeSize(); i++) {
                    searchChild.insertEntry(rightSon.entryAt(i));
                }
                if (!rightSon.getIsLeaf()) {
                    for (int j = 0; j < rightSon.sons.size(); j++) {
                        searchChild.insertChild(rightSon.childAt(j), searchChild.sons.size());
                    }
                }
                if (root == this.root) {
                    this.root = searchChild;
                }
            } else {
                //沒有右兄弟,試試左兄嘚
                Node<K, V> leftSon = root.childAt(re.getIndex() - 1);
                searchChild.insertEntry(root.entryAt(re.getIndex() - 1), 0);
                root.removeChild(re.getIndex() - 1);
                root.removeEntry(re.getIndex() - 1);
                for (int i = 0; i < leftSon.nodeSize(); i++) {
                    searchChild.insertEntry(leftSon.entryAt(i));
                }
                if (!leftSon.getIsLeaf()) {
                    for (int i = leftSon.sons.size() - 1; i >= 0; i--) {
                        searchChild.insertChild(leftSon.childAt(i), 0);
                    }
                }
                if (root == this.root) {
                    this.root = searchChild;
                }
            }
//            if (root == this.root && root.nodeSize() == 0) {
//                root = searchChild;
//            }
            return delete(searchChild, entry);
        }
    }

  下面放 B 樹實現的整體代碼:

package BTree;

import java.util.*;

/**
 * @Author Nxy
 * @Date 2020/2/23 14:04
 * @Description B樹實現
 */
public class BTree<K, V> {

    /**
     * @Author Nxy
     * @Date 2020/2/23 14:06
     * @Description 內部類,B樹中節點中的元素。K:鍵類型,V:值類型,可以是指向數據的索引,也可以是實體數據
     */
    private class Entry<K, V> {
        private K key;
        private V value;

        public void setKey(K key) {
            this.key = key;
        }

        public K getKey() {
            return this.key;
        }

        public void setValue(V value) {
            this.value = value;
        }

        public V getValue() {
            return this.value;
        }

        @Override
        public String toString() {
            return "key: " + this.key + " , ";
        }
    }

    /**
     * @Author Nxy
     * @Date 2020/2/23 14:12
     * @Description 內部類,封裝搜索結果
     */
    private class SearchResult<V> {
        private boolean isExist;
        private V value;
        private int index;

        //構造方法,將查詢結果封裝入對象
        public SearchResult(boolean isExist, int index, V value) {
            this.isExist = isExist;
            this.index = index;
            this.value = value;
        }

        public boolean isExist() {
            return isExist;
        }

        public V getValue() {
            return value;
        }

        public int getIndex() {
            return index;
        }
    }

    /**
     * @Author Nxy
     * @Date 2020/2/23 14:28
     * @Description 樹的節點
     */
    public class Node<K, V> {
        //節點內的項
        private List<Entry<K, V>> entrys;
        //節點的孩子節點們
        private List<Node<K, V>> sons;
        //是否是葉子節點
        private boolean isLeaf;
        //鍵值比較函數對象,如果采用倒序或者其它排序方式,傳入該對象
        private Comparator<K> kComparator;

        //比較兩個key,如果沒有傳入自定義排序方式則采用默認的升序
        private int compare(K key1, K key2) {
            return this.kComparator == null ? ((Comparable<K>) key2).compareTo(key1) : kComparator.compare(key1, key2);
        }

        //普通構造函數
        Node() {
            this.entrys = new LinkedList<Entry<K, V>>();
            this.sons = new LinkedList<Node<K, V>>();
            this.isLeaf = false;
        }

        //自定義K排序方式的構造函數
        Node(Comparator<K> kComparator) {
            this();
            this.kComparator = kComparator;
        }

        public void setIsLeaf(boolean isLeaf) {
            this.isLeaf = isLeaf;
        }

        public boolean getIsLeaf() {
            return this.isLeaf;
        }

        //返回本節點的項數
        public int nodeSize() {
            return this.entrys.size();
        }

        /**
         * @Author Nxy
         * @Date 2020/2/23 15:19
         * @Param key:待查找元素的key值
         * @Return 查找結果封裝入 SearchResult
         * @Exception
         * @Description 在本節點內查找元素, 本質就是一個有序數組的二分查找
         */
        public SearchResult<V> search(K key) {
            int begin = 0;
            int end = this.nodeSize() - 1;
//            if (end == 0) {
//                return new SearchResult<V>(false, 0, null);
//            }
            int mid = (begin + end) / 2;
            boolean isExist = false;
            int index = 0;
            V value = null;
            //二分查找
            while (begin < end) {
                mid = (begin + end) / 2;
                Entry midEntry = this.entrys.get(mid);
                int compareRe = compare((K) midEntry.getKey(), key);
                //找到了
                if (compareRe == 0) {
                    break;
                } else {
                    if (compareRe > 0) {
                        //在中點右邊
                        begin = mid + 1;
                    } else {
                        end = mid - 1;
                    }
                }
            }
            //二分查找結束,判斷結果;三個元素以上才是正經二分,只有兩個或一個元素屬於邊界條件要着重考慮
            if (begin < end) {
                //找到了
                isExist = true;
                index = mid;
                value = this.entrys.get(mid).getValue();
            } else if (begin == end) {
                K midKey = this.entrys.get(begin).getKey();
                int comRe = compare(midKey, key);
                if (comRe == 0) {
                    isExist = true;
                    index = begin;
                    value = this.entrys.get(mid).getValue();
                } else if (comRe > 0) {
                    isExist = false;
                    index = begin + 1;
                    value = null;
                } else {
                    isExist = false;
                    index = begin;
                    value = null;
                }
            } else {
                isExist = false;
                index = begin;
                value = null;
            }
            return new SearchResult<V>(isExist, index, value);
        }

        //刪除給定索引位置的項
        public Entry<K, V> removeEntry(int index) {
            Entry<K, V> re = this.entrys.get(index);
            this.entrys.remove(index);
            return re;
        }

        //得到index處的項
        public Entry<K, V> entryAt(int index) {
            return this.entrys.get(index);
        }

        //將新項插入指定位置
        private void insertEntry(Entry<K, V> entry, int index) {
            this.entrys.add(index, entry);
        }

        //節點內插入項
        private boolean insertEntry(Entry<K, V> entry) {
            SearchResult<V> result = search(entry.getKey());
            if (result.isExist()) {
                return false;
            } else {
                insertEntry(entry, result.getIndex());
                return true;
            }
        }

        //更新項,如果項存在,更新其值並返回原值,否則直接插入
        public V putEntry(Entry<K, V> entry) {
            SearchResult<V> re = search(entry.getKey());
            if (re.isExist) {
                Entry oldEntry = this.entrys.get(re.getIndex());
                V oldValue = (V) oldEntry.getValue();
                oldEntry.setValue(entry.getValue());
                return oldValue;
            } else {
                insertEntry(entry);
                return null;
            }
        }

        //獲得指定索引的子節點
        public Node childAt(int index) {
            return this.sons.get(index);
        }

        //刪除給定索引的子節點
        public void removeChild(int index) {
            this.sons.remove(index);
        }

        //將新的子節點插入到指定位置
        public void insertChild(Node<K, V> child, int index) {
            this.sons.add(index, child);
        }
    }

    //度數T,不傳入則默認為 2-3 樹
    private Integer DEFAULT_T = 2;
    //根節點
    private Node<K, V> root;

    private int t = DEFAULT_T;

    //非根節點的最小項數,體現的是除了根節點,其余節點都是分裂而來的!
    private int nodeMinSize = t - 1;
    //節點的最大項數
    private int nodeMaxSize = 2 * t - 1;
    //比較函數對象
    private Comparator<K> kComparator;

    //構造一棵自然排序的B樹
    BTree() {
        Node<K, V> root = new Node<K, V>();
        this.root = root;
        root.setIsLeaf(true);
    }

    //構造一棵度為 t 的B樹
    BTree(int t) {
        this();
        this.t = t;
        nodeMinSize = t - 1;
        nodeMaxSize = 2 * t - 1;
    }

    //構造一棵按給定排序方式排序,且度為 t 的B樹
    BTree(Comparator<K> com, int t) {
        this(t);
        this.kComparator = com;
    }

    //在以root為根的樹內搜索key項
    private V search(Node<K, V> root, K key) {
        SearchResult<V> re = root.search(key);
        if (re.isExist) {
            return re.value;
        } else {
            //回歸條件
            if (root.isLeaf) {
                return null;
            }
            int index = re.index;
            //遞歸搜索子節點
            return (V) search(root.childAt(index), key);
        }
    }

    public V search(K key) {
        return search(this.root, key);
    }

    /**
     * @Author Nxy
     * @Date 2020/2/23 20:49
     * @Param 分裂滿子結點,fatherNode:待分裂節點的父節點,splitNode:待分裂節點,index:待分裂節點在父節點中的索引
     * @Return
     * @Exception
     * @Description 滿子節點的分裂過程:從中間節點斷開,后半部分形成新結點插入父節點。若分裂節點不是葉子節點,將子節點一並分裂到新節點
     */
    private void splitNode(Node<K, V> fatherNode, Node<K, V> splitNode, int index) {
        //分裂產生的新節點
        Node<K, V> newNode = new Node<K, V>(this.kComparator);
        //如果原節點為葉子節點,那么新節點也是
        newNode.setIsLeaf(splitNode.isLeaf);
        //將 t到2*t-2 項遷移到新節點
        for (int i = t; i < this.nodeMaxSize; i++) {
            newNode.entrys.add(splitNode.entrys.get(i));
        }
        //中間節點向上融合到父節點的 index+1
        Entry<K, V> midEntry = splitNode.entrys.get(t - 1);
        for (int i = this.nodeMaxSize - 1; i >= t - 1; i--) {
            //刪除原節點中已遷移的項,刪除時注意從尾部向前刪除
            splitNode.entrys.remove(i);
        }
        //如果分裂的節點不是葉子節點,子節點一並跟隨分裂
        if (!splitNode.getIsLeaf()) {
            for (int i = t; i < this.nodeMaxSize + 1; i++) {
                newNode.sons.add(splitNode.sons.get(i));
            }
            //刪除時注意從尾部向前刪除
            for (int i = this.nodeMaxSize; i >= t; i--) {
                splitNode.sons.remove(i);
            }
        }
        //父節點插入分裂的中間元素,分裂出的新節點加入父節點的 sons
        fatherNode.insertEntry(midEntry);
        fatherNode.insertChild(newNode, index + 1);
    }

    /**
     * @Author Nxy
     * @Date 2020/2/23 23:53
     * @Param root:當前節點,entry:待插入元素
     * @Return
     * @Exception
     * @Description 插入一個非滿節點:一路向下尋找插入位置。
     * 在尋找的路徑上,如果碰到大小為2t-1的節點,分裂並向上融合。
     * 每次插入都從葉子節點插入,通過分裂將插入動作向上反饋,直到融合到根節點,只有由根節點的分裂
     * 才能增加整棵樹的高度,從而維持樹的平衡。
     * 樹在一開始就是平衡的(只有根),整棵樹的高度增加必須由根節點的分裂引發,從而高度增加后還是平衡的
     * 因為沒次檢查子節點前如果子節點滿了會先分裂,所以除根節點外,其余節點被其子節點向上融合均不會導致節點滿
     * 僅插入一個元素的情況下,每個節點最多經歷一次子節點的分裂
     */
    private boolean insertNotFull(Node<K, V> root, Entry<K, V> entry) {
        if (root.getIsLeaf()) {
            //到達葉子節點,直接插入
            return root.insertEntry(entry);
        }
        SearchResult<V> re = root.search(entry.getKey());
        if (re.isExist) {
            //已存在key,直接返回
            return false;
        }
        int index = re.getIndex();
        Node<K, V> searchChild = root.childAt(index);
        //待查詢子節點已滿,分裂后再判斷該搜索哪個子節點
        if (searchChild.nodeSize() == 2 * t - 1) {
            splitNode(root, searchChild, index);
            if (root.compare(root.entryAt(index).getKey(), entry.getKey()) > 0) {
                searchChild = root.childAt(index + 1);
            }
        }
        return insertNotFull(searchChild, entry);
    }

    //插入一個新節點
    public boolean insertNode(Entry<K, V> entry) {
        //根節點滿了,先分裂根節點
        if (root.nodeSize() == 2 * t - 1) {
            Node<K, V> newRoot = new Node<K, V>();
            newRoot.setIsLeaf(false);
            newRoot.insertChild(root, 0);
            splitNode(newRoot, root, 0);
            this.root = newRoot;
        }
        return insertNotFull(root, entry);
    }

    /**
     * @Author Nxy
     * @Date 2020/2/24 14:00
     * @Param
     * @Return
     * @Exception
     * @Description 如果Key已存在,更新value,否則直接插入entry
     */
    private V putNotFull(Node<K, V> root, Entry<K, V> entry) {
        assert root.nodeSize() < nodeMaxSize;
        if (root.isLeaf) {
            return root.putEntry(entry);
        }
        SearchResult<V> re = root.search(entry.getKey());
        if (re.isExist) {
            //如果存在,則更新
            root.entryAt(re.index).setValue(entry.getValue());
            return re.value;
        }
        //如果不存在,繼續向下搜素,先判斷子節點是否需要分裂
        Node<K, V> searchChild = root.childAt(re.index);
        if (searchChild.nodeSize() == 2 * t - 1) {
            splitNode(root, searchChild, re.index);
            if (root.compare(entry.getKey(), root.entryAt(re.index).getKey()) > 0) {
                searchChild = root.childAt(re.index + 1);
            }
        }
        return putNotFull(searchChild, entry);
    }

    // 如果樹中已存在 key 則更新並返回原 value,否則插入並返回null
    public V put(Entry<K, V> entry) {
        //如果根節點已滿,先分裂根節點
        if (this.root.nodeSize() == nodeMaxSize) {
            Node<K, V> newRoot = new Node<K, V>(kComparator);
            newRoot.setIsLeaf(false);
            newRoot.insertChild(root, 0);
            splitNode(newRoot, root, 0);
            this.root = newRoot;
        }
        return putNotFull(root, entry);
    }

    private Entry<K, V> delete(Node<K, V> root, Entry<K, V> entry) {
        SearchResult<V> re = root.search(entry.getKey());
        if (re.isExist()) {
            //回歸條件,如果是葉子節點中的元素,直接刪除
            if (root.getIsLeaf()) {
                return root.removeEntry(re.getIndex());
            }
            //如果不是葉子節點,判斷應將待刪除節點交換到左子節點還是右子節點
            Node<K, V> leftChild = root.childAt(re.getIndex());
            //如果左子節點包含多於 t-1 個項,轉移到左子節點刪除
            if (leftChild.nodeSize() >= t) {
                //刪除過程為,將待刪除項與其左子節點最后一項互換,並遞歸互換下去,直到將待刪除節點換到葉子節點后刪除
                root.removeEntry(re.getIndex());
                root.insertEntry(leftChild.entryAt(leftChild.nodeSize() - 1), re.getIndex());
                leftChild.removeEntry(leftChild.nodeSize() - 1);
                leftChild.insertEntry(entry);
                return delete(leftChild, entry);
            }
            //左子節點不可刪除項,則同樣邏輯檢查右子節點
            Node<K, V> rightChild = root.childAt(re.getIndex() + 1);
            if (rightChild.nodeSize() >= t) {
                root.removeEntry(re.getIndex());
                root.insertEntry(rightChild.entryAt(0), re.getIndex());
                rightChild.removeEntry(0);
                rightChild.insertEntry(entry);
                return delete(rightChild, entry);
            }
            //如果左右子節點均不能刪除項,將左右子節點合並,並將刪除項放到新節點的合並連接處
            Entry<K, V> deletedEntry = root.removeEntry(re.getIndex());
            leftChild.insertEntry(deletedEntry);
            root.removeChild(re.getIndex() + 1);
            //左右子節點合並
            for (int i = 0; i < rightChild.nodeSize(); i++) {
                leftChild.insertEntry(rightChild.entryAt(i));
            }
            //右子節點存在子節點,則子節點也合並入左子節點子節點集合
            if (!rightChild.getIsLeaf()) {
                for (int i = 0; i < rightChild.sons.size(); i++) {
                    leftChild.insertChild(rightChild.childAt(i), leftChild.sons.size());
                }
            }
            //合並后繼續向左遞歸
            return delete(leftChild, entry);
        } else {//刪除節點不在本節點
            //回歸條件,搜索到葉節點依然沒找到,待刪除節點不在樹中
            if (root.getIsLeaf()) {
                for (int i = 0; i < root.nodeSize(); i++) {
                    System.out.print("++++++++++++++++++++");
                    System.out.print(root.entryAt(i).getKey() + ",");
                    System.out.print("++++++++++++++++++++");
                }
                throw new RuntimeException(entry.key + " is not in this tree!");
            }
            Node<K, V> searchChild = root.childAt(re.index);
            //子節點可刪除項,遞歸刪除
            if (searchChild.nodeSize() >= t) {
                return delete(searchChild, entry);
            }
            //待旋轉節點,子節點項數小於等於 t-1 ,不能刪除項,准備左旋或右旋為其補充項數
            Node<K, V> siblingNode = null;
            int siblingIndex = -1;
            //存在右兄弟
            if (re.getIndex() < root.nodeSize() - 1) {
                Node<K, V> rightBrother = root.childAt(re.getIndex() + 1);
                if (rightBrother.nodeSize() >= t) {
                    siblingNode = rightBrother;
                    siblingIndex = re.getIndex() + 1;
                }
            }
            //不存在右兄弟則嘗試左兄嘚
            if (siblingNode == null) {
                if (re.getIndex() > 0) {
                    //嘗試左兄弟節點
                    Node<K, V> leftBrothr = root.childAt(re.getIndex() - 1);
                    if (leftBrothr.nodeSize() >= t) {
                        siblingNode = leftBrothr;
                        siblingIndex = re.getIndex() - 1;
                    }
                }
            }
            //至少有一個兄弟可以勻出項來
            if (siblingNode != null) {
                //是左兄嘚
                if (siblingIndex < re.getIndex()) {
                    //左節點最后一項右旋
                    searchChild.insertEntry(root.entryAt(siblingIndex), 0);
                    root.removeEntry(siblingIndex);
                    root.insertEntry(siblingNode.entryAt(siblingNode.nodeSize() - 1), siblingIndex);
                    siblingNode.removeEntry(siblingNode.nodeSize() - 1);
                    //子節點跟着右旋
                    if (!siblingNode.getIsLeaf()) {
                        searchChild.insertChild(siblingNode.childAt(siblingNode.sons.size() - 1), 0);
                        siblingNode.removeChild(siblingNode.sons.size() - 1);
                    }
                } else {
                    //是右兄嘚
                    searchChild.insertEntry(root.entryAt(re.getIndex()), searchChild.nodeSize() - 1);
                    root.removeEntry(re.getIndex());
                    root.insertEntry(siblingNode.entryAt(0), re.getIndex());
                    siblingNode.removeEntry(0);
                    if (!siblingNode.getIsLeaf()) {
                        searchChild.insertChild(siblingNode.childAt(0), searchChild.sons.size());
                        siblingNode.removeChild(0);
                    }
                }
                return delete(searchChild, entry);
            }
            //左右兄嘚都勻不出項來,直接由左右兄嘚節點與父項合並為一個節點
            if (re.getIndex() <= root.nodeSize() - 1) {
                Node<K, V> rightSon = root.childAt(re.getIndex() + 1);
                searchChild.insertEntry(root.entryAt(re.getIndex()), searchChild.nodeSize());
                root.removeEntry(re.getIndex());
                root.removeChild(re.getIndex() + 1);
                for (int i = 0; i < rightSon.nodeSize(); i++) {
                    searchChild.insertEntry(rightSon.entryAt(i));
                }
                if (!rightSon.getIsLeaf()) {
                    for (int j = 0; j < rightSon.sons.size(); j++) {
                        searchChild.insertChild(rightSon.childAt(j), searchChild.sons.size());
                    }
                }
                if (root == this.root) {
                    this.root = searchChild;
                }
            } else {
                //沒有右兄弟,試試左兄嘚
                Node<K, V> leftSon = root.childAt(re.getIndex() - 1);
                searchChild.insertEntry(root.entryAt(re.getIndex() - 1), 0);
                root.removeChild(re.getIndex() - 1);
                root.removeEntry(re.getIndex() - 1);
                for (int i = 0; i < leftSon.nodeSize(); i++) {
                    searchChild.insertEntry(leftSon.entryAt(i));
                }
                if (!leftSon.getIsLeaf()) {
                    for (int i = leftSon.sons.size() - 1; i >= 0; i--) {
                        searchChild.insertChild(leftSon.childAt(i), 0);
                    }
                }
                if (root == this.root) {
                    this.root = searchChild;
                }
            }
//            if (root == this.root && root.nodeSize() == 0) {
//                root = searchChild;
//            }
            return delete(searchChild, entry);
        }
    }

    public Entry<K, V> delete(K key) {
        Entry<K, V> en = new Entry<K, V>();
        en.setKey(key);
        return delete(root, en);
    }

    /**
     * @Author Nxy
     * @Date 2020/2/25 14:18
     * @Description 借助隊列打印B樹
     */
    public void output() {
        Queue<Node<K, V>> queue = new LinkedList<Node<K, V>>();
        queue.offer(this.root);
        while (!queue.isEmpty()) {
            Node<K, V> node = queue.poll();
            for (int i = 0; i < node.nodeSize(); ++i) {
                System.out.print(node.entryAt(i) + " ");
            }
            System.out.println();
            if (!node.getIsLeaf()) {
                for (int i = 0; i <= node.sons.size() - 1; ++i) {
                    queue.offer(node.childAt(i));
                }
            }
        }
    }

    public static void main(String[] args) {
        Random random = new Random();
        BTree<Integer, Integer> btree = new BTree<Integer, Integer>(3);
        List<Integer> save = new ArrayList<Integer>(30);
//        save.add(8290);
//        save.add(7887);
//        save.add(9460);
//        save.add(9928);
//        save.add(6127);
//        save.add(5891);
//        save.add(1592);
//        save.add(14);
//        save.add(8681);
//        save.add(4843);
//        save.add(1051);

        for (int i = 0; i < 20; ++i) {
            int r = random.nextInt(10000);
            save.add(r);
            System.out.print(r + "  ");
            BTree.Entry en = btree.new Entry<Integer, Integer>();
            en.setKey(r);
            en.setValue(r);
//            BTree.Entry en = btree.new Entry<Integer, Integer>();
//            en.setKey(save.get(i));
            btree.insertNode(en);
        }

        System.out.println("----------------------");
        btree.output();
        System.out.println("----------------------");
        btree.delete(save.get(0));
        btree.output();
    }

}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM