二叉樹、紅黑樹、B樹、B+樹 合集-----媽媽再也不用擔心我的“樹”拉!

這裏把各種樹做個總結,分別介紹各個樹是什麼,什麼原理,什麼特點,什麼情況下使用,另外很多時候它們很多地方是相似的,還要加以區別,之前我身邊一個很多年開發的經驗的老開發還以爲B樹、B-樹、B+樹是三種樹,實際沒有B-樹,它實際就是B樹,要是不區分清楚鬧出這樣的笑話就尷尬了。或者別人說“平衡樹”、“滿二叉樹”、“3階樹”等概念時你一臉懵逼,想吹牛逼但是沒詞兒,那也挺尷尬,怎麼辦,一點一點學吧,下面一 一介紹。

一、樹的基本術語

若一個結點有子樹,那麼該結點稱爲子樹根的"雙親",子樹的根是該結點的"孩子"。有相同雙親的結點互爲"兄弟"。一個結點的所有子樹上的任何結點都是該結點的後裔。從根結點到某個結點的路徑上的所有結點都是該結點的祖先。

結點的度:結點擁有的子樹的數目。
葉子:度爲零的結點(無子樹的結點)。
分支結點:度不爲零的結點。
樹的度:樹中結點的最大的度(下圖中樹的度即爲3)。

層次:根結點的層次爲1,其餘結點的層次等於該結點的雙親結點的層次加1。
樹的高度(樹的深度):樹中結點的最大層次。
無序樹:如果樹中結點的各子樹之間的次序是不重要的,可以交換位置。
有序樹:如果樹中結點的各子樹之間的次序是重要的, 不可以交換位置。
森林:0個或多個不相交的樹組成。對森林加上一個根,森林即成爲樹;刪去根,樹即成爲森林。

結點的度
樹的分層

 

二、二叉樹

 2.1 定義

二叉樹又叫二叉排序樹(Binary Sort Tree),“二叉”就是樹上的一根樹枝開兩個叉,而這棵樹上的節點是已經排好序的,具體的排序規則如下:

  1. 若左子樹不空,則左子樹上所有節點的值均小於它的根節點的值
  2. 若右子樹不空,則右字數上所有節點的值均大於它的根節點的值
  3. 它的左、右子樹也分別爲二叉排序數(遞歸定義)

                                 

上面的排序規則可以看出二叉樹的特點,如果我們要查找某個元素,它可以使我們具有和二分法等同的效率,每經過一個節點就可以減少一半的可能,可以使我們的查詢效率大幅提高。但是也會有比較極端的情況,那就是所有節點都位於同一側,直觀上看就是一條直線(這種樹也叫斜樹,如上右圖),這時查詢效率就和原來的順序查找一樣了,效率很低,於是就有了“平衡二叉樹”。

這裏“平衡”要重點解釋一下,說的是這棵樹的各個分支的高度是均勻的,它的左子樹和右子樹的高度之差絕對值小於1,這樣就不會出現一條支路特別長的情況。於是,在這樣的平衡樹中進行查找時,總共比較節點的次數不超過樹的高度,這就確保了查詢的效率(時間複雜度爲O(logn))。

     二叉樹的應用:

  1. 哈夫曼編碼,來源於哈夫曼樹(給定n個權值作爲n個葉子結點,構造一棵二叉樹,若帶權路徑長度達到最小,稱這樣的二叉樹爲最優二叉樹,也稱爲赫夫曼樹(Huffman tree)。即帶權路徑長度最短的樹),在數據壓縮上有重要應用,提高了傳輸的有效性,詳見《信息論與編碼》。
  2. 海量數據併發查詢,二叉樹複雜度是O(K+LgN)。二叉排序樹就既有鏈表的好處,也有數組的好處, 在處理大批量的動態的數據是比較有用。

 2.2 滿二叉樹

這裏還有幾個常見的概念:

滿二叉樹”:在一棵二叉樹中如果所有分支結點都存在左子樹和右子樹,且所有葉子都在同一層上,這樣的二叉樹稱爲滿二叉樹。

滿二叉樹的特點有:

  1. 葉子只能出現在最下一層。出現在其它層就不可能達成平衡。
  2. 非葉子結點的度一定是2。
  3. 在同樣深度的二叉樹中,滿二叉樹的結點個數最多,葉子數最多。
fef
滿二叉樹

  2.3 完全二叉樹

完全二叉樹”:一棵二叉樹中,只有最下面兩層結點的度可以小於2,並且最下一層的葉結點集中在靠左的若干位置上。這樣的二叉樹稱爲完全二叉樹。

e
完全二叉樹

葉子結點只能出現在最下層和次下層,且最下層的葉子結點集中在樹的左部。顯然,一棵滿二叉樹必定是一棵完全二叉樹,而完全二叉樹未必是滿二叉樹

 2.4 二叉樹的存儲

二叉樹有兩種存儲方式:順序存儲和鏈式存儲

 2.4.1 順序存儲

二叉樹的順序存儲結構就是使用一維數組存儲二叉樹中的結點,並且結點的存儲位置,就是數組的下標索引。

上圖所示的一棵完全二叉樹採用順序存儲方式,可以這樣表示:

同理,看下下面的右斜樹: 

順序表示爲:

其中,∧表示數組中此位置沒有存儲結點。此時可以發現,順序存儲結構中會有空間浪費的情況。因此順序存儲結構一般適用於完全二叉樹。

 2.4.2 鏈式存儲

既然順序存儲不能滿足二叉樹的存儲需求,那麼考慮採用鏈式存儲。節點表示成:

樹可以表示爲:

二叉鏈表結構靈活,操作方便,對於一般情況的二叉樹,甚至比順序存儲結構還節省空間。因此,二叉鏈表是最常用的二叉樹存儲方式。

 2.5 二叉樹的遍歷

二叉樹有三種遍歷方式:前序/中序/後續遍歷

 2.5.1 前(先)序遍歷

所謂的先序遍歷就是先訪問根節點,再訪問左節點,最後訪問右節點。若二叉樹爲空,則退出。

上面的完全二叉樹的前序遍歷順序就是:A、B、D、H、I、E、J、C、F、G

 2.5.2 中序遍歷

所謂的中序遍歷就是先訪問左節點,再訪問根節點,最後訪問右節點。若二叉樹爲空,則退出。

同樣的完全二叉樹的中序遍歷順序就是:H、D、I、B、J、E、A、F、C、G

 2.5.3 後序遍歷 

