Illustration of red-black tree and Java method for traversing red-black binary tree _java

Source: Internet
Author: User

Red and black Trees
Red-black tree is a kind of data structure and algorithm in the classroom often mentioned but do not talk about the tree, is also a technical interview often asked the tree, however, whether the book or online data, usually more rigid difficult to understand, can a more intuitive way to understand the red-black tree? This article will explain the insertion and deletion of the red-black tree in a graphical way.
The study of the tree structure is a progressive process, we usually contact the tree is two fork tree, the binary tree is simply that each non-leaf node has and only two children, called the Left and right children respectively. In a binary tree, there is a special kind of tree called Binary lookup tree, binary lookup tree is an ordered tree, for each non-leaf node, the value of its left subtree is less than it, its right subtree is greater than the value of the tree. More than the binary lookup tree is a two-fork balance tree, the binary balance tree in addition to ensure orderly, but also to maintain the height of the left and right subtree of each node is not more than 1. Common balance trees are AVL trees, treap, red-black trees, stretching trees, and so on.
A red-black tree is a binary lookup tree, but adding a storage bit to each node indicates the color of the node, which can be red or dark. The red-black tree ensures that no path will be twice times longer than the other paths, and thus is close to equilibrium by limiting the way each node is shaded from the root to the leaf path.
The red-black tree satisfies 5 properties:

    • Each node is red or black;
    • The root node is black;
    • Each leaf node nil is black;
    • If a node is red, its two children are black; (no two consecutive red nodes on each path)
    • Either node contains the same number of black nodes on the path to all of its descendants ' leaf nodes nil.

Note that in the red-black tree, the child of the traditional binary tree is pointed to nil, which is called nil as the leaf node in the red-black tree. The nil node contains a pointer to the parent node, which may be the reason why you need to change null to nil.

First, insert operation
The new node is first inserted in the two-fork lookup tree (where the new node is inserted at the leaf node) and painted red. Then redraw its color or rotate to maintain the nature of the red-black tree, the adjustment is divided into the following three kinds of situations:
1 new node n does not have a parent node (that is, at the root)
paints the new node n as black.

2 The parent node of the new node n is black
no adjustment.

3 new node N's parent node p is red
because the red-black tree does not allow two consecutive red nodes (Nature 4), so need to adjust, according to n Uncle node color divided into two situations: (We take n of the parent node p for the left child as an example, p is similar to the case of the right child, no longer detailed)
3.1 new node N Uncle node U for Red
The parent node p and Uncle node of the new node n are painted black and the grandfather node G is painted red so that the number of black nodes on the path from G to each null node is the same as the original. But since we turned G red, if G's father is red, it could lead to two consecutive red nodes (violating nature 4), so it is necessary to re-examine whether G violates the red-black tree nature.

3.2 new node N Uncle node U for Black
If the new node n is the left child of its parent node P: The parent node p is painted black, the grandfather node G is painted red, and then the G is rotated right.

If the new node n is the right child of its parent node P: A left rotation of its parent node, the problem translates to the left child.

Second, delete operation
"Introduction to Algorithms" and the Wikipedia approach are all when you delete a black node D, push D's Black "down" to its child node C, which means that C has a heavier extra black in addition to its own color, and then continues to move the extra black over the tree until it touches a red node, Make it black to ensure that the number of black nodes on the path remains unchanged, or move to the root of the tree so that the number of black nodes on all paths is reduced by one and remains equal. You may need to rotate and modify the colors of some nodes during the move up to ensure that the number of black nodes on the path is unchanged.
This approach may be beneficial to the implementation of the Code (in terms of iteration) but is not easy to understand (personally). In order to understand the priority, I based on the deleted node D of the child is nil do the following categories:
1 deleted node D's two children are nil.
1.1 deleted node D is red
Replace d with Nil.

1.2 deleted node D is black (we take D as the left child for example)
1.2.1 deleted node D's brother Node B's two children are nil
The brother Node B of D is painted red and the parent node p is painted black.

