标签:
二叉查找树(BST),平衡二叉查找树(AVL),红黑树(RBT),B~/B+树(B-tree)。这四种树都具备下面几个优势:
(1) 都是动态结构。在删除,插入操作的时候,都不需要彻底重建原始的索引树。最多就是执行一定量的旋转,变色操作来有限的改变树的形态。而这些操作所付出的代价都远远小于重建一棵树。这一优势在《查找结构专题(1):静态查找结构概论 》中讲到过。
(2) 查找的时间复杂度大体维持在O(log(N))数量级上。可能有些结构在最差的情况下效率将会下降很快,比如二叉树
1.二叉查找树(Binary Search Tree)
下面的图就是两棵二叉查找树,我们可以总结一下他的特点:
BST 的操作代价分析:
package com.asteele.clrs.data_structures; import java.util.function.Consumer; public class BinarySearchTree<K extends Comparable<? super K>> { private BSTNode<K> root; private int size; protected static class BSTNode<E extends Comparable<? super E>> { private E data; private BSTNode<E> right; private BSTNode<E> left; private BSTNode<E> parent; protected BSTNode(E data) { this.data = data; } protected BSTNode<E> right() { return this.right; } protected BSTNode<E> left() { return this.left; } protected BSTNode<E> parent() { return this.parent; } } /** * Constructs an empty BinarySearchTree. */ public BinarySearchTree() { this.size = 0; this.root = null; } /** * Adds the given element to the tree. * Returns true if the element is successfully added. * * @param element to be added */ public boolean add(K element) { return addNode(new BSTNode<K>(element)); } protected boolean addNode(BSTNode<K> addedNode) { BSTNode<K> chaser = null; BSTNode<K> current = this.root; while (current != null) { chaser = current; if (addedNode.data.compareTo(current.data) < 0) { current = current.left; } else { current = current.right; // Equal nodes are always placed right. } } addedNode.parent = chaser; if (chaser == null) { this.root = addedNode; } else if (addedNode.data.compareTo(chaser.data) < 0) { chaser.left = addedNode; } else { chaser.right = addedNode; } this.size++; return addedNode != null; } /** * Remove the given element from the tree. If the element is successfully * removed, returns true. If the element does not exist in the tree, returns * false. * * @param element * @return true if found & removed, false otherwise */ public boolean remove(K element) { return removeNode(search(element)); } protected boolean removeNode(BSTNode<K> nodeToRemove) { if (nodeToRemove == null) return false; // The element was not found. if (nodeToRemove.left == null) { transplant(nodeToRemove, nodeToRemove.right); // Transplant accepts null arguments. This works properly when both children are null. } else if (nodeToRemove.right == null) { transplant(nodeToRemove, nodeToRemove.left); } else { BSTNode<K> successor = findTreeMinimum(nodeToRemove.right); if (successor.parent != nodeToRemove) { transplant(successor, successor.right); successor.right = nodeToRemove.right; successor.right.parent = successor; } transplant(nodeToRemove, successor); successor.left = nodeToRemove.left; successor.left.parent = successor; } this.size--; return true; } /** * Finds the minimum node of the tree with the given element as its root. */ private BSTNode<K> findTreeMinimum(BSTNode<K> parent) { BSTNode<K> chaser = null; BSTNode<K> current = parent; while (current != null) { chaser = parent; current = current.left; } return chaser; } /** * Replaces the first given node and its tree with the second given node and * its tree. This method does not change nodeToReplaceWith‘s left and right * subtrees. Doing so is the responsibility of the caller. */ private void transplant(BSTNode<K> nodeToBeReplaced, BSTNode<K> nodeToReplaceWith) { if (nodeToBeReplaced.parent == null) { this.root = nodeToReplaceWith; } else if (nodeToBeReplaced == nodeToBeReplaced.parent.left) { nodeToBeReplaced.parent.left = nodeToReplaceWith; } else { nodeToBeReplaced.parent.right = nodeToReplaceWith; } if (nodeToReplaceWith != null) { nodeToReplaceWith.parent = nodeToBeReplaced.parent; } } /** * Returns true if the given element exists in the tree, false otherwise. */ public boolean contains(K element) { return search(element) != null; } /** * Find the given element and return its corresponding node. * Returns Null if the element is not found. */ protected BSTNode<K> search(K element) { BSTNode<K> current = this.root; while (current != null) { int comparison = current.data.compareTo(element); if (comparison < 0) { current = current.right; } else if (comparison > 0){ current = current.left; } else { return current; } } return null; } /** * Rotate the tree beginning at the entered node left such that * the entered node becomes the left child of its current right child node. */ protected void rotateLeft(BSTNode<K> topNode) { if (topNode.right != null) { BSTNode<K> rightNode = topNode.right; topNode.right = rightNode.left; if (rightNode.left != null) { rightNode.left.parent = topNode; } rightNode.parent = topNode.parent; if (topNode.parent == null) { this.root = rightNode; } else if (topNode == topNode.parent.left) { topNode.parent.left = rightNode; } else { topNode.parent.right = rightNode; } rightNode.left = topNode; topNode.parent = rightNode; } } /** * Rotate the tree beginning at the entered node right such that * the entered node becomes the right child of its current left child node. */ protected void rotateRight(BSTNode<K> topNode) { if (topNode.left != null) { BSTNode<K> leftNode = topNode.left; topNode.left = leftNode.right; if (leftNode.right != null) { leftNode.right.parent = topNode; } leftNode.parent = topNode.parent; if (topNode.parent == null) { this.root = leftNode; } else if (topNode == topNode.parent.left) { topNode.parent.left = leftNode; } else { topNode.parent.right = leftNode; } leftNode.right = topNode; topNode.parent = leftNode; } } /** * Returns the root node of the tree. * * This protected method is needed for convenience * during the addition of elements to a Red Black Tree. */ protected BSTNode<K> root() { return this.root; } /** * Performs a pre-order traversal of the elements in the tree. For each element the * given consumer action is performed. * * Note that this method does not and cannot alter the data of the tree. * * @param action * action to be performed on each element */ public void preOrderTraversal(java.util.function.Consumer<K> action) { preOrderTraversal(this.root, action); } private static <T extends Comparable<? super T>> void preOrderTraversal( BSTNode<T> root, java.util.function.Consumer<T> action) { if (root != null) { action.accept(root.data); preOrderTraversal(root.left(), action); preOrderTraversal(root.right(), action); } } /** * Performs an in-order traversal of the elements in the tree, that is, one * in which the elements are accessed in sorted order. For each element the * given consumer action is performed. * * Note that this method does not and cannot alter the data of the tree. * * @param action * action to be performed on each element */ public void inOrderTraversal(java.util.function.Consumer<K> action) { inOrderTraversal(this.root, action); } private static <T extends Comparable<? super T>> void inOrderTraversal( BSTNode<T> root, Consumer<T> action) { if (root != null) { inOrderTraversal(root.left(), action); action.accept(root.data); inOrderTraversal(root.right(), action); } } /** * Performs a post-order traversal of the elements in the tree. For each element the * given consumer action is performed. * * Note that this method does not and cannot alter the data of the tree. * * @param action * action to be performed on each element */ public void postOrderTraversal(java.util.function.Consumer<K> action) { postOrderTraversal(this.root, action); } private static <T extends Comparable<? super T>> void postOrderTraversal( BSTNode<T> root, java.util.function.Consumer<T> action) { if (root != null) { postOrderTraversal(root.left(), action); postOrderTraversal(root.right(), action); action.accept(root.data); } } /** * Returns the size of this tree. */ public int size() { return this.size; } }
2.平衡二叉树(Balanced Binary Search Tree)
平衡二叉查找树,又称 AVL树。 它除了具备二叉查找树的基本特征之外,还具有一个非常重要的特点:它 的左子树和右子树都是平衡二叉树,且左子树和右子树的深度之差的绝对值(平衡因子 ) 不超过1。 也就是说AVL树每个节点的平衡因子只可能是-1、0和1。
AVL 的操作代价分析:
package com.asteele.clrs.data_structures; public class AVLTree<K extends Comparable<? super K>> extends BinarySearchTree<K>{ private static class AVLNode<E extends Comparable<? super E>> extends BSTNode<E> { private int height; private AVLNode(E data) { super(data); this.height = 1; } private int balanceFactor() { return leftHeight() - rightHeight(); } private void updateHeight() { this.height = Math.max(leftHeight(), rightHeight()) + 1; } private int leftHeight() { return this.left() == null ? 0 : ((AVLNode<E>)this.left()).height; } private int rightHeight() { return this.right() == null ? 0 : ((AVLNode<E>)this.right()).height; } } /** * {@inheritDoc} */ @Override public boolean add(K element) { AVLNode<K> addedNode = new AVLNode<K>(element); boolean wasSuccessful = super.addNode(addedNode); updateTree(addedNode); // Check for balance starting at chaser. Update heights of nodes. return wasSuccessful; } /** * Walks up the tree beginning at startingNode, checking for balance and updating heights. * If the tree is unbalanced at any node on the path from the startingNode * to the root, the tree is re-balanced. */ private void updateTree(AVLNode<K> startingNode) { while (startingNode != null) { startingNode.updateHeight(); rebalanceTree(startingNode); // Ensure AVL conditions are maintained. startingNode = (AVLNode<K>) startingNode.parent(); } } /** * Re-balances the tree with the given node as its root. If the tree is * already balanced, does nothing. */ private void rebalanceTree(AVLNode<K> root) { int balanceFactor = root.balanceFactor(); if (balanceFactor < -1 || balanceFactor > 1) { AVLNode<K> problemChild; // Which child node‘s side is too deep? // Is left side too deep? if (balanceFactor > 1) { problemChild = (AVLNode<K>) root.left(); if (problemChild.balanceFactor() == -1) { // Root-Left-Right misalignment super.rotateLeft(problemChild); problemChild = (AVLNode<K>) root.left(); ((AVLNode<K>) problemChild.left()).updateHeight(); } // Root-Left-left misalignment super.rotateRight(root); // Else is right side too deep? } else { problemChild = (AVLNode<K>) root.right(); if (problemChild.balanceFactor() == 1) { // Root-Right-Left misalignment super.rotateRight(problemChild); problemChild = (AVLNode<K>) root.right(); ((AVLNode<K>) problemChild.right()).updateHeight(); } // Root-Right-Right misalignment super.rotateLeft(root); } root.updateHeight(); // Root is now lower than problemChild. Update first. problemChild.updateHeight(); // This is called twice, once here and once in updateTree(). } } /** * {@inheritDoc} */ @Override public boolean remove(K element) { AVLNode<K> nodeToRemove = (AVLNode<K>) search(element); if (super.removeNode(nodeToRemove)) { updateTree((AVLNode<K>) nodeToRemove.parent()); // TODO: SHOULD START AT SUCCESSOR.PARENT. return true; } return false; } }
3.红黑树(Red-BlackTree)
二叉平衡树的严格平衡策略以牺牲建立查找结构(插入,删除操作)的代价,换来了稳定的O(logN) 的查找时间复杂度。但是这样做是否值得呢?
能不能找一种折中策略,即不牺牲太大的建立查找结构的代价,也能保证稳定高效的查找效率呢? 答案就是:红黑树。
红黑树(red-black tree) 是一棵满足下述性质的二叉查找树:
RBT 的操作代价分析:
import java.util.ArrayList; import java.util.List; import java.util.Random; //Binary Search Tree public class RedBlackTree extends BinarySearchTree { public final static String RED = "R"; public final static String BLACK = "B"; public final static BinarySearchTreeNode NIL = new RedBlackTreeNode(); static { NIL.setColor(BLACK); NIL.setKey(-10); } protected static void treeInsert(BinarySearchTree T, int key) { BinarySearchTreeNode a = new RedBlackTreeNode(); a.setKey(key); a.setParent(NIL); a.setLeft(NIL); a.setRight(NIL); a.setColor(""); treeInsert(T, a); } public static void treeInsert(BinarySearchTree T, BinarySearchTreeNode target) { // System.out.println("rb treeinsert"); if (target == null) { return; } BinarySearchTreeNode parent = NIL; BinarySearchTreeNode current = T.getRoot(); while (current != NIL && current != null) { parent = current; if (target.getKey() < current.getKey()) { // 小于在左边 current = current.getLeft(); } else if (target.getKey() > current.getKey()) { // 大于在右边 current = current.getRight(); } else { // 等于返回,因为左旋右旋容易出事 return; } } target.setParent(parent); if (parent == NIL) { // Tree root was empty T.setRoot(target); } else { if (target.getKey() < parent.getKey()) { parent.setLeft(target); } else { parent.setRight(target); } } target.setLeft(NIL); target.setRight(NIL); target.setColor(RED); // int spaceLength = 4; // String character = "^"; // printTreeByBFS(T.getRoot(), spaceLength, character); // printTreeByBFS(T.getRoot(), 7, "="); rbInsertFixup(T, target); // printTreeByBFS(T.getRoot(), 7, "*"); } private static void rbInsertFixup(BinarySearchTree T, BinarySearchTreeNode z) { BinarySearchTreeNode uncle = null; // System.out.println(z.getKey()); while (z.getParent().getColor() == RED) { if (z.getParent() == z.getParent().getParent().getLeft()) { uncle = z.getParent().getParent().getRight(); if (uncle.getColor() == RED) { z.getParent().setColor(BLACK); uncle.setColor(BLACK); z.getParent().getParent().setColor(RED); z = z.getParent().getParent(); } else { if (z == z.getParent().getRight()) { z = z.getParent(); leftRotate(T, z); } z.getParent().setColor(BLACK); z.getParent().getParent().setColor(RED); rightRotate(T, z.getParent().getParent()); } } else { uncle = z.getParent().getParent().getLeft(); if (uncle.getColor() == RED) { z.getParent().setColor(BLACK); uncle.setColor(BLACK); z.getParent().getParent().setColor(RED); z = z.getParent().getParent(); } else { if (z == z.getParent().getLeft()) { z = z.getParent(); rightRotate(T, z); } z.getParent().setColor(BLACK); z.getParent().getParent().setColor(RED); leftRotate(T, z.getParent().getParent()); } } } T.getRoot().setColor(BLACK); } public static void treeDelete_Suc(BinarySearchTree T, int key) { BinarySearchTreeNode target = treeSearch_Iterative(T.getRoot(), key); treeDelete_Suc(T, target); } public static BinarySearchTreeNode treeMinimum(BinarySearchTreeNode t) { while (t.getLeft() != null && t.getLeft() != NIL) { t = t.getLeft(); } return t; } public static BinarySearchTreeNode treeMaximum(BinarySearchTreeNode t) { while (t.getRight() != null && t.getRight() != NIL) { t = t.getRight(); } return t; } public static BinarySearchTreeNode treeSuccessor(BinarySearchTreeNode t) { if (t == null) { return t; } if (t.getRight() != null && t.getRight() != NIL) { return treeMinimum(t.getRight()); } BinarySearchTreeNode successor = t.getParent(); while (successor != null && successor != NIL && t == successor.getRight()) { t = successor; successor = successor.getParent(); } return successor; } public static BinarySearchTreeNode treePredecessor(BinarySearchTreeNode t) { if (t == null) { return t; } if (t.getLeft() != null && t.getLeft() != NIL) { return treeMaximum(t.getLeft()); } BinarySearchTreeNode predecessor = t.getParent(); while (predecessor != null && predecessor != NIL && t == predecessor.getLeft()) { t = predecessor; predecessor = predecessor.getParent(); } return predecessor; } public static void treeDelete_Suc(BinarySearchTree T, BinarySearchTreeNode target) { // System.out.println("rb treeDelete_Suc"); BinarySearchTreeNode candidate = null; BinarySearchTreeNode child = null; if (target == null || target == NIL) { return; } if (target.getLeft() == NIL || target.getRight() == NIL) { candidate = target; } else { candidate = treeSuccessor(target); } if (candidate.getLeft() != NIL) { child = candidate.getLeft(); } else { child = candidate.getRight(); } child.setParent(candidate.getParent()); if (candidate.getParent() == null || candidate.getParent() == NIL) { T.setRoot(child); } else { if (candidate == candidate.getParent().getLeft()) { candidate.getParent().setLeft(child); } else { candidate.getParent().setRight(child); } } if (candidate != target) { target.setKey(candidate.getKey()); } // printTreeByBFS(T.getRoot(), 7, "="); if (candidate.getColor() == BLACK) { rbDeleteFixup(T, child); } // printTreeByBFS(T.getRoot(), 7, "*"); } private static void rbDeleteFixup(BinarySearchTree T, BinarySearchTreeNode current) { BinarySearchTreeNode sibling = null; while (current != T.getRoot() && current.getColor() == BLACK) { if (current == current.getParent().getLeft()) { sibling = current.getParent().getRight(); if (sibling.getColor() == RED) { sibling.setColor(BLACK); current.getParent().setColor(RED); leftRotate(T, current.getParent()); sibling = current.getParent().getRight(); } if (sibling.getLeft().getColor() == BLACK && sibling.getRight().getColor() == BLACK) { sibling.setColor(RED); current = current.getParent(); } else { if (sibling.getRight().getColor() == BLACK) { // sibling.getRight().getColor() == RED && // sibling.getRight().getColor() == BLACK sibling.getLeft().setColor(BLACK); sibling.setColor(RED); rightRotate(T, sibling); sibling = current.getParent().getRight(); } // sibling.getRight().getColor() == RED sibling.setColor(current.getParent().getColor()); current.getParent().setColor(BLACK); sibling.getRight().setColor(BLACK); leftRotate(T, current.getParent()); current = T.getRoot(); } } else { sibling = current.getParent().getLeft(); if (sibling.getColor() == RED) { sibling.setColor(BLACK); current.getParent().setColor(RED); rightRotate(T, current.getParent()); sibling = current.getParent().getLeft(); } if (sibling.getRight().getColor() == BLACK && sibling.getLeft().getColor() == BLACK) { sibling.setColor(RED); current = current.getParent(); } else { if (sibling.getLeft().getColor() == BLACK) { sibling.getRight().setColor(BLACK); sibling.setColor(RED); leftRotate(T, sibling); sibling = current.getParent().getLeft(); } sibling.setColor(current.getParent().getColor()); current.getParent().setColor(BLACK); sibling.getLeft().setColor(BLACK); rightRotate(T, current.getParent()); current = T.getRoot(); } } } current.setColor(BLACK); } // FROM WIKI private static void rbDeleteFixup2(BinarySearchTree T, BinarySearchTreeNode child) { if (child.getColor() == RED) { child.setColor(BLACK); } else { deleteCase1(T, child); } } private static void deleteCase1(BinarySearchTree T, BinarySearchTreeNode child) { if (child.getParent() != null) { deleteCase2(T, child); } } private static void deleteCase2(BinarySearchTree T, BinarySearchTreeNode child) { BinarySearchTreeNode s = child.getSibling(); if (s.getColor() == RED) { child.getParent().setColor(RED); s.setColor(BLACK); if (child == child.getParent().getLeft()) { leftRotate(T, child.getParent()); } else { rightRotate(T, child.getParent()); } } deleteCase3(T, child); } private static void deleteCase3(BinarySearchTree T, BinarySearchTreeNode child) { BinarySearchTreeNode s = child.getSibling(); if (child.getParent().getColor() == BLACK && s.getColor() == BLACK && s.getLeft().getColor() == BLACK && s.getRight().getColor() == BLACK) { s.setColor(RED); deleteCase1(T, child.getParent()); } else { deleteCase4(T, child); } } private static void deleteCase4(BinarySearchTree T, BinarySearchTreeNode child) { BinarySearchTreeNode s = child.getSibling(); if (child.getParent().getColor() == RED && s.getColor() == BLACK && s.getLeft().getColor() == BLACK && s.getRight().getColor() == BLACK) { s.setColor(RED); child.getParent().setColor(BLACK); } else { deleteCase5(T, child); } } private static void deleteCase5(BinarySearchTree T, BinarySearchTreeNode child) { BinarySearchTreeNode s = child.getSibling(); if (s.getColor() == BLACK) { if (child == child.getParent().getLeft()) { if (s.getLeft().getColor() == RED && s.getRight().getColor() == BLACK) { s.setColor(RED); s.getLeft().setColor(BLACK); rightRotate(T, s); } } else { if (s.getLeft().getColor() == BLACK && s.getRight().getColor() == RED) { s.setColor(RED); s.getRight().setColor(BLACK); leftRotate(T, s); } } } deleteCase6(T, child); } private static void deleteCase6(BinarySearchTree T, BinarySearchTreeNode child) { BinarySearchTreeNode s = child.getSibling(); s.setColor(child.getParent().getColor()); child.getParent().setColor(BLACK); if (child == child.getParent().getLeft()) { s.getRight().setColor(BLACK); leftRotate(T, child.getParent()); } else { s.getLeft().setColor(BLACK); rightRotate(T, child.getParent()); } } public static BinarySearchTreeNode leftRotate(BinarySearchTree T, BinarySearchTreeNode target) { // System.out.println("leftRotate"); // printTreeByBFS(target, 3, "|"); if (target == null) { return null; } BinarySearchTreeNode parent = target.getRight(); target.setRight(parent.getLeft()); if (parent.getLeft() != RedBlackTree.NIL) { parent.getLeft().setParent(target); } parent.setParent(target.getParent()); if (target.getParent() == RedBlackTree.NIL) { T.setRoot(parent); } else { if (target == target.getParent().getLeft()) { target.getParent().setLeft(parent); } else { target.getParent().setRight(parent); } } parent.setLeft(target); target.setParent(parent); return parent; } public static BinarySearchTreeNode rightRotate(BinarySearchTree T, BinarySearchTreeNode target) { if (target == null) { return null; } BinarySearchTreeNode parent = target.getLeft(); target.setLeft(parent.getRight()); if (parent.getRight() != RedBlackTree.NIL) { parent.getRight().setParent(target); } parent.setParent(target.getParent()); if (target.getParent() == RedBlackTree.NIL) { T.setRoot(parent); } else { if (target == target.getParent().getRight()) { target.getParent().setRight(parent); } else { target.getParent().setLeft(parent); } } parent.setRight(target); target.setParent(parent); return parent; } public static BinarySearchTree initTree(int[] a) { BinarySearchTree T = new RedBlackTree(); int length = a.length; for (int i = 0; i < length; i++) { int v = a[i]; treeInsert(T, v); // int spaceLength = 3; // String character = "*"; // printTreeByBFS(T, spaceLength, character); } return T; } public static void main(String[] args) { int key = 85; int treeLength = 20; int spaceLength = 4; String character = " "; int[] a = getRandomArray(treeLength, key); BinarySearchTree tree = (BinarySearchTree) initTree(a); inOrderTreeWalk(tree.getRoot()); System.out.println(); printTreeByBFS(tree.getRoot(), spaceLength, character); } }
在前面专题中讲的BST、AVL、RBT都是典型的二叉查找树结构,其查找的时间复杂度与树高相关。那么降低树高自然对查找效率是有所帮助的。另外还有一个比较实际的问题:就是大量数据存储中,实现查询这样一个实际背景下,平衡二叉树由于树深度过大而造成磁盘IO读写过于频繁,进而导致效率低下。那么如何减少树的深度(当然不能减少查询数据量),一个基本的想法就是:
1. 每个节点存储多个元素 (但元素数量不能无限多,否则查找就退化成了节点内部的线性查找了)。
2. 摒弃二叉树结构,采用多叉树 (由于节点内元素数量不能无限多,自然子树的数量也就不会无限多了)。
这样我们就提出来了一个新的查找树结构 ——多路查找树。 根据AVL给我们的启发,一颗平衡多路查找树(B~树)自然可以使得数据的查找效率保证在O(logN)这样的对数级别上。
4.多路查找树[2-4]树
树是一棵典型的平衡多路查找树。性质如下:
1. 大小性质:每个结点最多4个子结点。
2. 深度性质:所有外部结点的深度相同。
其实是一棵迷你型的B树,其主要应用并不是为了将大数据量存储在外存上,而是通过减少树高来降低二叉查找树的查找代价
5.B~树(平衡多路二叉树)
B~树,又叫平衡多路查找树。一棵m阶的B~树 (m叉树)的特性如下:
1) 树中每个结点至多有m个孩子;
2) 除根结点和叶子结点外,其它每个结点至少有[m/2]个孩子;
3) 若根结点不是叶子结点,则至少有2个孩子;
4) 所有叶子结点都出现在同一层,叶子结点不包含任何关键字信息(可以看做是外部接点或查询失败的接点,实际上这些结点不存在,指向这些结点的指针都为null);
5) 每个非终端结点中包含有n个关键字信息: (n,A0,K1,A1,K2,A2,......,Kn,An)。其中,
a) Ki (i=1...n)为关键字,且关键字按顺序排序Ki < K(i-1)。
b) Ai为指向子树根的接点,且指针A(i-1)指向子树种所有结点的关键字均小于Ki,但都大于K(i-1)。
c) 关键字的个数n必须满足: [m/2]-1 <= n <= m-1
6.B+树
B+树:是应文件系统所需而产生的一种B~树的变形树。 一棵m阶的B+树和m阶的B-树的差异在于:
1) 有n棵子树的结点中含有n个关键字; (B~树是n棵子树有n+1个关键字)
2) 所有的叶子结点中包含了全部关键字的信息,及指向含有这些关键字记录的指针,且叶子结点本身依关键字的大小自小而大的顺序链接。 (B~树的叶子节点并没有包括全部需要查找的信息)
3) 所有的非终端结点可以看成是索引部分,结点中仅含有其子树根结点中最大(或最小)关键字。 (B~树的非终节点也包含需要查找的有效信息)
a、B+树的磁盘读写代价更低
我们都知道磁盘时可以块存储的,也就是同一个磁道上同一盘块中的所有数据都可以一次全部读取(详见《 外部存储器—磁盘 》 )。而B+树的内部结点并没有指向关键字具体信息的指针(比如文件内容的具体地址 , 比如说不包含B~树结点中的FileHardAddress[filenum]部分) 。因此其内部结点相对B~树更小。如果把所有同一内部结点的关键字存放在同一盘块中,那么盘块所能容纳的关键字数量也越多。这样,一次性读入内存中的需要查找的关键字也就越多。相对来说IO读写次数也就降低了。
举个例子,假设磁盘中的一个盘块容纳16bytes,而一个关键字2bytes,一个关键字具体信息指针2bytes。一棵9阶B~树(一个结点最多8个关键字)的内部结点需要2个盘快。而B+树内部结点只需要1个盘快。当需要把内部结点读入内存中的时候,B~树就比B+数多一次盘块查找时间(在磁盘中就是盘片旋转的时间)。
b、B+树的查询效率更加稳定。
由于非终结点并不是最终指向文件内容的结点,而只是叶子结点中关键字的索引。所以任何关键字的查找必须走一条从根结点到叶子结点的路。所有关键字查询的路径长度相同,导致每一个数据的查询效率相当。
标签:
原文地址:http://www.cnblogs.com/zhulongchao/p/4776992.html