所謂的後序遍歷就是先訪問左節點,再訪問右節點,最後訪問根節點。若二叉樹爲空,則退出。

同樣的完全二叉樹的中序遍歷順序就是:H、I、D、J、E、B、F、G、C、A

 2.6 代碼實例

上面概念、原理說了一大通了,該是動手用代碼實現一下的時候了,Talk is cheap, show me the code。   

package tree.binary;

import java.util.LinkedList;
import java.util.List;

/**
 * @Author: GeFeng
 * @Date: 2020年6月10日10:10:44
 * @Description: 二叉樹節點
 */
public class BinaryTreeDemo {
    public static void main(String[] args) {
        BinaryTree bt = new BinaryTree();
        bt.addNode(6);
        bt.addNode(4);
        bt.addNode(8);
        bt.addNode(1);
        bt.addNode(11);
        bt.addNode(2);
        bt.addNode(7);
        System.out.println("【前序:】");
        preOrder(bt.root);
        System.out.println("【中序:】");
        midOrder(bt.root);
        System.out.println("【後序:】");
        posOrder(bt.root);
    }

    /**
     * 新建二叉樹
     */
    public static class BinaryTree {
        BinaryTreeNode root;

        public void addNode(int value) {
            root = addNode(root, value);
        }

        private BinaryTreeNode addNode(BinaryTreeNode current, int value) {
            if (current == null) {
                return new BinaryTreeNode(value);
            }
            if (value < current.data) {
                current.leftChild = addNode(current.leftChild, value);
            } else if (value > current.data) {
                current.rightChild = addNode(current.rightChild, value);
            } else {
                return current;
            }
            return current;
        }
    }

    /**
     * 前序遍歷 根-> 左-> 右
     * 遞歸
     */
    public static void preOrder(BinaryTreeNode Node)
    {
        if (Node != null)
        {
            System.out.print(Node.getData() + " ");
            preOrder(Node.getLeftChild());
            preOrder(Node.getRightChild());
        }
    }

    /**
     * 中序遍歷  左-> 根-> 右
     * 遞歸
     */
    public static void midOrder(BinaryTreeNode Node)
    {
        if (Node != null)
        {
            midOrder(Node.getLeftChild());
            System.out.print(Node.getData() + " ");
            midOrder(Node.getRightChild());
        }
    }

    /**
     * 後序遍歷 左-> 右-> 根
     * 遞歸
     */
    public static void posOrder(BinaryTreeNode Node)
    {
        if (Node != null)
        {
            posOrder(Node.getLeftChild());
            posOrder(Node.getRightChild());
            System.out.print(Node.getData() + " ");
        }
    }
}

 

結果: 

 

【前序: 根-> 左-> 右】
6 4 1 2 8 7 11 
【中序: 左-> 根-> 右】
1 2 4 6 7 8 11 
【後序: 左-> 右-> 根】
2 1 4 7 11 8 6 

 

 2.7 平衡二叉樹

爲什麼最後一小節來介紹平衡二叉樹呢?一是平衡二叉樹是前面普通二叉樹的升級版,放在最後做個昇華;二是下面要介紹的紅黑樹和平衡二叉樹有很多相似的地方,因此在這裏介紹下,希望能承上啓下。平衡二叉樹是啥樣的?見下圖:

     可見平衡二叉樹的特點:

  1. 從任何一個節點出發,左右子樹深度之差的絕對值不超過1。
  2. 左右子樹仍然爲平衡二叉樹。

前面我們介紹了“左斜樹”、“右斜樹”,會造成查詢效率低下的問題,一顆二叉查找樹的優勢完全喪失了。怎麼辦呢?既然上面的二叉查找樹在插入的時候變成了“一條腿”,也就是喪失了平衡,那我們乾脆做出一點改進,讓它平衡一下。比如上面的平衡二叉樹中我們要再插入一個4,按照普通的二叉樹規則會出現下面的情況:

若按照平衡二叉樹的要求,則會調整數的結構,使得整體滿足平衡二叉樹的規則:

平衡二叉樹是高度平衡的,優勢就是能夠保持高效的查詢效率;但是在插入和刪除節點時因爲要動態維護平衡,也會影響性能。

 

三、紅黑樹 —— RBTree

3.1 定義

上面說了平衡二叉樹大量插入和刪除節點的場景下,平衡二叉樹爲了保持平衡需要調整的頻率會更高,性能會受到影響,這時紅黑樹成了首選。

紅黑樹其實就是一種數據結構,設計它的目的就是爲了高效地進行增刪改查,紅黑樹放棄了追求完全平衡,追求大致平衡,在與平衡二叉樹的時間複雜度相差不大的情況下,保證每次插入最多隻需要三次旋轉就能達到平衡,實現起來也更爲簡單,而平衡二叉樹追求絕對平衡,條件比較苛刻,實現起來比較麻煩,每次插入新節點之後需要旋轉的次數不能預知。

所以在大量查找的情況下,平衡二叉樹的效率更高,也是首要選擇。在大量增刪的情況下,紅黑樹是首選。

那到底啥是紅黑樹?看下圖:

特性:

  1. 每個節點只有兩種顏色:紅色和黑色;
  2. 根節點是黑色的;
  3. 從根節點到葉子節點,不會出現兩個連續的紅色節點;
  4. 葉子節點都爲黑色,且爲 null;
  5. 從任何一個節點出發,到葉子節點,這條路徑上都有相同數目的黑色節點。

因此不能就直接說紅黑樹不追求平衡,紅黑樹和平衡二叉樹(AVL樹)都是二叉查找樹的變體,但紅黑樹的統計性能要好於AVL樹。因爲,AVL樹是嚴格維持平衡的,紅黑樹是黑平衡的。維持平衡需要額外的操作,這就加大了數據結構的時間複雜度,所以紅黑樹可以看作是二叉搜索樹和AVL樹的一個折中,維持平衡的同時也不需要花太多時間維護數據結構的性質。

3.2 紅黑樹中的操作

紅黑樹的基本操作和其他樹形結構一樣,一般都包括查找、插入、刪除等操作,不同的是因爲要符合紅黑樹規則而多了旋轉操作。旋轉操作有分爲左旋和右旋。

3.2.1 左旋

(盜的動態圖,完美!)

               

3.2.2 右旋

(還是盜的動態圖,完美!)

            

3.2.3 插入

紅黑樹的插入過程和二叉查找樹插入過程基本類似,不同的地方在於,紅黑樹插入新節點後,需要進行調整,以滿足紅黑樹的性質。