Half red and half black in the figure indicates that the node may be red or black. If p turns out to be red, then the number of black nodes on the path is the same as before deleting D; If p turns out to be black, then removing d will cause the number of black nodes on the path to be less than before the deletion, so it is also necessary to continue to check whether the change in the number of black nodes through P's path affects the nature of the red-black tree.
1.2.2 Deleted node D brother Node B there's a child not for nil
The child must be red, otherwise the number of black nodes on the path from the parent node of D to each leaf node will vary (in violation of 5).
If the child is a right child, the right child of B is painted black, B is painted as the original color of its parent node p, p is painted black, and then the p is rotated one left.

If the child is the left child, the left child of B is painted black, B is painted red, and then a right rotation of B, the problem is converted to the situation of the right child.

1.2.3 deleted node D's brother Node B's two children are not nil
If B is red, then B's two children must be black. Paint B Black, B's left child is red, and then rotate the left one at a time.

If B is black, then B's two children must be red. The parent of B is painted black, the right child of B is painted black, B is painted as the original color of its parent node p, and then a left rotation is made to p.

2 deleted node D's two kids are not nil.
the successor node of D is found by means of the binary lookup tree deletion node. Swap the contents of D and S (color remains unchanged), the deleted node becomes s, if s has a node that is not nil, then continue to replace s with the successor node of S, until the two children of the deleted node are nil, The problem translates to the case where the two children of the deleted node D are nil.
3 deleted node D there's a kid who's not nil.
This child C must be a red node, otherwise the number of black nodes on the path from D to each nil node will vary (in violation of 5).
Exchange D and C content (color remains unchanged), the deleted node becomes C, the problem is converted to the deleted node D two children are nil.

Traversal of binary Tree
There are three kinds of traversal of two-tree: pre-sequence traversal, middle-sequence traversal and sequential traversal. The implementation of each traversal has two kinds of recursion and iteration, this article we discuss how to use the more elegant code to implement the traversal of the two-fork tree.
First I'll define a node for a binary tree:

public class TreeNode {
  int val;
  TreeNode left;
  TreeNode right;
  
  public TreeNode (int x) {
    val = x;
  }
}


first-order traversal (preorder traversal)
in short, the first-order traversal is to access the parent node, then access the left child, the last access to the right child, that is, the parent, left and right in the order to traverse.
Recursive implementation is very simple, the code is as follows:

public class Solution {
  list<integer> result = new arraylist<integer> ();
  
  Public list<integer> preordertraversal (TreeNode root) {
    Dfs (root);
    return result;
  }
  
  private void Dfs (TreeNode root) {
    if (root = null) {return
      ;
    } 
    Result.add (root.val);
    DFS (root.left);
    DFS (root.right);
  }

An iterative implementation requires a stack to store the right node that is not accessed, as follows:

public class Solution {public
 
  list<integer> preordertraversal (TreeNode root) {
    list<integer> result = new arraylist<integer> ();
    
    if (root = = null) {return result
      ;
    }
    
    stack<treenode> stack = new stack<treenode> ();
    Stack.push (root);
    
    while (!stack.isempty ()) {
      TreeNode Curr = Stack.pop ();
      Result.add (curr.val);
      
      if (curr.right!= null) {
        stack.push (curr.right);
      }
      if (curr.left!= null) {
        stack.push (curr.left);
      }
    }
    
    return result;
  }


second, in-sequence traversal (inorder traversal)
in short, the middle-order traversal is to first access the left child, and then access the parent node, the last access to the right child, that is, the left, the parent, the right sequence traversal.
Recursive code is also relatively easy, as follows:

public class Solution {public
 
  list<integer> inordertraversal (TreeNode root) {
    list<integer> result = new arraylist<integer> ();
    Recurse (root, result);
    return result;
  }
  
  private void Recurse (TreeNode root, list<integer> result) {
    if (root = null) return;
    Recurse (root.left, result);
    Result.add (root.val);
    Recurse (root.right, result);
  }

This is different from the recursive code of the preceding sequence traversal, recursion we use the member variable to store the results of the traversal, where we use the method parameters to save the results of the traversal. Both types of writing can be, like which kind of use.
The iterative implementation of the sequence traversal is not as simple as the forward traversal, although it requires a stack, but the conditions for the termination of the iteration are different. Imagine, for a binary tree, the first node we visit is its leftmost node, of course we can reach its leftmost through a while loop, but when we roll back, how do we know if a node's left child has been visited? We use a Curr variable to record the currently accessed node, when we have access to the right node of a Shang tree, we should rollback the parent of the subtree, and Curr is null, so we can use this to distinguish between the Zuozi of a node that has been accessed. The code is as follows:

public class Solution {public
  
