二叉树,平衡树,红黑树,B~/B+树汇总
二叉查找树(BST),平衡二叉查找树(AVL),红黑树(RBT),B~/B+树(B-tree)。这四种树都具备下面几个优势:
(1) 都是动态结构。在删除,插入操作的时候,都不需要彻底重建原始的索引树。最多就是执行一定量的旋转,变色操作来有限的改变树的形态。而这些操作所付出的代价都远远小于重建一棵树。这一优势在《查找结构专题(1):静态查找结构概论 》中讲到过。
(2) 查找的时间复杂度大体维持在O(log(N))数量级上。可能有些结构在最差的情况下效率将会下降很快,比如二叉树
1.二叉查找树(Binary Search Tree)
下面的图就是两棵二叉查找树,我们可以总结一下他的特点:
- (1) 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值
- (2) 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值
- (3) 它的左、右子树也分别为二叉查找树
BST 的操作代价分析:
- (1) 查找代价: 任何一个数据的查找过程都需要从根结点出发,沿某一个路径朝叶子结点前进。因此查找中数据比较次数与树的形态密切相关。当树中每个结点左右子树高度大致相同时,树高为logN。则平均查找长度与logN成正比,查找的平均时间复杂度在O(logN)数量级上。当先后插入的关键字有序时,BST退化成单支树结构。此时树高n。平均查找长度为(n+1)/2,查找的平均时间复杂度在O(N)数量级上。
- (2) 插入代价: 新结点插入到树的叶子上,完全不需要改变树中原有结点的组织结构。插入一个结点的代价与查找一个不存在的数据的代价完全相同。
- (3) 删除代价: 当删除一个结点P,首先需要定位到这个结点P,这个过程需要一个查找的代价。然后稍微改变一下树的形态。如果被删除结点的左、右子树只有一个存在,则改变形态的代价仅为O(1)。如果被删除结点的左、右子树均存在,只需要将当P的左孩子的右孩子的右孩子的...的右叶子结点与P互换,在改变一些左右子树即可。因此删除操作的时间复杂度最大不会超过O(logN)。
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 的操作代价分析:
- 查找代价: AVL是严格平衡的BST(平衡因子不超过1)。那么查找过程与BST一样,只是AVL不会出现最差情况的BST(单支树)。因此查找效率最好,最坏情况都是O(logN)数量级的。
- 插入代价: AVL必须要保证严格平衡(|bf|<=1),那么每一次插入数据使得AVL中某些结点的平衡因子超过1就必须进行旋转操作。事实上,AVL的每一次插入结点操作最多只需要旋转1次(单旋转或双旋转)。因此,总体上插入操作的代价仍然在O(logN)级别上(插入结点需要首先查找插入的位置)。
- 删除代价:AVL删除结点的算法可以参见BST的删除结点,但是删除之后必须检查从删除结点开始到根结点路径上的所有结点的平衡因子。因此删除的代价稍微要大一些。每一次删除操作最多需要O(logN)次旋转。因此,删除操作的时间复杂度为O(logN)+O(logN)=O(2logN)
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) 是一棵满足下述性质的二叉查找树:
- 每一个结点要么是红色,要么是黑色。
- 根结点是黑色的。
- 所有叶子结点都是黑色的(实际上都是Null指针,下图用NIL表示)。叶子结点不包含任何关键字信息,所有查询关键字都在非终结点上。
- 每个红色结点的两个子节点必须是黑色的。换句话说:从每个叶子到根的所有路径上不能有两个连续的红色结点
- 从任一结点到其每个叶子的所有路径都包含相同数目的黑色结点
RBT 的操作代价分析:
- 查找代价:由于红黑树的性质(最长路径长度不超过最短路径长度的2倍),可以说明红黑树虽然不像AVL一样是严格平衡的,但平衡性能还是要比BST要好。其查找代价基本维持在O(logN)左右,但在最差情况下(最长路径是最短路径的2倍少1),比AVL要略逊色一点。
- 插入代价:RBT插入结点时,需要旋转操作和变色操作。但由于只需要保证RBT基本平衡就可以了。因此插入结点最多只需要2次旋转,这一点和AVL的插入操作一样。虽然变色操作需要O(logN),但是变色操作十分简单,代价很小。
- 删除代价:RBT的删除操作代价要比AVL要好的多,删除一个结点最多只需要3次旋转操作。
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+树的查询效率更加稳定。
由于非终结点并不是最终指向文件内容的结点,而只是叶子结点中关键字的索引。所以任何关键字的查找必须走一条从根结点到叶子结点的路。所有关键字查询的路径长度相同,导致每一个数据的查询效率相当。
二叉树,平衡树,红黑树,B~/B+树汇总的更多相关文章
- 二叉树、红黑树、伸展树、B树、B+树
好多树啊,程序猿砍树记,吼吼. 许多程序要解决的关键问题是:快速定位特定排序项的能力. 第一类:散列 第二类:字符串查找 第三类:树算法 树算法可以在辅助存储器中存储大量的数据. 二叉树.红黑树和伸展 ...
- 二叉树,红黑树,B+树
在实际使用时会根据链表和有序数组等数据结构的不同优势进行选择.有序数组的优势在于二分查找,链表的优势在于数据项的插入和数据项的删除.但是在有序数组中插入数据就会很慢,同样在链表中查找数据项效率就很低. ...
- 二叉树,红黑树,B树,B+树
1.不要认为红黑树仅仅是在二叉树的节点上涂上颜色,他们最根本的区别是,红黑树根据节点涂色的约束限制,最终形成的树的结构与普通二叉树不同,最重要的是,其树的高度大大缩短,从而在查找.增删改等方面提高效率 ...
- 数据结构(一)二叉树 & avl树 & 红黑树 & B-树 & B+树 & B*树 & R树
参考文档: avl树:http://lib.csdn.net/article/datastructure/9204 avl树:http://blog.csdn.net/javazejian/artic ...
- 二叉树、平衡二叉树、红黑树、B树、B+树与B*树
转: 二叉树.平衡二叉树.红黑树.B树.B+树与B*树 一.二叉树 1️⃣二叉查找树的特点就是左子树的节点值比父亲节点小,而右子树的节点值比父亲节点大,如图: 基于二叉查找树的这种特点,在查找某个节点 ...
- 红黑树、B(+)树、跳表、AVL等数据结构,应用场景及分析,以及一些英文缩写
在网上学习了一些材料. 这一篇:https://www.zhihu.com/question/30527705 AVL树:最早的平衡二叉树之一.应用相对其他数据结构比较少.windows对进程地址空间 ...
- AVL树、红黑树以及B树介绍
简介 首先,说一下在数据结构中为什么要引入树这种结构,在我们上篇文章中介绍的数组与链表中,可以发现,数组适合查询这种静态操作(O(1)),不合适删除与插入这种动态操作(O(n)),而链表则是适合删除与 ...
- C# 链表 二叉树 平衡二叉树 红黑树 B-Tree B+Tree 索引实现
链表=>二叉树=>平衡二叉树=>红黑树=>B-Tree=>B+Tree 1.链表 链表结构是由许多节点构成的,每个节点都包含两部分: 数据部分:保存该节点的实际数据. 地 ...
- 二叉搜索树、平衡二叉树、红黑树、B树、B+树
完全二叉树: 空树不是完全二叉树,叶子结点只能出现在最下层和次下层,且最下层的叶子结点集中在树的左部.如果遇到一个结点,左孩子不为空,右孩子为空:或者左右孩子都为空:则该节点之后的队列中的结点都为叶子 ...
- 红黑树和AVL树的实现与比较-----算法导论
一.问题描述 实现3种树中的两种:红黑树,AVL树,Treap树 二.算法原理 (1)红黑树 红黑树是一种二叉查找树,但在每个结点上增加一个存储位表示结点的颜色,可以是red或black.红黑树满足以 ...
随机推荐
- use 2 stacks to simulate a queue
class Stack{ private: ; ]; public: void push(int n); int pop(); int peek(); int size(); }; void Stac ...
- Mac 下office 2013制作组合表
1.选择所有数据,插入图表→柱状图.2.选中柱状图中得某一数据,如栏目量.这个在柱状图上单击一次橙色柱子就能全部选中.3.更改图表类型,改为饼状图,再单击饼状图改为折线图.
- js上拉跳转原理
今天遇到一个需要上拉跳转的地方,其原理跟上拉加载有点类似,代码如下 window.onscroll = function(){ if(getScrollTop() + getClientHeight( ...
- MySql - JdbcType - Oracle类型映射
MySql - JdbcType - Oracle类型映射 MySQL数据类型 JDBC TYPE Oracle数据类型 BIGINT BIGINT NUMBER(20) TINYINT TINY ...
- ACE_Message_Block消息数据类
ACE_Message_Block ACE_Message_Block用于构建"固定"和"可变"长度的消息.ACE_Message_Block可以将多条消息连接 ...
- HDU1115--Lifting the Stone(求凸多边形的重心)
Problem Description There are many secret openings in the floor which are covered by a big heavy sto ...
- H5加载优化
- ios UIApplication简单使用
每个app有且只有一个UIApplication对象,当程序启动的时候通过调用UIApplicationMain方法得到的.可以通过sharedApplication方法得到. UIApplicati ...
- LoadRunner编写Socket协议脚本方法
本文主要介绍使用LoadRunner手工编写Windows Socket协议测试脚本的方法. 通过LoadRunner编写Windows Socket协议测试脚本,总体说来,比较简单.就像把大象放进冰 ...
- LeetCode OJ 99. Recover Binary Search Tree
Two elements of a binary search tree (BST) are swapped by mistake. Recover the tree without changing ...