3.2.4 刪除

紅黑樹的插入過程和二叉查找樹插入過程基本類似,不同的地方在於,紅黑樹插入新節點後,需要進行調整,以滿足紅黑樹的性質。相較於插入操作,紅黑樹的刪除操作則要更爲複雜一些。刪除操作首先要確定待刪除節點有幾個孩子,如果有兩個孩子,不能直接刪除該節點。而是要先找到該節點的前驅(該節點左子樹中最大的節點)或者後繼(該節點右子樹中最小的節點),然後將前驅或者後繼的值複製到要刪除的節點中,最後再將前驅或後繼刪除。

3.3 紅黑樹的應用

  • linux進程調度Completely Fair Scheduler,用紅黑樹管理進程控制塊
  • 廣泛用在C++的STL中,map和set都是用紅黑樹實現的
  • epoll在內核中的實現,用紅黑樹管理事件塊
  • nginx中,用紅黑樹管理timer等
  • Java的TreeMap、HashMap實現

上面可能比較陌生,不容易接觸到,可以重點看下JDK裏怎麼應用的紅黑樹,這裏重點介紹了HashMap中對紅黑樹的應用及實現:https://blog.csdn.net/weixin_41231928/article/details/103413167

 3.4 紅黑樹代碼實現

@Data
public class RBTreeNode {
    private final boolean RED = false;
    private final boolean BLACK = true;
    private int key;
    private boolean color;
    private RBTreeNode left;
    private RBTreeNode right;
    private RBTreeNode parent;
}

 

@Data
public class RBTree {
    RBTreeNode root;
    private final boolean RED = false;
    private final boolean BLACK = true;

    public RBTreeNode query(int key) {
        RBTreeNode tmp = root;
        while (tmp != null) {
            if (tmp.getKey() == key)
                return tmp;
            else if (tmp.getKey() > key)
                tmp = tmp.getLeft();
            else
                tmp = tmp.getRight();
        }
        return null;
    }

    public void insert(int key) {
        RBTreeNode node = new RBTreeNode(key);
        if (root == null) {
            root = node;
            node.setColor(BLACK);
            return;
        }
        RBTreeNode parent = root;
        RBTreeNode son = null;
        if (key <= parent.getKey()) {
            son = parent.getLeft();
        } else {
            son = parent.getRight();
        }
        //find the position
        while (son != null) {
            parent = son;
            if (key <= parent.getKey()) {
                son = parent.getLeft();
            } else {
                son = parent.getRight();
            }
        }
        if (key <= parent.getKey()) {
            parent.setLeft(node);
        } else {
            parent.setRight(node);
        }
        node.setParent(parent);

        //fix up
        insertFix(node);
    }

    private void insertFix(RBTreeNode node) {
        RBTreeNode father, grandFather;
        while ((father = node.getParent()) != null && father.getColor() == RED) {
            grandFather = father.getParent();
            if (grandFather.getLeft() == father) {  //F爲G左兒子的情況,如之前的分析
                RBTreeNode uncle = grandFather.getRight();
                if (uncle != null && uncle.getColor() == RED) {
                    setBlack(father);
                    setBlack(uncle);
                    setRed(grandFather);
                    node = grandFather;
                    continue;
                }
                if (node == father.getRight()) {
                    leftRotate(father);
                    RBTreeNode tmp = node;
                    node = father;
                    father = tmp;
                }
                setBlack(father);
                setRed(grandFather);
                rightRotate(grandFather);
            } else {                               //F爲G的右兒子的情況,對稱操作
                RBTreeNode uncle = grandFather.getLeft();
                if (uncle != null && uncle.getColor() == RED) {
                    setBlack(father);
                    setBlack(uncle);
                    setRed(grandFather);
                    node = grandFather;
                    continue;
                }
                if (node == father.getLeft()) {
                    rightRotate(father);
                    RBTreeNode tmp = node;
                    node = father;
                    father = tmp;
                }
                setBlack(father);
                setRed(grandFather);
                leftRotate(grandFather);
            }
        }
        setBlack(root);
    }

    public void delete(int key) {
        delete(query(key));
    }

    private void delete(RBTreeNode node) {
        if (node == null)
            return;
        if (node.getLeft() != null && node.getRight() != null) {
            RBTreeNode replaceNode = node;
            RBTreeNode tmp = node.getRight();
            while (tmp != null) {
                replaceNode = tmp;
                tmp = tmp.getLeft();
            }
            int t = replaceNode.getKey();
            replaceNode.setKey(node.getKey());
            node.setKey(t);
            delete(replaceNode);
            return;
        }
        RBTreeNode replaceNode = null;
        if (node.getLeft() != null)
            replaceNode = node.getLeft();
        else
            replaceNode = node.getRight();

        RBTreeNode parent = node.getParent();
        if (parent == null) {
            root = replaceNode;
            if (replaceNode != null)
                replaceNode.setParent(null);
        } else {
            if (replaceNode != null)
                replaceNode.setParent(parent);
            if (parent.getLeft() == node)
                parent.setLeft(replaceNode);
            else {
                parent.setRight(replaceNode);
            }
        }
        if (node.getColor() == BLACK)
            removeFix(parent, replaceNode);

    }

    //多餘的顏色在node裏
    private void removeFix(RBTreeNode father, RBTreeNode node) {
        while ((node == null || node.getColor() == BLACK) && node != root) {
            if (father.getLeft() == node) {  //S爲P的左兒子的情況,如之前的分析
                RBTreeNode brother = father.getRight();
                if (brother != null && brother.getColor() == RED) {
                    setRed(father);
                    setBlack(brother);
                    leftRotate(father);
                    brother = father.getRight();
                }
                if (brother == null || (isBlack(brother.getLeft()) && isBlack(brother.getRight()))) {
                    setRed(brother);
                    node = father;
                    father = node.getParent();
                    continue;
                }
                if (isRed(brother.getLeft())) {
                    setBlack(brother.getLeft());
                    setRed(brother);
                    rightRotate(brother);
                    brother = brother.getParent();
                }

                brother.setColor(father.getColor());
                setBlack(father);
                setBlack(brother.getRight());
                leftRotate(father);
                node = root;//跳出循環
            } else {                         //S爲P的右兒子的情況,對稱操作
                RBTreeNode brother = father.getLeft();
                if (brother != null && brother.getColor() == RED) {
                    setRed(father);
                    setBlack(brother);
                    rightRotate(father);
                    brother = father.getLeft();
                }
                if (brother == null || (isBlack(brother.getLeft()) && isBlack(brother.getRight()))) {
                    setRed(brother);
                    node = father;
                    father = node.getParent();
                    continue;
                }
                if (isRed(brother.getRight())) {
                    setBlack(brother.getRight());
                    setRed(brother);
                    leftRotate(brother);
                    brother = brother.getParent();
                }

                brother.setColor(father.getColor());
                setBlack(father);
                setBlack(brother.getLeft());
                rightRotate(father);
                node = root;//跳出循環
            }
        }

        if (node != null)
            node.setColor(BLACK);
    }

