JavaScript data structure and algorithm study notes (in)

Seven, JavaScript implements tree structure (1)

1. Introduction to tree structure

1.1 Simple understanding of tree structure

Advantages of tree structure over array/linked list/hash table:

array:

  • Advantages: can be accessed by subscript value , high efficiency;
  • Disadvantages: When searching for data, you need to sort the data first to generate an ordered array to improve the search efficiency; and when inserting and deleting elements, a large number of displacement operations are required ;

linked list:

  • Advantages: data insertion and deletion operations are very efficient;
  • Disadvantages: The search efficiency is low, and it needs to be searched from the beginning until the target data is found; when it is necessary to insert or delete data in the middle of the linked list, the efficiency of inserting or deleting is not high.

Hash table:

  • Advantages: the insertion/query/deletion efficiency of the hash table is very high;
  • Disadvantages: The space utilization rate is not high , and many units in the underlying array are not used; and the elements in the hash table are out of order, and the elements in the hash table cannot be traversed in a fixed order; and the hash cannot be quickly found These special values ​​are the maximum or minimum values ​​in the table.

tree structure:

  • Advantages: The tree structure combines the advantages of the above three structures, and also makes up for their shortcomings (although the efficiency is not necessarily higher than them), such as the data in the tree structure is ordered, the search efficiency is high; the space utilization rate High; and can quickly obtain the maximum and minimum values, etc.

In general: each data structure has its own specific application scenarios

tree structure:

  • Tree (Tree) : A finite collection of n (n ≥ 0) nodes . When n = 0, it is called an empty tree .

For any non-empty tree (n > 0), it has the following properties:

  • There is a special node called Root in the number, represented by **r**;
  • The rest of the nodes can be divided into m (m > 0) finite sets T1, T2, ..., Tm that are not intersecting each other, and each set itself is a tree, called the subtree of the original tree (SubTree ) .

Common terms for trees:

b692456e722440f98996cba99e56d23d.png

  • Node degree (Degree) : the number of subtrees of the node , for example, the degree of node B is 2;
  • Degree of the tree : the maximum degree of all nodes in the tree , as shown in the above figure, the degree of the tree is 2;
  • Leaf node (Leaf) : a node with a degree of 0 (also called a leaf node), such as H, I, etc. in the above figure;
  • Parent node (Parent) : A node whose degree is not 0 is called a parent node, as shown in the figure above, node B is the parent node of nodes D and E;
  • Child node (Child) : If B is the parent node of D, then D is the child node of B;
  • Sibling : nodes with the same parent node are sibling nodes to each other, such as B and C in the above figure, D and E are sibling nodes to each other;
  • Path and path length : A path refers to the passage from one node to another node. The number of edges contained in the path is called the path length. For example, the path length of A->H is 3;
  • Node level (Level) : It is stipulated that the root node is at level 1 , and the level of any other node is the level of its parent node plus 1 . For example, the levels of nodes B and C are 2;
  • Depth of the tree (Depth) : The maximum level among all nodes of the tree is the depth of the tree, as shown in the above figure, the depth of the tree is 4;

1.2. Representation of tree structure

  • The most common representation:

cf3b5235f97746d894e14e8da754df11.png

As shown in the figure, the composition of the tree structure is similar to that of a linked list, which is composed of nodes connected one by one. However, depending on the number of child nodes of each parent node, the number of references required by each parent node is also different.

The disadvantage of this method is that we cannot determine the number of references to a node.

  • Son-brother notation:

6619e6dc8fe44de9ae31e4131f65c7ab.png

This representation method can completely record the data of each node, such as:

//Node A

Node{

  //Storing data

  this.data = data

  //Uniformly only record the child nodes on the left

  this.leftChild = B

  //Uniformly only record the first sibling node on the right

  this.rightSibling = null

}

//Node B

Node{

  this.data = data

  this.leftChild = E

  this.rightSibling = C

}

//Node F

Node{

  this.data = data

  this.leftChild = null

  this.rightSibling = null

}

The advantage of this notation is that the number of references in each node is deterministic.

  • son-brother notation rotation

The following is a tree structure composed of son-sibling notation:

5251e20441d546b4904ddc5c5d4c2065.png

 After rotating it 45° clockwise:

be5381ebe278417cafc6e4e4300df21e.png

