虐你千萬遍,還要待她如初戀的紅黑樹,是否對她既歡喜又畏懼。別擔憂,經過本文講解,但願你能有史無前例的感動。java
紅黑樹也是二叉查找樹,但比普通的二叉查找樹多一些特性條件限制,每一個結點上都存儲有紅色或黑色的標記。由於是二叉查找樹,因此他擁有二叉查找樹的全部特性。紅黑樹是一種自平衡二叉查找樹,在極端數據條件插入時(正序或倒敘)不會退化成類鏈狀數據,能夠更高效的在O(log(n))時間內完成查找,插入,刪除操做。node
在閱讀本文以前,建議先閱讀我上篇文章《二叉查找樹的解讀和實現》,能夠更好的幫助你理解紅黑樹。this
紅黑樹的查找方式和上篇文章所講述的原理同樣,這裏就不從新講述,以結點[38,20,50,15,27,43,70,60,90]
爲例,返回一顆紅黑樹。編碼
紅黑樹的插入和刪除,分爲多種狀況,相對來講比較複雜。插入或刪除新結點後的樹,必需要知足上面五點特性的二叉查找樹,因此要經過不一樣手段來調整樹。但普通操做就是和普通二叉查找樹操做同樣。
好比普通插入中,由於每一個結點只能是紅色或黑色,因此咱們定義新添加的非根結點默認顏色爲紅色。將新結點定義爲紅色的緣由是爲了知足特性4(任一結點到葉子結點的每條路徑上黑色結點數量都相等),不然會多出一個黑色結點打破規則。
如今向樹中插入結點10。設計
從圖中能夠看到,父結點15爲黑色結點,插入紅色結點10,不會增長黑色結點的數量,其餘規則也沒有受到影響,因此,當插入結點的父結點爲黑色時,直接插入樹中,不會破壞原紅黑樹的規則。
該種狀況代碼實現:
結點對象code
package com.ytao.rbt; /** * Created by YANGTAO on 2019/11/9 0009. */ public class Node { public static String RED = "red"; public static String BLACK = "black"; public Integer value; public String color; public Node left; public Node right; public Node(Integer value, String color, Node left, Node right) { this.value = value; this.color = color; this.left = left; this.right = right; } public Node(int value, String color) { this.value = value; this.color = color; } }
實現操做對象
public void commonInsert(Node node, Integer newVal){ if (node == null) node = new Node(newVal, Node.BLACK); while (true){ if (newVal < node.value){ if (node.left == null){ // 若是左樹爲葉子結點而且父結點爲黑色,能夠直接插入紅色新結點 if (node.color == Node.BLACK){ node.left = new Node(newVal, Node.RED); break; } } node = node.left; }else if (newVal > node.value){ if (node.right == null){ if (node.color == Node.BLACK){ node.right = new Node(newVal, Node.RED); break; } } node = node.right; } } }
看到這段代碼,是否似曾相識的感受,沒錯,這就是上篇文章的插入操做加了個顏色限制。
一樣刪除也是如此,這裏就不在細述。blog
爲了更好分析清楚變色的緣由,咱們將樹中的50結點提取出來做爲根結點,如圖:get
向樹中添加結點55,獲得樹如圖:博客
這時55和60都爲紅色結點,不符合紅黑樹的特性(不容許連續兩個結點都爲紅色),這時咱們須要調整,就使用到變色。
將父結點60變爲黑色,又遇到不符合紅黑樹特性(任一結點到葉子結點的每條路徑上黑色結點數量都相等),由於咱們增長了黑色結點60,多出了一個黑色結點。
這時的結點70必定爲黑色,由於本來的父結點60的顏色爲紅色。將結點70變爲紅色,知足告終點70的左子樹,但右子樹受結點70變爲紅色的影響,少了個黑色結點,恰好結點90爲紅色,能夠將其變爲黑色,知足結點70的右子樹要求。
該種特殊狀況較爲簡單處理,只需經過變色就能處理。
這種條件結構的紅黑樹實現:
public void changeColor(Node node, int newVal){ if (node.left == null || node.right == null) return; // 經過判斷待插入結點的父結點和叔叔結點,是否知足咱們須要的條件 if (node.left.color == Node.RED && node.right.color == Node.RED){ // 肯定是更新到左樹仍是右樹中 Node base = compare(newVal, node.value) > 0 ? node.right : node.left; // 和待插入結點的父結點做比較 if (newVal < base.value && base.left == null){ base.left = new Node(newVal, Node.RED); }else if (newVal > base.value && base.right == null){ base.right = new Node(newVal, Node.RED); } } node.color = Node.RED; // 經過取反獲取插入結點的叔叔結點並將顏色變黑色 Node uncleNode = compare(newVal, node.value) > 0 ? node.left : node.right; uncleNode.color = Node.BLACK; } public int compare(int o1, int o2){ if (o1 == o2) return 0; return o1 > o2 ? 1 : -1; }
當僅僅經過變色沒法解決咱們須要知足特性時,咱們就要考慮使用紅黑樹的旋轉。
旋轉在插入和刪除中,會頻繁用到該操做,爲了知足咱們的五條特性,經過旋轉能夠生成一顆新的紅黑樹,旋轉分爲左旋轉和右旋轉。
左旋轉爲逆時針的旋轉,相似於把父結點往左邊拉(能夠這麼記憶區分左右旋轉的方向),變換如圖:
右旋轉與左旋轉出方向相反外,其餘都同樣,變換如圖:
從圖中能夠看出,旋轉後的父子結點,關係對調了,同時子結點的子結點給了父結點。
若是是左旋轉,那麼父結點會成爲旋轉結點的左子結點;子結點的左子結點會成爲父結點的右子結點。
若是是右旋轉,那麼父結點會成爲旋轉結點的右子結點;子結點的右子結點會成爲父結點的左子結點。
聽起來比較比較拗口,記住一條規則,左小右大,結合上圖兩個旋轉就比較好理解。
用代碼實現旋轉以下:
/** * * @param node 兩個旋轉結點中的父結點 * @param value 兩個旋轉結點中子結點的值,由於在整合旋轉的時候,node能夠遍歷查找出來,value做爲須要旋轉的標記結點 */ public void rotate(Node node, int value){ Node nodeChild = compare(value, node.value) > 0 ? node.right : node.left; if (nodeChild != null && value == nodeChild.value){ Node parent = node; // 旋轉子結點小於旋轉父結點,執行的是右旋轉,不然爲左旋轉 if (value < node.value){ rightRotate(parent); }else if (value > node.value){ leftRotate(parent); } } } /** * 左旋轉 * @param node 旋轉的父結點 */ public void leftRotate(Node node){ Node rightNode = node.right; // 旋轉結點的左子結點給父結點的右子結點 node.right = rightNode.left; // 父結點做爲子結點的左子結點 rightNode.left = node; } /** * 右旋轉 * @param node */ public void rightRotate(Node node){ Node leftNode = node.left; // 旋轉結點的右子結點給父結點的左子結點 node.left = leftNode.right; // 父結點做爲子結點的右子結點 leftNode.right = node; }
在上面結點爲38的紅黑中插入結點55。應用前面講解到的變色後,紅黑樹結構如圖:
此時出現不知足紅黑樹特性(不容許連續兩個結點都爲紅色),這時須要咱們將結點50和結點38進行左旋轉,獲得以下圖:
根結點50不符合紅黑樹特性(根結點必須爲黑色),因此先將根結點變爲黑色後。
如今獲得的紅黑樹,又出現違背(任一結點到葉子結點的每條路徑上黑色結點數量都相等)特性,左樹比右樹多一個黑色結點,此時將38,20,15,27顏色改變。
這裏通過變色旋轉完成了上面這課樹的操做紅黑樹的調整。
因爲代碼篇幅較大,並無將所有可能狀況都考慮進來。相信理解了紅黑樹的對編碼實現不是太大問題。
紅黑樹的操做是基於普通二叉查找樹上加了紅黑樹的特性,不論是插入仍是刪除操做,也就是在普通紅黑樹上進行旋轉變色調整樹結構,因此在理解紅黑樹的時候,主要把握旋轉,變色,利用旋轉變色來知足紅黑樹的特性,這也是紅黑樹的精華所在。懂得其原理和設計思想的話,應用到實際中解決問題確實是很不錯的設計。固然,紅黑樹在實際的操做過程當中是多變的,複雜的,要徹底掌握仍是要花點時間來研究的。
我的博客: https://ytao.top
個人公衆號 ytao