    private boolean isBlack(RBTreeNode node) {
        if (node == null)
            return true;
        return node.getColor() == BLACK;
    }

    private boolean isRed(RBTreeNode node) {
        if (node == null)
            return false;
        return node.getColor() == RED;
    }

    private void leftRotate(RBTreeNode node) {
        RBTreeNode right = node.getRight();
        RBTreeNode parent = node.getParent();
        if (parent == null) {
            root = right;
            right.setParent(null);
        } else {
            if (parent.getLeft() != null && parent.getLeft() == node) {
                parent.setLeft(right);
            } else {
                parent.setRight(right);
            }
            right.setParent(parent);
        }
        node.setParent(right);
        node.setRight(right.getLeft());
        if (right.getLeft() != null) {
            right.getLeft().setParent(node);
        }
        right.setLeft(node);
    }

    private void rightRotate(RBTreeNode node) {
        RBTreeNode left = node.getLeft();
        RBTreeNode parent = node.getParent();
        if (parent == null) {
            root = left;
            left.setParent(null);
        } else {
            if (parent.getLeft() != null && parent.getLeft() == node) {
                parent.setLeft(left);
            } else {
                parent.setRight(left);
            }
            left.setParent(parent);
        }
        node.setParent(left);
        node.setLeft(left.getRight());
        if (left.getRight() != null) {
            left.getRight().setParent(node);
        }
        left.setRight(node);
    }

    private void setBlack(RBTreeNode node) {
        node.setColor(BLACK);
    }

    private void setRed(RBTreeNode node) {
        node.setColor(RED);
    }
    
    private void inOrder(RBTreeNode node) {
        if (node == null)
            return;
        inOrder(node.getLeft());
        System.out.println(node);
        inOrder(node.getRight());
    }
}

 

四、B樹

首先就要說明白,“B樹”和“B-樹”是一個哈,B-樹不是一種新的樹,不要多想。

4.1 爲什麼要有B樹?

學習前首先問下爲啥需要B樹?已經有紅黑樹、二叉樹等一堆多樹了,又來個B樹幹啥?可以從下面幾個方面考慮一下:

計算機有一個局部性原理,就是說,當一個數據被用到時,其附近的數據也通常會馬上被使用。所以當你用紅黑樹的時候,你一次只能得到一個鍵值的信息,而用B樹,可以得到最多M-1個鍵值的信息。這樣來說B樹當然更好了。另外一方面,同樣的數據,紅黑樹的階數更大,B樹更短,這樣查找的時候當然B樹更具有優勢了,效率也就越高。

4.2 爲什麼要有B樹?

B樹事實上是一種平衡的多叉查找樹,也就是說最多可以開m個叉(m>=2),我們稱之爲m階b樹。

先盜個圖:

     一個m階B樹應該具備下面的特徵:

  1. 根結點只有一個,分支數量範圍爲[2,m];
  2. 分支結點,每個結點包含分支數範圍爲[ceil(m/2), m];
  3. 所有的葉結點都在同一層上;
  4. 有 k 棵子樹的分支結點則存在 k-1個關鍵碼,關鍵碼按照遞增次序進行排列;
  5. 每個結點關鍵字的數量範圍爲[ceil(m/2)-1, m-1]


(m階指的是分叉的個數最多爲m個,即一個非葉子節點最多可以有m個子節點。ceil表示向上取整,ceil(2.5)=3),下面是一個五階B樹:

這是一棵5階的B樹,每個節點的分支數在【3,5】之間,同時除根節點,一般節點所擁有的分支數也不得少於3;每個節點至多擁有4個關鍵碼,除根節點外每個節點至少擁有2個關鍵碼,結點內的關鍵字是有序的

4.3 B樹的查詢規則

在B-樹中查找給定關鍵字的方法是,首先把根結點取來,在根結點所包含的關鍵字K1,…,Kn查找給定的關鍵字(可用順序查找或二分查找法),若找到等於給定值的關鍵字,則查找成功;否則,一定可以確定要查找的關鍵字在Ki與Ki+1之間,Pi爲指向子樹根節點的指針,此時取指針Pi所指的結點繼續查找,直至找到,或指針Pi爲空時查找失敗。

4.4 B樹代碼實例

因爲B樹的特徵比較多,所以它的代碼比較複雜,首先建立節點內的Entry類:

/**
 * B樹節點中的鍵值對
 */
public class Entry<K, V> {

    private K key;
    private V value;

    public Entry(K k, V v)
    {
        this.key = k;
        this.value = v;
    }

    public K getKey()
    {
        return key;
    }

    public V getValue()
    {
        return value;
    }

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

    @Override
    public String toString()
    {
        return key + ":" + value;
    }

}

在建立B樹中的節點類(這裏的Node類似HashMap的結構,新增/查詢等也類似,可以參考):

package com.wo.domain.Btree;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;

/**
 * B樹中的節點。
 */
public class BTreeNode<K, V>
{
    /** 節點的項,按鍵非降序存放 */
    private List<Entry<K,V>> entrys;
    /** 內節點的子節點 */
    private List<BTreeNode<K, V>> children;
    /** 是否爲葉子節點 */
    private boolean leaf;
    /** 鍵的比較函數對象 */
    private Comparator<K> kComparator;

    BTreeNode()
    {
        entrys = new ArrayList<Entry<K, V>>();
        children = new ArrayList<BTreeNode<K, V>>();
        leaf = false;
    }

    public BTreeNode(Comparator<K> kComparator)
    {
        this();
        this.kComparator = kComparator;
    }

    public boolean isLeaf()
    {
        return leaf;
    }

    public void setLeaf(boolean leaf)
    {
        this.leaf = leaf;
    }