This becomes a binary tree, from which we can conclude that any tree can be simulated by a binary tree .

Second, the binary tree

2.1. Introduction to binary tree

The concept of a binary tree : If each node in the tree can only have at most two child nodes , such a tree is called a binary tree ;

Binary trees are very important not only because of their simplicity, but also because almost all trees can be represented as binary trees.

The composition of the binary tree:

  • The binary tree can be empty, that is, there are no nodes;
  • If the binary tree is not empty, it consists of a root node and two disjoint binary trees called its left subtree TL and right subtree TR;

Five forms of binary trees:

dd04e6487e614172a2d76a2f0d4771c1.png

Features of a binary tree:

  • The maximum node tree of the i-th layer of a binary tree is: 2(i-1), i >= 1;
  • The maximum total number of nodes in a binary tree with a depth of k is: 2k - 1, k >= 1;
  • For any non-empty binary tree, if n0 represents the number of leaf nodes, and n2 represents the number of non-leaf nodes with a degree of 2, then the two satisfy the relationship: n0 = n2 + 1; as shown in the figure below: H, E, I, J, G are leaf nodes, the total number is 5; A, B, C, F are non-leaf nodes with degree 2, the total number is 4; satisfy the law of n0 = n2 + 1.

bdf12cb3c9bc485e9bee7ad6bd37468d.png

 2.2. Special binary trees

perfect binary tree

Perfect Binary Tree (Perfect Binary Tree) is also called Full Binary Tree (Full Binary Tree). In a binary tree, except for the leaf nodes of the lowest layer, each layer of nodes has 2 child nodes, which constitutes a perfect binary tree.

a2d86417e05741aca53ac565c957c2c7.png

Complete Binary Tree:

  • Except for the last layer of the binary tree, the number of nodes in each layer has reached the maximum value;
  • Moreover, the leaf nodes of the last layer exist continuously from left to right, and only a few leaf nodes on the right are missing;
  • A perfect binary tree is a special complete binary tree;

c78ab933278e41c08faf0352a294cece.png

In the image above, since H is missing its right child, it is not a complete binary tree.

2.3. Binary tree data storage

Common binary tree storage methods are arrays and linked lists :

Use an array:

  • Complete binary tree : store data from top to bottom and from left to right.

601c8ead7af542dca946615e9b2495dc.png

When using array storage, it is also very convenient to fetch data: the serial number of the left child node is equal to the serial number of the parent node * 2 , and the serial number of the right child node is equal to the serial number of the parent node * 2 + 1 .

  • Incomplete binary tree: The incomplete binary tree needs to be converted into a complete binary tree to be stored according to the above scheme, which will waste a lot of storage space.

b9f1fff6e84c4cce9426d570328b40bd.png

use linked list

The most common storage method of a binary tree is a linked list : each node is encapsulated into a Node, and the Node contains stored data, references to the left node, and references to the right node.

daba40c8da6b4c819a59d71b8c5ce25f.png

 3. Binary search tree

3.1. Know the binary search tree

Binary Search Tree ( BST , Binary Search Tree), also known as binary sorting tree and binary search tree .

A binary search tree is a binary tree that can be empty;

If not empty, the following properties are met :

  • Condition 1: All key values ​​of the non-empty left subtree are less than the key values ​​of its root node.
  • Condition 2: All key values ​​of the non-empty right subtree are greater than the key values ​​of its root node;
  • Condition 3: The left and right subtrees themselves are also binary search trees;

41c33542419b41d1b7394bd4bc4b8d6d.png

Eight, JavaScript implements tree structure (2)

1. Encapsulation of binary search tree

Basic properties of a binary search tree:

As shown in the figure: the binary search tree has four most basic attributes: the root pointing to the node (root), the key (key) in the node , the left pointer (right), and the right pointer (right).

89ed012121f440e9a60fc42acd9171c4.png

Therefore, in addition to defining the root attribute in the binary search tree, a node internal class should also be defined, which contains three attributes of left, right and key in each node:

//Encapsulate binary search tree

    function BinarySearchTree(){

      // node inner class

      function Node(key){

        this.key = key

        this.left = null

        this.right = null

      }

      //Attributes

      this.root = null

  }