  list<integer> inordertraversal (TreeNode root) {
    list<integer> result = new arraylist<integer> ();
    
    stack<treenode> stack = new stack<treenode> ();
    TreeNode Curr = root;
    
    while (Curr!= null | |!stack.isempty ()) {while
      (Curr!= null) {
        stack.push (curr);
        Curr = Curr.left;
      }
      Curr = Stack.pop ();
      Result.add (curr.val);
      
      Curr = curr.right;
    }
    
    return result;
  }


third, the subsequent traversal (postorder traversal)
in short, sequential traversal is the first access to the left child, access to the right child, the last access to the parent node, that is, the left, right, and the parent in the order of traversal.
Following the sequence traversal, it is easy to write the recursive implementation of the subsequent traversal:

public class Solution {public
 
  list<integer> postordertraversal (TreeNode root) {
    list<integer> result = new arraylist<integer> ();
    Recurse (root, result);
    return result;
  }
  
  private void Recurse (TreeNode root, list<integer> result) {
    if (root = null) return;
    Recurse (root.left, result);
    Recurse (root.right, result);
    Result.add (Root.val);
  }

Subsequent traversal of the iteration, but also need a logo to distinguish between the left and right of a node whether the child has been visited, if not, then access its left and right children, if accessed, then access the node. To this end, we use a pre variable to represent the last access node, if the last access node is the current node of the left child or right child, then the current node of the child has been visited, then can access the node, otherwise, you need to access the children in turn. The code is as follows:

public class Solution {public
 
  list<integer> postordertraversal (TreeNode root) {
    list<integer> result = new linkedlist<integer> ();
    
    stack<treenode> stack = new stack<treenode> ();
    if (root!= null) Stack.push (root);
    
    TreeNode pre = root;
    
    while (!stack.isempty ()) {
      TreeNode Curr = Stack.peek ();
      if (Curr.left = Pre | | curr.right = PRE | | (Curr.left = = NULL && curr.right = null)) {
        result.add (curr.val);
        Stack.pop ();
        Pre = Curr;
      } else {
        if (curr.right!= null) Stack.push (curr.right);
        if (curr.left!= null) Stack.push (curr.left);
      }
    }
    
    return result;
  }

There is another relatively simple implementation of the iteration of the sequential traversal, and we know that the order of first-order traversal is the parent, left, right, and then the sequence of traversal is left, right, father, then if we take the first sequence of a slightly modified, change to the father, right and left order, then just the order of the sequence of traversal and the reverse, in such a sequential access to the end, Finally, we will make a reversal of the results of the visit. The iterative implementation of the first-order traversal is relatively easy, and we can follow the example above to achieve the following:

public class Solution {public
 
  list<integer> postordertraversal (TreeNode root) {
    list<integer> result = new linkedlist<integer> ();
    
    stack<treenode> stack = new stack<treenode> ();
    if (root!= null) Stack.push (root);
    
    while (!stack.isempty ()) {
      TreeNode Curr = Stack.pop ();
      Result.add (curr.val);
      if (curr.left!= null) Stack.push (curr.left);
      if (curr.right!= null) Stack.push (curr.right);
    }
 
    Collections.reverse (result);
    
    return result;
  }


Iv. Summary
recursive implementations of three traversal are easy. The iterative implementation of the first sequence traversal is best written, only need a stack on the good; the most difficult part of the sequence traversal, in addition to determining whether the stack is empty, but also to determine whether the current node is empty, to indicate whether the left subtree has been traversed, and the subsequent traversal of the iteration if converted to the first-order traversal of the iteration, it will be much easier, otherwise, You also need to record the last accessed node to indicate whether the left and right subtree of the current node has been accessed.

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.