    /**
     * 返回項的個數。如果是非葉子節點,根據B樹的定義,
     * 該節點的子節點個數爲({@link #size()} + 1)。
     * @return 關鍵字的個數
     */
    public int size()
    {
        return entrys.size();
    }

    @SuppressWarnings("unchecked")
    int compare(K key1, K key2)
    {
        return kComparator == null ? ((Comparable<K>)key1).compareTo(key2) : kComparator.compare(key1, key2);
    }

    /**
     * 在節點中查找給定的鍵。
     * 如果節點中存在給定的鍵,則返回一個SearchResult,
     * 標識此次查找成功,給定的鍵在節點中的索引和給定的鍵關聯的值;
     * 這是一個二分查找算法,可以保證時間複雜度爲O(log(t))。
     * @param key - 給定的鍵值
     * @return - 查找結果
     */
    public SearchResult<V> searchKey(K key)
    {
        int low = 0;
        int high = entrys.size() - 1;
        int mid = 0;
        while(low <= high)
        {
            mid = (low + high) / 2; // 先這麼寫吧,BTree實現中,l+h不可能溢出
            Entry<K, V> entry = entrys.get(mid);
            if(compare(entry.getKey(), key) == 0) // entrys.get(mid).getKey() == key
                break;
            else if(compare(entry.getKey(), key) > 0) // entrys.get(mid).getKey() > key
                high = mid - 1;
            else // entry.get(mid).getKey() < key
                low = mid + 1;
        }
        boolean result = false;
        int index = 0;
        V value = null;
        if(low <= high) // 說明查找成功
        {
            result = true;
            index = mid; // index表示元素所在的位置
            value = entrys.get(index).getValue();
        }
        else
        {
            result = false;
            index = low; // index表示元素應該插入的位置
        }
        return new SearchResult<V>(result, index, value);
    }

    /**
     * 將給定的項追加到節點的末尾,
     * 你需要自己確保調用該方法之後,節點中的項還是
     * 按照關鍵字以非降序存放。
     * @param entry - 給定的項
     */
    public void addEntry(Entry<K, V> entry)
    {
        entrys.add(entry);
    }

    /**
     * 刪除給定索引的entry
     * 你需要自己保證給定的索引是合法的。
     * @param index - 給定的索引
     */
    public Entry<K, V> removeEntry(int index)
    {
        return entrys.remove(index);
    }

    /**
     * 得到節點中給定索引的項。
     * 你需要自己保證給定的索引是合法的。
     * @param index - 給定的索引
     * @return 節點中給定索引的項
     */
    public Entry<K, V> entryAt(int index)
    {
        return entrys.get(index);
    }

    /**
     * 如果節點中存在給定的鍵,則更新其關聯的值。
     * 否則插入。
     * @param entry - 給定的項
     * @return null,如果節點之前不存在給定的鍵,否則返回給定鍵之前關聯的值
     */
    public V putEntry(Entry<K, V> entry)
    {
        SearchResult<V> result = searchKey(entry.getKey());
        if(result.isExist())
        {
            V oldValue = entrys.get(result.getIndex()).getValue();
            entrys.get(result.getIndex()).setValue(entry.getValue());
            return oldValue;
        }
        else
        {
            insertEntry(entry, result.getIndex());
            return null;
        }
    }

    /**
     * 在該節點中插入給定的項,
     * 該方法保證插入之後,其鍵值還是以非降序存放。
     * 不過該方法的時間複雜度爲O(t)。
     * 注意:B樹中不允許鍵值重複。
     * @param entry - 給定的鍵值
     * @return true,如果插入成功,false,如果插入失敗
     */
    public boolean insertEntry(Entry<K, V> entry)
    {
        SearchResult<V> result = searchKey(entry.getKey());
        if(result.isExist())
            return false;
        else
        {
            insertEntry(entry, result.getIndex());
            return true;
        }
    }

    /**
     * 在該節點中給定索引的位置插入給定的項,
     * 你需要自己保證項插入了正確的位置。
     * @param index - 給定的索引
     */
    public void insertEntry(Entry<K, V> entry, int index)
    {
        /*
         * 通過新建一個ArrayList來實現插入
         * 要是有類似C中的reallocate就好了。
         */
        List<Entry<K, V>> newEntrys = new ArrayList<Entry<K, V>>();
        int i = 0;
        // index = 0或者index = keys.size()都沒有問題
        for(; i < index; ++ i)
            newEntrys.add(entrys.get(i));
        newEntrys.add(entry);
        for(; i < entrys.size(); ++ i)
            newEntrys.add(entrys.get(i));
        entrys.clear();
        entrys = newEntrys;
    }

    /**
     * 返回節點中給定索引的子節點。
     * 你需要自己保證給定的索引是合法的。
     * @param index - 給定的索引
     * @return 給定索引對應的子節點
     */
    public BTreeNode<K, V> childAt(int index)
    {
        if(isLeaf())
            throw new UnsupportedOperationException("Leaf node doesn't have children.");
        return children.get(index);
    }

    /**
     * 將給定的子節點追加到該節點的末尾。
     * @param child - 給定的子節點
     */
    public void addChild(BTreeNode<K, V> child)
    {
        children.add(child);
    }

    /**
     * 刪除該節點中給定索引位置的子節點。
     * 你需要自己保證給定的索引是合法的。
     * @param index - 給定的索引
     */
    public void removeChild(int index)
    {
        children.remove(index);
    }

    /**
     * 將給定的子節點插入到該節點中給定索引
     * 的位置。
     * @param child - 給定的子節點
     * @param index - 子節點帶插入的位置
     */
    public void insertChild(BTreeNode<K, V> child, int index)
    {
        List<BTreeNode<K, V>> newChildren = new ArrayList<BTreeNode<K, V>>();
        int i = 0;
        for(; i < index; ++ i)
            newChildren.add(children.get(i));
        newChildren.add(child);
        for(; i < children.size(); ++ i)
            newChildren.add(children.get(i));
        children = newChildren;
    }
}

 把查詢結果單獨保存:

/**
 * 在B樹節點中搜索給定鍵值的返回結果。
 * 該結果有兩部分組成。第一部分表示此次查找是否成功,
 * 如果查找成功,第二部分表示給定鍵值在B樹節點中的位置,
 * 如果查找失敗,第二部分表示給定鍵值應該插入的位置。
 */
public class SearchResult<V>
{
    private boolean exist;
    private int index;
    private V value;

    public SearchResult(boolean exist, int index)
    {
        this.exist = exist;
        this.index = index;
    }