Common operations on binary search trees :

  • insert(key): Insert a new key into the tree;
  • search(key): Find a key in the tree, and return true if the node exists; false if it does not exist;
  • inOrderTraverse: traverse all nodes through inorder traversal;
  • preOrderTraverse: traverse all nodes through preorder traversal;
  • postOrderTraverse: traverse all nodes through post-order traversal;
  • min: returns the smallest value/key in the tree;
  • max: returns the largest value/key in the tree;
  • remove(key): remove a key from the tree;

1. Insert data

Implementation idea:

  • First create a node object according to the incoming key;
  • Then judge whether the root node exists, if not, pass: this.root = newNode, directly use the new node as the root node of the binary search tree.
  • If there is a root node, redefine an internal method insertNode() to find the insertion point.

//insert method: method exposed to external users

  BinarySearchTree.prototype.insert = function(key){

        //1. Create a node according to the key

        let newNode = new Node(key)

        //2. Determine whether the root node exists

        if (this.root == null) {

          this.root = newNode

          // when the root node exists

        }else {

          this.insert(this.root, newNode)

        }

      }

The implementation idea of ​​the internal method insert() :

According to the comparison of the two incoming nodes, it keeps searching for the suitable insertion position of the new node until the new node is successfully inserted.

When newNode.key < node.key look left:

  • Case 1: When node has no left child node, insert directly:
  • Case 2: When the node has a left child node, call insert() recursively until new is successfully inserted without a left child node, and this situation is no longer met, so insert() is no longer called, and the recursion stops.

b5ef2e56e08c489baca0c3d11a46803c.png

When newNode.key >= node.key search to the right, similar to search to the left:

  • Case 1: When node has no right child node, insert directly:
  • Case 2: When the node has a right child node, insert() is still called recursively until the node passed into the insert method has no right child node and is successfully inserted into newNode:

cc158f20575943ecbcdffe0666d1f13a.png

 insert() code implementation:

 //The insert method used internally: used to compare whether the node is inserted from the left or the right


  BinarySearchTree.prototype.insert= function(node, newNode){         //When newNode.key < node.key search left         if(newNode.key < node.key){ //Case 1: node has no left child node, insert directly           if (node.left == null) {             node.left = newNode //case 2: node has a left child node, call insert() recursively           }else{             this.insert(node.left, newNode)           }








 //When newNode.key >= node.key look right
        }else{           //Case 1: node has no right child node, insert directly           if(node.right == null){             node.right == newNode           //Case 2 : node has a right child node, still call insert() recursively           }else{             this.insert(node.right, newNode)           }         }       }








2. Traverse the data

This traversal works for all binary trees. The three common binary tree traversal methods are:

  • preorder traversal;
  • Inorder traversal;
  • post-order traversal;

There is also layer order traversal, which is less used.

2.1. Preorder traversal

The process of preorder traversal is:

  • First, traverse the root node;
  • Then, traverse its left subtree;
  • Finally, traverse its right subtree;

ce44a3a2687945eab53508f3725639ee.png

As shown in the figure above, the node traversal order of the binary tree is: A -> B -> D -> H -> I -> E -> C -> F -> G.

Code:

      //Preorder traversal
      //Incorporate a handler function to facilitate the processing of the obtained key
      BinarySearchTree.prototype.preOrderTraversal = function(handler){         this.preOrderTraversalNode(this.root, handler)       }

      //Encapsulate the internal method to traverse a node

 BinarySearchTree.prototype.preOrderTraversalNode = function(node,handler){         if (node ​​!= null) {           //1. Process the passed node           handler(node.key)           //2. Traverse the nodes in the left subtree this.preOrderTraversalNode( node.left, handler)           //3. Traverse the nodes in the right subtree           this.preOrderTraversalNode(node.right, handler)         }       }








2.2. Inorder traversal

Implementation idea:

  • First, traverse its left subtree;
  • Then, traverse the root (parent) node;
  • Finally, traverse its right subtree;

06883e8ff0c8499fab86c4fd1eaa1659.png

The order of output nodes should be: 3 -> 5 -> 6 -> 7 -> 8 -> 9 -> 10 -> 11 -> 12 -> 13 -> 14 -> 15 -> 18 -> 20 -> 25 .

Code:

//Inorder traversal

 BinarySearchTree.prototype.midOrderTraversal = function(handler){

 this.midOrderTraversalNode(this.root, handler)

      }

 BinarySearchTree.prototype.midOrderTraversalNode = function(node, handler){

        if (node != null) {

          //1. Traverse the nodes in the left subtree

 this.midOrderTraversalNode(node.left, handler)

          //2. Processing node

          handler(node.key)

          //3. Traverse the nodes in the right subtree

 this.midOrderTraversalNode(node.right, handler)

        }

      }

2.3. Post-order traversal

Implementation idea:

  • First, traverse its left subtree;
  • Then, traverse its right subtree;
  • Finally, the root (parent) node is traversed;

d874a7d90a1940b4a15a9d56a251d928.png

The order of output nodes should be: 3 -> 6 -> 5 -> 8 -> 10 -> 9 -> 7 -> 12 -> 14 -> 13 -> 18 -> 25 -> 20 -> 15 -> 11 .

Code:

      //后序遍历
 BinarySearchTree.prototype.postOrderTraversal = function(handler){
        this.postOrderTraversalNode(this.root, handler)
      }

 BinarySearchTree.prototype.postOrderTraversalNode = function(node, handler){         if (node ​​!= null) {           //1. Traverse the nodes in the left subtree           this.postOrderTraversalNode(node.left, handler)           //2. Traverse the right subtree Node in           this.postOrderTraversalNode(node.right, handler)



          

          //3. Processing node
          handler(node.key)
        }
      }

3. Find data

3.1. Find the maximum & minimum value

Finding the most value in the binary search tree is very simple, the minimum value is at the far left of the binary search tree , and the maximum value is at the far right of the binary search tree . You only need to search left/right all the time to get the most value, as shown in the figure below:

18b8c3bc62824a399d5946f3f8e53348.png

Code:

      //Find the maximum value
      BinarySearchTree.prototype.max = function () {         //1. Get the root node         let node = this.root         //2. Define the key to save the node value         let key = null         //3. Search continuously to the right , until the node is null         while (node ​​!= null) {           key = node.key           node = node.right         }         return key       }










      //Find the minimum value
      BinarySearchTree.prototype.min = function(){          //1. Get the root node          let node = this.root         //2. Define the key to save the node value         let key = null         //3. Keep searching to the left in turn , until the node is null         while (node ​​!= null) {           key = node.key           node = node.left         }         return key       }










3.2. Find a specific value

Starting from the root node, compare the key value of the node to be searched with. If node.key < root , search to the left. If node.key > root , search to the right until it finds or finds null. Implemented with recursion/loop.

Implementation code:

//Find a specific key

  BinarySearchTree.prototype.search = function(key){

        //1. Get the root node

        let node = this.root

        //2. Loop search key

        while(node != null){

          if (key < node.key) {

            //If it is smaller than the root (parent) node, look to the left

            node = node.left

            //If it is greater than the root (parent) node, look for it to the right

          }else if(key > node.key){

            node = node.right

          }else{

            return true

          }

        } 

        return false

      }

4. Delete data

Implementation idea:

Step 1 : Find the node that needs to be deleted first, if not found, you don’t need to delete it;

First define the variable current to save the node to be deleted, the variable parent to save its parent node, and the variable isLeftChild to save whether the current is the left node of the parent, so that it is convenient to change the direction of the related node when deleting the node later.

Implementation code:

//1.1. Define variables

        let current = this.root

        let parent = null

        let isLeftChild = true

        //1.2. Start looking for deleted nodes

        while (current.key != key) {

          parent = current

          // If less than, search to the left

          if (key < current.key) {

            isLeftChild = true

            current = current.left

          } else{

            isLeftChild = false

            current = current.rigth

          }

          //Find the last node that is still not found

          if (current == null) {

            return false

          }

        }

        //After the end of the while loop: current.key = key

Step 2 : Delete the specified node found, and there are three cases:

  • Delete leaf nodes;
  • Delete a node with only one child node;
  • Delete a node with two children;

4.1. Case 1: No child nodes

There are also two cases when there are no child nodes:

  • When the leaf node is the root node, directly pass: this.root = null to delete the root node.
  • There are also two situations when the leaf node is not the root node, as shown in the following figure

0ff1f2cb4a3244df8081f561cee33358.png

 Code:

        //Case 1: A leaf node is deleted (no child nodes)
        if (current.left == null && current.right ==null) {           if (current == this.root) {             this.root = null           }else if (isLeftChild){             parent. left = null           } else {             parent. right = null           }         }







4.2. Case 2: There is a child node

There are six situations:

When current has a left child node (current.right == null):

  • Case 1: current is the root node;
  • Case 2: current is the left child node of the parent node parent (isLeftChild == true);
  • Case 3: current is the right child node of the parent node parent (isLeftChild == false);

d280084db4624702b3a2c9fee1b49a0e.png

When current has a right child node (current.left = null):

  • Case 4: current is the root node (current == this.root);
  • Case 5: current is the left child node of the parent node parent (isLeftChild == true);
  • Case 6: current is the right child node of the parent node parent (isLeftChild == false);

db81090a69f241e9b7f40b65cfb3f0f5.png

4.3. Case 3: There are two child nodes

This situation is very complicated. First, we discuss such problems based on the following binary search tree:

3f859a9a153449b0a993681ab6105bfa.png

delete node 9

Under the premise of ensuring that the original binary tree is still a binary search tree after deleting node 9, there are two ways:

  • Method 1: Select a suitable node from the left subtree of node 9 to replace node 9, and it can be seen that node 8 meets the requirements;
  • Method 2: Select a suitable node from the right subtree of node 9 to replace node 9, and it can be seen that node 10 meets the requirements;

98f25aa6a0574c078fd389fd7583287d.png

delete node 7

Under the premise of ensuring that the original binary tree is still a binary search tree after deleting node 7, there are two ways:

  • Method 1: Select a suitable node from the left subtree of node 7 to replace node 7, it can be seen that node 5 meets the requirements;
  • Method 2: Select a suitable node from the right subtree of node 7 to replace node 7, and it can be seen that node 8 meets the requirements;

42035c636c0d4e3bbe43ed2e920b29f1.png

delete node 15

Under the premise of ensuring that the original tree binary tree is still a binary search tree after deleting node 15, there are also two ways:

  • Mode 1: select a suitable node from the left subtree of node 15 to replace node 15, it can be known that node 14 meets the requirements;
  • Mode 2: select a suitable node from the right subtree of node 15 to replace node 15, it can be seen that node 18 meets the requirements;

23307d3dbe3e44959f0096831f35df45.png

Rule summary: If the node to be deleted has two child nodes, or even the child node has child nodes, in this case, it is necessary to find a suitable node from the child nodes below the node to be deleted to replace the current node.

If current is used to represent the node to be deleted, the appropriate node refers to:

  • The node in the current left subtree that is a little smaller than current is the maximum value in the current left subtree;
  • The node in the current right subtree that is slightly larger than current is the minimum value in the current right subtree;

predecessor & successor

In a binary search tree, these two special nodes have special names:

  • A node that is a little bit smaller than current is called the predecessor of the current node . For example, node 5 in the figure below is the predecessor of node 7;
  • A node that is a little bit larger than current is called the successor of the current node . For example, node 8 in the figure below is the successor of node 7;

98310e077ed645ad934683bf146ac931.png

Code:

  • To find the successor of current, you need to find the minimum value in the right subtree of current ;
  • When looking for the predecessor , you need to find the maximum value in the left subtree of current ;

4.4. Realize the search successor

      //Delete node
  BinarySearchTree.prototype.remove = function(key){ /*-----1. Find the node to be deleted ------*/         let current = this.root         let parent = null         let isLeftChild = true




        while (current.key != key) {           parent = current           // If it is less than, search to the left           if (key < current.key) {             isLeftChild = true             current = current.left           } else{             isLeftChild = false             current = current.right           }           / /Find the last node that is still not found           if (current == null) {             return false           }         }         //After the end of the while loop: current.key = key














   /*--2. Delete the node according to the corresponding situation -----*/
        //Case 1: The leaf node is deleted (no child node)
        if (current.left == null && current.right ==null) {           if (current == this.root) {             this.root = null           }else if(isLeftChild){             parent.left = null           }else {             parent.right =null           }         }         //Case 2: The deleted node has a child node         / /When current has a left child node         else if(current.right == null){             if (current == this.root) {               this.root = current.left             } else if(isLeftChild) {                 parent.left = current.left















            } else{                 parent.right = current.left             }         //When current has a right child node       } else if(current.left == null){             if (current == this.root) {               this.root = current.right             } else if(isLeftChild) {                 parent.left = current.right             } else{                 parent.right = current.right             }        }         //Case 3: The deleted node has two child nodes         else{           //1. Get the successor node           let successor = this .getSuccessor(current)















          //2. Judgment right or wrong root point
          if (current == this.root) {             this.root = successor           }else if (isLeftChild){             parent.left = successor           }else{             parent.right = successor           }





          //3. Change the successor left child node to the left child node of the deleted node
          successor.left = current.left
        }
      }

      //Encapsulate the method of finding the successor
      BinarySearchTree.prototype.getSuccessor = function(delNode){         //1. Define variables to save the found successor         let successor = delNode         let current = delNode.right         let successorParent = delNode



        //2. Loop to find the right subtree node of current
        while(current != null){           successorParent = successor           successor = current           current = current.left         }



        //3. Determine whether the found successor node is directly the right node of the deleted node
        if(successor != delNode.right){           successorParent.left = successor.right           successor.right = delNode.right          }         return successor       }




2. Balance tree

Disadvantages of binary search tree:

When the inserted data is ordered data, the depth of the binary search tree will be too large, seriously affecting the performance of the binary search tree.

29ebd351b5374672aa8555afcd989183.png

unbalanced tree

  • After inserting continuous data , the data distribution in the binary search tree becomes uneven , and we call this tree an unbalanced tree ;
  • For a balanced binary tree , the efficiency of operations such as insertion/finding is O(logN) ;
  • For an unbalanced binary tree, it is equivalent to writing a linked list, and the search efficiency becomes O(N) ;

tree balance

In order to be able to operate a tree in faster time O(logN) , we need to ensure that the tree is always balanced :

  • At least most of them are balanced, and the time complexity at this time is also close to O(logN);
  • This requires that the number of descendant nodes on the left of each node in the tree should be as equal as possible to the number of descendant nodes on the right ;

common balanced tree

  • AVL tree: It is the earliest balanced tree, which keeps the tree balanced by storing an extra data in each node. Since the AVL tree is a balanced tree, its time complexity is also O(logN). But its overall efficiency is not as good as the red-black tree, and it is less used in development.
  • Red-black tree: It also maintains the balance of the tree through some characteristics , and the time complexity is also O(logN). When performing operations such as insertion/deletion, the performance is better than that of the AVL tree , so the application of the balanced tree is basically a red-black tree.

Nine, graphic red-black tree

1. Five rules of red-black tree

In addition to complying with the basic rules of the binary search tree, the red-black tree also adds the following features:

  • Rule 1: Nodes are either red or black;
  • Rule 2: The root node is black;
  • Rule 3: Each leaf node is a black empty node (NIL node);
  • Rule 4: Both children of each red node are black (it is impossible to have two consecutive red nodes on all paths from each leaf to the root);
  • Rule 5: All paths from any node to each of its leaf nodes contain the same number of black nodes;

18384f84ff0243b0a57569a6f2f8a90f.png

Relative balance of red-black tree

The constraints of the previous five rules ensure the following key properties of red-black trees:

  • The longest path from the root to the leaf node will not exceed twice the shortest path ;
  • The result is that the tree is basically balanced;
  • Although there is no absolute balance, it can be guaranteed that the tree is still efficient in the worst case;

Why can the longest path not exceed twice the shortest path?

  • Property 4 determines that there cannot be two connected red nodes on the path;
  • Therefore, the longest path must be alternately formed by red nodes and black nodes;
  • Since the root node and leaf nodes are both black, the shortest path may be black nodes, and there must be more black nodes than red nodes in the longest path;
  • Property 5 determines that there are the same number of black nodes on all paths;
  • This means that no path can be more than twice as long as any other path.

2. Three transformations of red-black tree

When inserting a new node, it is possible that the tree is no longer balanced, and the tree can be kept balanced by three ways of transformation:

  • Discoloration;
  • rotate left;
  • rotate right;

2.1. Discoloration

In order to conform to the rules of the red-black tree again, it is necessary to turn the red node into black , or turn the black node into red ;

Inserted new nodes are usually red nodes :

  • When the inserted node is red , most cases do not violate any rules of the red-black tree;

  • Inserting a black node will inevitably lead to an extra black node on a path , which is difficult to adjust;

  • Although red nodes may lead to red-red connections , this situation can be adjusted by color swapping and rotation ;

2.2. Rotate left

Rotate the binary search tree counterclockwise with node X as the root , so that the original position of the parent node is replaced by its own right child node, and the position of the left child node is replaced by the parent node;

Detailed explanation:

As shown above, after left rotation:

  • Node X replaces the original position of node a;
  • Node Y replaces the original position of node X;
  • The left subtree a of node X  is still the left subtree of node X (here, the left subtree of X has only one node, and the same applies when there are multiple nodes, the same applies below);
  • The right subtree c of node Y is still the right subtree  of node Y ;
  • The left subtree b  of node Y is translated to the left to become the right subtree of node X ;

In addition, the binary search tree is still a binary search tree after left rotation:

2.3. Rotate right

Rotate the binary search tree clockwise with node X as the root , so that the original position of the parent node is replaced by its own left child node, and the position of the right child node is replaced by the parent node;

 

Detailed explanation:

As shown in the figure above, after right rotation:

  • Node X replaces the original position of node a;
  • Node Y replaces the original position of node X;
  • The right subtree a of node X is still the right subtree  of node X (here, although the right subtree of X has only one node, it also applies to multiple nodes, the same applies below);
  • The left subtree b of node Y is still the left subtree  of node Y ;
  • The right subtree  c of node Y is translated to the right to become the left subtree of node X ;

In addition, the binary search tree is still a binary search tree after right rotation

Third, the insertion operation of the red-black tree

First of all, it needs to be clear that the newly inserted node must be a red node when the five rules of the red-black tree are guaranteed to be satisfied .

For the convenience of explanation, the following four nodes are stipulated: the newly inserted node is N (Node), the parent node of N is P (Parent), the sibling node of P is U (Uncle), and the parent node of U is G (Grandpa), as follows As shown in the figure:

3.1. Situation 1

When a new node N is inserted at the root of the tree, it has no parent.

In this case, only need to change the red node to black node to satisfy rule 2.

3.2. Situation 2

The parent node P of the new node N is a black node, and no change is required at this time.

At this time, both rule 4 and rule 5 are satisfied. Although the new node is red, the new node N has two black nodes NIL, so the number of black nodes on the path leading to it is still equal, so rule 5 is satisfied.

3.3. Situation 3

Node P is red, and node U is also red. At this time, node G must be black, that is, the father is red, the uncle is red, and the ancestor is black .

In this case you need:

  • First change the parent node P to black;
  • Then change the uncle node U to black;
  • Finally, turn the grandparent node G into red;

That is to say, father black uncle black ancestor red , as shown in the figure below:

Problems that may arise:

  • The parent node of N's grandparent node G may also be red, which violates rule 4. At this time, the node color can be adjusted recursively;
  • When the recursive adjustment reaches the root node, it needs to be rotated, as shown in node A and node B in the figure below, and the specific situation will be introduced later;

3.4. Situation 4

Node P is a red node, node U is a black node, and node N is the left child node of node P. At this time, node G must be a black node, that is, father red uncle black ancestor black .

In this case you need:

  • Change color first: change the parent node P to black, and the grandparent node G to red;
  • Back rotation: right rotation with the grandparent node G as the root;

3.5. Situation 5

Node P is a red node, node U is a black node, and node N is the right child node of node P. At this time, node G must be a black node, that is, father red uncle black ancestor black .

In this case you need:

  • First rotate left with the node P as the root, as shown in Figure b after the rotation;
  • Then, the red node P and the black node B are regarded as a whole red node N1 , and the newly inserted red node N is regarded as a red node P1  , as shown in Figure c. At this time, the whole is transformed into case 4.

Then it can be processed according to case 4:

  • Change color first: change the parent node P1 of the N1 node to black, and change the grandparent node G to red;

  • Post-rotation: right-rotate with the grandparent node G as the root, as shown in Figure e after rotation;

  • Finally, transform the nodes N1 and P1 back to complete the insertion of the node N, as shown in Figure f;

Guess you like

Origin blog.csdn.net/m0_65835778/article/details/126482262