I. overview
The red black tree is a balanced binary tree. This kind of tree can carry out efficient middle order traversal. By constraining the color of each node on any simple path from root to leaf, it is ensured that no path is twice as long as other paths, so it is nearly balanced. We use red black tree when it has less rotation to keep balance and more insertion and deletion times. Widely used in C + + STL. For example, map and set are implemented by red black tree. [this paragraph comes from: https://blog.csdn.net/rth362147773/article/details/78014688] The code implements the algorithm of inserting and deleting the red black tree, including rotation, inserting repair, deleting repair and other sub algorithms. All are described with native javascript, without using third-party extension, which is rough but can see the effect.
II. Main functions
Refreshing the page automatically generates and renders a red black tree. It consists of 99 random numbers within 200. Click a node to delete it. Using the chrome console, you can see the general operation log to help you understand and debug the algorithm.
Three. Algorithm
The main difficulty of red black tree algorithm is to repair the properties of red black tree in the process of insertion and deletion. The algorithm is completely from Chapter 13 "red black tree" in "Introduction to algorithm".
Properties of Mangrove
- Each node is either red or black
- The root node is black
- Each leaf node (this.nil) is black
- Both children of the red node are black
- Each node contains the same number of black nodes on its simple path to all leaf nodes below it.
Key method
- insert node: insert
- Insert fix: insertFixup
- Delete node: delete
- Delete fix: deleteFixup
- Left rotate, right rotate
- Find: search
I spent a week learning and debugging the algorithm, and also reviewed the knowledge of binary tree. I feel that I have absorbed the most critical ideas. Welcome to exchange and discuss.
Four. Code
<head> <style> div{ padding:1px; margin:1px; border:1px #000 solid; border-left:0; display:table-cell; text-align:center; border-bottom:0px; } </style> </head> <body> </body> <script> (function(){ function preventBubble(event){ var e=arguments.callee.caller.arguments[0]||event; //If you omit this sentence, change e below to event, IE can run, but other browsers are not compatible if (e && e.stopPropagation) { e.stopPropagation(); } else if (window.event) { window.event.cancelBubble = true; } } // var RED = 'red'; // var BLACK = 'black'; // Red black tree function rbTree(){ console.log('init rbTree') this.nil = {p:null,color:BLACK} this.root = this.nil } // Levo rbTree.prototype.leftRotate = function(x){ console.log('leftRotate:'+x.key) y = x.right x.right = y.left if(y.left!=this.nil) y.left.p = x y.p = x.p if(x.p==this.nil) this.root = y else if(x==x.p.left) x.p.left = y else x.p.right = y y.left = x x.p = y } // Dextral rotation rbTree.prototype.rightRotate = function(x){ console.log('rightRotate:'+x.key) y = x.left x.left = y.right if(y.right!=this.nil) y.right.p = x y.p = x.p if(x.p==this.nil) this.root = y else if(x==x.p.right) x.p.right = y else x.p.left = y y.right = x x.p = y } // Insertion element rbTree.prototype.insert = function(z){ console.log('insert:'+z.key) z.p=this.nil // y = this.nil x = this.root while(x!=this.nil){ y = x if(z.key<x.key) x = x.left else x = x.right } z.p = y if(y==this.nil) this.root = z else if(z.key<y.key) y.left = z else y.right = z // z.left = this.nil z.right = this.nil z.color = RED this.insertFixup(z) } // Repair after insertion rbTree.prototype.insertFixup = function(z){ console.log('insertFixup:'+z.key) // while(z.p.color==RED){ if(z.p==z.p.p.left){ y = z.p.p.right if(y.color==RED){ z.p.color = BLACK y.color = BLACK z.p.p.color = RED z = z.p.p } else { if(z==z.p.right){ z=z.p this.leftRotate(z) } z.p.color = BLACK z.p.p.color = RED this.rightRotate(z.p.p) } } else{ y = z.p.p.left if(y.color==RED){ z.p.color = BLACK y.color = BLACK z.p.p.color = RED z = z.p.p } else { if(z==z.p.left){ z=z.p this.rightRotate(z) } z.p.color = BLACK z.p.p.color = RED this.leftRotate(z.p.p) } } } this.root.color=BLACK } // Show red black tree rbTree.prototype.display = function(t){ if(!t.key) return var position = t==t.p.left?'left':'right' if(t.p==this.nil) position = "root" var o = "key:"+t.key+ ",color:"+t.color+ ",p:"+t.p.key+ ",position: "+position //console.log(o) //console.log(t.p.key) if(position!='root'){ var pk = t.p.p && t.p.p!=this.nil ? 'node_'+t.p.p.key+'_'+t.p.key : 'node_'+t.p.key; var k = 'node_' + t.p.key + '_' + t.key } else{ var pk = ''; var k = 'node_' + t.key } //console.log(pk,t.p.p) var box = position=='root' ? document.body : document.getElementById(pk) //console.log('node_'+t.p.key) box.innerHTML += "<div onclick = 'window.delete(this);return false;' style='color:"+t.color+";border-color:"+t.color+"' id='"+k+"'>"+t.key+"<br /></div>" // //var div=document.createElement("div"); //div.innerHTML(o.) //btn.appendChild(t); this.display(t.left) this.display(t.right) } // Find the minimum value rbTree.prototype.minimum = function(x){ console.log("minimum:"+x.key) while(x.left!=this.nil){ x = x.left } return x } // Replace one element with another rbTree.prototype.transplant = function(u,v){ console.log("transplant:"+(v==this.nil?'null':v.key)) if(u.p==this.nil) this.root = v else if(u==u.p.left) u.p.left = v else u.p.right = v v.p=u.p } // Delete elements rbTree.prototype.delete = function(z){ console.log("delete:"+z.key) y=z y_original_color = y.color if(z.left == this.nil){ x=z.right this.transplant(z,z.right) } else if(z.right==this.nil){ x=z.left this.transplant(z,z.left) } else{ y=this.minimum(z.right) y_original_color = y.color x=y.right if(y.p==z){ x.p=y } else{ this.transplant(y,y.right) y.right = z.right y.right.p = y } this.transplant(z,y) y.left = z.left y.left.p = y y.color = z.color } if(y_original_color==BLACK) this.deleteFixup(x) } // Repair after deletion rbTree.prototype.deleteFixup = function(x){ console.log("deleteFixup:"+x.key) while (x!=this.root && x.color==BLACK ){ if (x==x.p.left) { w=x.p.right // Brother if (w.color==RED){ //Case 1: if the brother is red, the parent node must be black console.log('deleteFixup,condition:left,1') w.color=BLACK //Brother to black x.p.color=RED //Change the parent node to red this.leftRotate(x.p) //Rotate the parent node to the left and put the red color on the x to create conditions for the repair (prepare to remove the black). w = x.p.right } console.log(w.left.color,w.right.color) if (w.left.color==BLACK && w.right.color==BLACK){ //Situation 2: brother and disciple nodes are all black console.log('deleteFixup,condition:left,2') w.color=RED //Change brother to red, reduce brother Heigao (at this time, the balance is under x.p, and debt is pushed to the upper level) x=x.p //Up one layer, parent node double black (black height less than 1) } else{ //Brothers and children have red, brothers are black //Situation 3: brother is black, brother's right child is black, brother's left child is red. Push red to the right // (right brother, right brother, left brother) //This operation ensures that the right brother's right child is red if (w.right.color==BLACK ){ console.log('deleteFixup,condition:left,3') w.left.color=BLACK //Brother left child marked black w.color=RED //Brother label red this.rightRotate(w) //Brother dextral w=x.p.right // * new brothers } //Situation 4: right brother's right child is red. Eliminate double black, the most critical place console.log('deleteFixup,condition:left,4') w.color=x.p.color // When the brotherhood becomes a grandfather node, it needs to inherit the original color of the parent node. No matter red or black, it only needs to inherit. x.p.color=BLACK //The parent node is marked black (it may be original black, no effect, here is the key). w.right.color=BLACK //Brother right child marked black (key, there is an extra black element on the right side) this.leftRotate(x.p) // The parent node rotates to the left. At the most critical point, this rotation will add a black element to the left, the right side will remain unchanged, and the black elimination will be completed. x=this.root // In fact, break is realized here; } } else{ w=x.p.left // Brother if (w.color==RED){ //Case 1: if the brother is red, the parent node must be black console.log('deleteFixup,condition:right,1') w.color=BLACK //Brother to black x.p.color=RED //Change the parent node to red this.rightRotate(x.p) //Rotate the parent node to the left and put the red color on the x to create conditions for the repair (prepare to remove the black). w = x.p.left } console.log(w.left.color,w.right.color) if (w.right.color==BLACK && w.left.color==BLACK){ //Situation 2: brother and disciple nodes are all black console.log('deleteFixup,condition:right,2') w.color=RED //Change brother to red, reduce brother Heigao (at this time, the balance is under x.p, and debt is pushed to the upper level) x=x.p //Up one layer, parent node double black (black height less than 1) } else{ //Brothers and children have red, brothers are black //Situation 3: brother is black, brother's right child is black, brother's left child is red. Push red to the right // (right brother, right brother, left brother) //This operation ensures that the right brother's right child is red if (w.left.color==BLACK ){ console.log('deleteFixup,condition:right,3') w.left.color=BLACK //Brother left child marked black w.color=RED //Brother label red this.leftRotate(w) //Brother dextral w=x.p.left // * new brothers } //Situation 4: right brother's right child is red. Eliminate double black, the most critical place console.log('deleteFixup,condition:right,4') w.color=x.p.color // When the brotherhood becomes a grandfather node, it needs to inherit the original color of the parent node. No matter red or black, it only needs to inherit. x.p.color=BLACK //The parent node is marked black (it may be original black, no effect, here is the key). w.left.color=BLACK //Brother right child marked black (key, there is an extra black element on the right side) this.rightRotate(x.p) // The parent node rotates to the left. At the most critical point, this rotation will add a black element to the left, the right side will remain unchanged, and the black elimination will be completed. x=this.root // In fact, break is realized here; } } } // No matter from case 2 or case 4, this place must point to root, so it's actually T.root.color=BLACK x.color=BLACK } // lookup rbTree.prototype.search = function(x,k){ console.log("search:"+x.key) if(k==x.key) return x; if(k<x.key) return this.search(x.left,k) else return this.search(x.right,k) } // ------------These are the red and black trees rb = new rbTree(); // Click delete node window.delete = function(x){ console.log("cleck delete:"+x.id) var key = x.id.split('_').pop() console.log(key) node = rb.search(rb.root,key) rb.delete(node) document.body.innerHTML = '' console.log('display',rb.root) rb.display(rb.root) preventBubble() return false; } //--debug for manual build debugging var tmp = false; //var tmp = [71, 38, 80, 70, 52, 82, 16, 30, 96, 89, 20, 73, 30, 46, 14, 51, 87, 14, 52, 23, 55, 78, 58, 62, 99, 56, 54, 94, 27, 79, 71] if(tmp){ for(var _i=0;_i<tmp.length;_i++) rb.insert({key:tmp[_i],color:RED}) document.body.innerHTML = '' console.log('display',rb.root) rb.display(rb.root) return ; } //--debug var path = []; for(var i=0;i<99;i++){ var key = parseInt(Math.random()*199) path.push(key) //console.log(path) rb.insert({key:key,color:RED}) } document.body.innerHTML = '' console.log('display',rb.root) rb.display(rb.root) })(); </script> </html>``` # changelog 2019-12-13 Debug complete, release