    public SearchResult(boolean exist, int index, V value)
    {
        this(exist, index);
        this.value = value;
    }

    public boolean isExist()
    {
        return exist;
    }

    public int getIndex()
    {
        return index;
    }

    public V getValue()
    {
        return value;
    }
}

下面進入真正的BTree類:

public class BTree<K, V>
{
    private static Log logger = LogFactory.getLog(BTree.class);

    private static final int DEFAULT_T = 2;

    /** B樹的根節點 */
    private BTreeNode<K, V> root;
    /** 根據B樹的定義,B樹的每個非根節點的關鍵字數n滿足(t - 1) <= n <= (2t - 1) */
    private int t = DEFAULT_T;
    /** 非根節點中最小的鍵值數 */
    private int minKeySize = t - 1;
    /** 非根節點中最大的鍵值數 */
    private int maxKeySize = 2*t - 1;
    /** 鍵的比較函數對象 */
    private Comparator<K> kComparator;

    /**
     * 構造一顆B樹,鍵值採用採用自然排序方式
     */
    public BTree()
    {
        root = new BTreeNode<K, V>();
        root.setLeaf(true);
    }

    public BTree(int t)
    {
        this();
        this.t = t;
        minKeySize = t - 1;
        maxKeySize = 2*t - 1;
    }

    /**
     * 以給定的鍵值比較函數對象構造一顆B樹。
     *
     * @param kComparator - 鍵值的比較函數對象
     */
    public BTree(Comparator<K> kComparator)
    {
        root = new BTreeNode<K, V>(kComparator);
        root.setLeaf(true);
        this.kComparator = kComparator;
    }

    public BTree(Comparator<K> kComparator, int t)
    {
        this(kComparator);
        this.t = t;
        minKeySize = t - 1;
        maxKeySize = 2*t - 1;
    }

    @SuppressWarnings("unchecked")
    int compare(K key1, K key2)
    {
        return kComparator == null ? ((Comparable<K>)key1).compareTo(key2) : kComparator.compare(key1, key2);
    }

    /**
     * 搜索給定的鍵。
     *
     * @param key - 給定的鍵值
     * @return 鍵關聯的值,如果存在,否則null
     */
    public V search(K key)
    {
        return search(root, key);
    }

    /**
     * 在以給定節點爲根的子樹中,遞歸搜索
     * 給定的<code>key</code>
     *
     * @param node - 子樹的根節點
     * @param key - 給定的鍵值
     * @return 鍵關聯的值,如果存在,否則null
     */
    private V search(BTreeNode<K, V> node, K key)
    {
        SearchResult<V> result = node.searchKey(key);
        if(result.isExist())
            return result.getValue();
        else
        {
            if(node.isLeaf())
                return null;
            else
                search(node.childAt(result.getIndex()), key);

        }
        return null;
    }

    /**
     * 分裂一個滿子節點<code>childNode</code>。
     * <p/>
     * 你需要自己保證給定的子節點是滿節點。
     *
     * @param parentNode - 父節點
     * @param childNode - 滿子節點
     * @param index - 滿子節點在父節點中的索引
     */
    private void splitNode(BTreeNode<K, V> parentNode, BTreeNode<K, V> childNode, int index)
    {
        assert childNode.size() == maxKeySize;

        BTreeNode<K, V> siblingNode = new BTreeNode<K, V>(kComparator);
        siblingNode.setLeaf(childNode.isLeaf());
        // 將滿子節點中索引爲[t, 2t - 2]的(t - 1)個項插入新的節點中
        for(int i = 0; i < minKeySize; ++ i)
            siblingNode.addEntry(childNode.entryAt(t + i));
        // 提取滿子節點中的中間項,其索引爲(t - 1)
        Entry<K, V> entry = childNode.entryAt(t - 1);
        // 刪除滿子節點中索引爲[t - 1, 2t - 2]的t個項
        for(int i = maxKeySize - 1; i >= t - 1; -- i)
            childNode.removeEntry(i);
        if(!childNode.isLeaf()) // 如果滿子節點不是葉節點,則還需要處理其子節點
        {
            // 將滿子節點中索引爲[t, 2t - 1]的t個子節點插入新的節點中
            for(int i = 0; i < minKeySize + 1; ++ i)
                siblingNode.addChild(childNode.childAt(t + i));
            // 刪除滿子節點中索引爲[t, 2t - 1]的t個子節點
            for(int i = maxKeySize; i >= t; -- i)
                childNode.removeChild(i);
        }
        // 將entry插入父節點
        parentNode.insertEntry(entry, index);
        // 將新節點插入父節點
        parentNode.insertChild(siblingNode, index + 1);
    }

    /**
     * 在一個非滿節點中插入給定的項。
     *
     * @param node - 非滿節點
     * @param entry - 給定的項
     * @return true,如果B樹中不存在給定的項,否則false
     */
    private boolean insertNotFull(BTreeNode<K, V> node, Entry<K, V> entry)
    {
        assert node.size() < maxKeySize;

        if(node.isLeaf()) // 如果是葉子節點,直接插入
            return node.insertEntry(entry);
        else
        {
            /* 找到entry在給定節點應該插入的位置,那麼entry應該插入
             * 該位置對應的子樹中
             */
            SearchResult<V> result = node.searchKey(entry.getKey());
            // 如果存在,則直接返回失敗
            if(result.isExist())
                return false;
            BTreeNode<K, V> childNode = node.childAt(result.getIndex());
            if(childNode.size() == 2*t - 1) // 如果子節點是滿節點
            {
                // 則先分裂
                splitNode(node, childNode, result.getIndex());
                /* 如果給定entry的鍵大於分裂之後新生成項的鍵,則需要插入該新項的右邊,
                 * 否則左邊。
                 */
                if(compare(entry.getKey(), node.entryAt(result.getIndex()).getKey()) > 0)
                    childNode = node.childAt(result.getIndex() + 1);
            }
            return insertNotFull(childNode, entry);
        }
    }

    /**
     * 在B樹中插入給定的鍵值對。
     *
     * @param key - 鍵
     * @param value - 值
     */
    public boolean insert(K key, V value)
    {
        if(root.size() == maxKeySize) // 如果根節點滿了,則B樹長高
        {
            BTreeNode<K, V> newRoot = new BTreeNode<K, V>(kComparator);
            newRoot.setLeaf(false);
            newRoot.addChild(root);
            splitNode(newRoot, root, 0);
            root = newRoot;
        }
        return insertNotFull(root, new Entry<K, V>(key, value));
    }

    /**
     * 如果存在給定的鍵,則更新鍵關聯的值,
     * 否則插入給定的項。
     *
     * @param node - 非滿節點
     * @param entry - 給定的項
     * @return true,如果B樹中不存在給定的項,否則false
     */
    private V putNotFull(BTreeNode<K, V> node, Entry<K, V> entry)
    {
        assert node.size() < maxKeySize;

        if(node.isLeaf()) // 如果是葉子節點,直接插入
            return node.putEntry(entry);
        else
        {
            /* 找到entry在給定節點應該插入的位置,那麼entry應該插入
             * 該位置對應的子樹中
             */
            SearchResult<V> result = node.searchKey(entry.getKey());
            // 如果存在,則更新
            if(result.isExist())
                return node.putEntry(entry);
            BTreeNode<K, V> childNode = node.childAt(result.getIndex());
            if(childNode.size() == 2*t - 1) // 如果子節點是滿節點
            {
                // 則先分裂
                splitNode(node, childNode, result.getIndex());
                /* 如果給定entry的鍵大於分裂之後新生成項的鍵,則需要插入該新項的右邊,
                 * 否則左邊。
                 */
                if(compare(entry.getKey(), node.entryAt(result.getIndex()).getKey()) > 0)
                    childNode = node.childAt(result.getIndex() + 1);
            }
            return putNotFull(childNode, entry);
        }
    }

    /**
     * 如果B樹中存在給定的鍵,則更新值。
     * 否則插入。
     *
     * @param key - 鍵
     * @param value - 值
     * @return 如果B樹中存在給定的鍵,則返回之前的值,否則null
     */
    public V put(K key, V value)
    {
        if(root.size() == maxKeySize) // 如果根節點滿了,則B樹長高
        {
            BTreeNode<K, V> newRoot = new BTreeNode<K, V>(kComparator);
            newRoot.setLeaf(false);
            newRoot.addChild(root);
            splitNode(newRoot, root, 0);
            root = newRoot;
        }
        return putNotFull(root, new Entry<K, V>(key, value));
    }

    /**
     * 從B樹中刪除一個與給定鍵關聯的項。
     *
     * @param key - 給定的鍵
     * @return 如果B樹中存在給定鍵關聯的項,則返回刪除的項,否則null
     */
    public Entry<K, V> delete(K key)
    {
        return delete(root, key);
    }

    /**
     * 從以給定<code>node</code>爲根的子樹中刪除與給定鍵關聯的項。
     * <p/>
     * 刪除的實現思想請參考《算法導論》第二版的第18章。
     *
     * @param node - 給定的節點
     * @param key - 給定的鍵
     * @return 如果B樹中存在給定鍵關聯的項,則返回刪除的項,否則null
     */
    private Entry<K, V> delete(BTreeNode<K, V> node, K key)
    {
        // 該過程需要保證,對非根節點執行刪除操作時,其關鍵字個數至少爲t。
        assert node.size() >= t || node == root;

        SearchResult<V> result = node.searchKey(key);
        /*
         * 因爲這是查找成功的情況,0 <= result.getIndex() <= (node.size() - 1),
         * 因此(result.getIndex() + 1)不會溢出。
         */
        if(result.isExist())
        {
            // 1.如果關鍵字在節點node中,並且是葉節點,則直接刪除。
            if(node.isLeaf())
                return node.removeEntry(result.getIndex());
            else
            {
                // 2.a 如果節點node中前於key的子節點包含至少t個項
                BTreeNode<K, V> leftChildNode = node.childAt(result.getIndex());
                if(leftChildNode.size() >= t)
                {
                    // 使用leftChildNode中的最後一個項代替node中需要刪除的項
                    node.removeEntry(result.getIndex());
                    node.insertEntry(leftChildNode.entryAt(leftChildNode.size() - 1), result.getIndex());
                    // 遞歸刪除左子節點中的最後一個項
                    return delete(leftChildNode, leftChildNode.entryAt(leftChildNode.size() - 1).getKey());
                }
                else
                {
                    // 2.b 如果節點node中後於key的子節點包含至少t個關鍵字
                    BTreeNode<K, V> rightChildNode = node.childAt(result.getIndex() + 1);
                    if(rightChildNode.size() >= t)
                    {
                        // 使用rightChildNode中的第一個項代替node中需要刪除的項
                        node.removeEntry(result.getIndex());
                        node.insertEntry(rightChildNode.entryAt(0), result.getIndex());
                        // 遞歸刪除右子節點中的第一個項
                        return delete(rightChildNode, rightChildNode.entryAt(0).getKey());
                    }
                    else // 2.c 前於key和後於key的子節點都只包含t-1個項
                    {
                        Entry<K, V> deletedEntry = node.removeEntry(result.getIndex());
                        node.removeChild(result.getIndex() + 1);
                        // 將node中與key關聯的項和rightChildNode中的項合併進leftChildNode
                        leftChildNode.addEntry(deletedEntry);
                        for(int i = 0; i < rightChildNode.size(); ++ i)
                            leftChildNode.addEntry(rightChildNode.entryAt(i));
                        // 將rightChildNode中的子節點合併進leftChildNode,如果有的話
                        if(!rightChildNode.isLeaf())
                        {
                            for(int i = 0; i <= rightChildNode.size(); ++ i)
                                leftChildNode.addChild(rightChildNode.childAt(i));
                        }
                        return delete(leftChildNode, key);
                    }
                }
            }
        }
        else
        {
            /*
             * 因爲這是查找失敗的情況,0 <= result.getIndex() <= node.size(),
             * 因此(result.getIndex() + 1)會溢出。
             */
            if(node.isLeaf()) // 如果關鍵字不在節點node中,並且是葉節點,則什麼都不做,因爲該關鍵字不在該B樹中
            {
                logger.info("The key: " + key + " isn't in this BTree.");
                return null;
            }
            BTreeNode<K, V> childNode = node.childAt(result.getIndex());
            if(childNode.size() >= t) // // 如果子節點有不少於t個項,則遞歸刪除
                return delete(childNode, key);
            else // 3
            {
                // 先查找右邊的兄弟節點
                BTreeNode<K, V> siblingNode = null;
                int siblingIndex = -1;
                if(result.getIndex() < node.size()) // 存在右兄弟節點
                {
                    if(node.childAt(result.getIndex() + 1).size() >= t)
                    {
                        siblingNode = node.childAt(result.getIndex() + 1);
                        siblingIndex = result.getIndex() + 1;
                    }
                }
                // 如果右邊的兄弟節點不符合條件,則試試左邊的兄弟節點
                if(siblingNode == null)
                {
                    if(result.getIndex() > 0) // 存在左兄弟節點
                    {
                        if(node.childAt(result.getIndex() - 1).size() >= t)
                        {
                            siblingNode = node.childAt(result.getIndex() - 1);
                            siblingIndex = result.getIndex() - 1;
                        }
                    }
                }
                // 3.a 有一個相鄰兄弟節點至少包含t個項
                if(siblingNode != null)
                {
                    if(siblingIndex < result.getIndex()) // 左兄弟節點滿足條件
                    {
                        childNode.insertEntry(node.entryAt(siblingIndex), 0);
                        node.removeEntry(siblingIndex);
                        node.insertEntry(siblingNode.entryAt(siblingNode.size() - 1), siblingIndex);
                        siblingNode.removeEntry(siblingNode.size() - 1);
                        // 將左兄弟節點的最後一個孩子移到childNode
                        if(!siblingNode.isLeaf())
                        {
                            childNode.insertChild(siblingNode.childAt(siblingNode.size()), 0);
                            siblingNode.removeChild(siblingNode.size());
                        }
                    }
                    else // 右兄弟節點滿足條件
                    {
                        childNode.insertEntry(node.entryAt(result.getIndex()), childNode.size() - 1);
                        node.removeEntry(result.getIndex());
                        node.insertEntry(siblingNode.entryAt(0), result.getIndex());
                        siblingNode.removeEntry(0);
                        // 將右兄弟節點的第一個孩子移到childNode
                        // childNode.insertChild(siblingNode.childAt(0), childNode.size() + 1);
                        if(!siblingNode.isLeaf())
                        {
                            childNode.addChild(siblingNode.childAt(0));
                            siblingNode.removeChild(0);
                        }
                    }
                    return delete(childNode, key);
                }
                else // 3.b 如果其相鄰左右節點都包含t-1個項
                {
                    if(result.getIndex() < node.size()) // 存在右兄弟,直接在後面追加
                    {
                        BTreeNode<K, V> rightSiblingNode = node.childAt(result.getIndex() + 1);
                        childNode.addEntry(node.entryAt(result.getIndex()));
                        node.removeEntry(result.getIndex());
                        node.removeChild(result.getIndex() + 1);
                        for(int i = 0; i < rightSiblingNode.size(); ++ i)
                            childNode.addEntry(rightSiblingNode.entryAt(i));
                        if(!rightSiblingNode.isLeaf())
                        {
                            for(int i = 0; i <= rightSiblingNode.size(); ++ i)
                                childNode.addChild(rightSiblingNode.childAt(i));
                        }
                    }
                    else // 存在左節點,在前面插入
                    {
                        BTreeNode<K, V> leftSiblingNode = node.childAt(result.getIndex() - 1);
                        childNode.insertEntry(node.entryAt(result.getIndex() - 1), 0);
                        node.removeEntry(result.getIndex() - 1);
                        node.removeChild(result.getIndex() - 1);
                        for(int i = leftSiblingNode.size() - 1; i >= 0; -- i)
                            childNode.insertEntry(leftSiblingNode.entryAt(i), 0);
                        if(!leftSiblingNode.isLeaf())
                        {
                            for(int i = leftSiblingNode.size(); i >= 0; -- i)
                                childNode.insertChild(leftSiblingNode.childAt(i), 0);
                        }
                    }
                    // 如果node是root並且node不包含任何項了
                    if(node == root && node.size() == 0)
                        root = childNode;
                    return delete(childNode, key);
                }
            }
        }
    }

    /**
     * 一個簡單的層次遍歷B樹實現,用於輸出B樹。
     */
    public void output()
    {
        Queue<BTreeNode<K, V>> queue = new LinkedList<BTreeNode<K, V>>();
        queue.offer(root);
        while(!queue.isEmpty())
        {
            BTreeNode<K, V> node = queue.poll();
            for(int i = 0; i < node.size(); ++ i)
                System.out.print(node.entryAt(i) + " ");
            System.out.println();
            if(!node.isLeaf())
            {
                for(int i = 0; i <= node.size(); ++ i)
                    queue.offer(node.childAt(i));
            }
        }
    }

}

 測試類:

public class Test {
    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>();
        for(int i = 0; i < 10; ++ i)
        {
            int r = random.nextInt(100);
            save.add(r);
            System.out.println(r);
            btree.insert(r, r);
        }

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

測試結果:

65
30
15
86
54
43
30
8
81
65
----------------------
54:54 
8:8 15:15 30:30 43:43 
65:65 81:81 86:86 
----------------------
54:54 
8:8 15:15 30:30 43:43 
81:81 86:86 

五、B+樹

5.1 什麼是B+樹?

B+樹是在B樹上的擴展,查詢性能更優秀。

一個m階的B+樹具有如下幾個特徵:

  1. 有k個子樹的中間節點包含有k個元素(B樹中是k-1個元素),每個元素不保存數據,只用來索引,所有數據都在葉子節點。
  2. 所有葉子結點中包含全部元素的信息,及指向含這些元素記錄的指針,且葉子結點本身依關鍵字的大小自小而大順序鏈接。
  3. 所有的中間節點元素都同時存在於子節點,在子節點元素中是最大(或最小)元素。

上面就是一個B+樹,首先父節點的元素都出現在了子節點中,且是子節點中最大(或最小)的元素,根節點中最大元素等同於整棵樹的最大元素,在刪除新增元素時這個都不會變,保證最大元素在根節點;因爲父節點的元素都出現在了子節點中,所以所有葉子節點包含了全部的元素信息。葉子節點是通過指針連接的,形成了有序鏈表,因此B+樹可以支持範圍查詢。

5.2 B+樹和B樹區別

  1. B+樹相同情況下比B樹需要的IO次數更少
  2. B+樹的查詢必須最終查詢到子節點,B樹只要找到元素即可,不管是不是在也是節點
  3. B+樹支持範圍查找,B樹是一次一次遍歷

 

(ps:還有一種B*樹的,B*樹是在B+樹基礎上,爲非葉子結點也增加鏈表指針)

 

六、其他

6.1 B樹和紅黑樹的區別

B樹與紅黑樹最大的不同在於,B樹的結點可以有許多子女,從幾個到幾千個。

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章