死磕 java集合之TreeMap源碼分析(二)- 內含紅黑樹分析全過程

来源:https://www.cnblogs.com/tong-yuan/archive/2019/04/04/10657274.html
-Advertisement-
Play Games

死磕 java集合之TreeMap源碼分析(二) 紅黑樹插入元素的時間複雜度如何? 為什麼插入元素之後要做平衡? 以什麼樣的形式平衡最省時間? 如果插入元素的順序不一樣,會得到同樣的樹嗎? ...


歡迎關註我的公眾號“彤哥讀源碼”,查看更多源碼系列文章, 與彤哥一起暢游源碼的海洋。


插入元素

插入元素,如果元素在樹中存在,則替換value;如果元素不存在,則插入到對應的位置,再平衡樹。

public V put(K key, V value) {
    Entry<K,V> t = root;
    if (t == null) {
        // 如果沒有根節點,直接插入到根節點
        compare(key, key); // type (and possibly null) check
        root = new Entry<>(key, value, null);
        size = 1;
        modCount++;
        return null;
    }
    // key比較的結果
    int cmp;
    // 用來尋找待插入節點的父節點
    Entry<K,V> parent;
    // 根據是否有comparator使用不同的分支
    Comparator<? super K> cpr = comparator;
    if (cpr != null) {
        // 如果使用的是comparator方式,key值可以為null,只要在comparator.compare()中允許即可
        // 從根節點開始遍歷尋找
        do {
            parent = t;
            cmp = cpr.compare(key, t.key);
            if (cmp < 0)
                // 如果小於0從左子樹尋找
                t = t.left;
            else if (cmp > 0)
                // 如果大於0從右子樹尋找
                t = t.right;
            else
                // 如果等於0,說明插入的節點已經存在了,直接更換其value值並返回舊值
                return t.setValue(value);
        } while (t != null);
    }
    else {
        // 如果使用的是Comparable方式,key不能為null
        if (key == null)
            throw new NullPointerException();
        @SuppressWarnings("unchecked")
        Comparable<? super K> k = (Comparable<? super K>) key;
        // 從根節點開始遍歷尋找
        do {
            parent = t;
            cmp = k.compareTo(t.key);
            if (cmp < 0)
                // 如果小於0從左子樹尋找
                t = t.left;
            else if (cmp > 0)
                // 如果大於0從右子樹尋找
                t = t.right;
            else
                // 如果等於0,說明插入的節點已經存在了,直接更換其value值並返回舊值
                return t.setValue(value);
        } while (t != null);
    }
    // 如果沒找到,那麼新建一個節點,並插入到樹中
    Entry<K,V> e = new Entry<>(key, value, parent);
    if (cmp < 0)
        // 如果小於0插入到左子節點
        parent.left = e;
    else
        // 如果大於0插入到右子節點
        parent.right = e;

    // 插入之後的平衡
    fixAfterInsertion(e);
    // 元素個數加1(不需要擴容)
    size++;
    // 修改次數加1
    modCount++;
    // 如果插入了新節點返回空
    return null;
}

插入再平衡

插入的元素預設都是紅色,因為插入紅色元素只違背了第4條特性,那麼我們只要根據這個特性來平衡就容易多了。

根據不同的情況有以下幾種處理方式:

  1. 插入的元素如果是根節點,則直接塗成黑色即可,不用平衡;

  2. 插入的元素的父節點如果為黑色,不需要平衡;

  3. 插入的元素的父節點如果為紅色,則違背了特性4,需要平衡,平衡時又分成下麵三種情況:

(如果父節點是祖父節點的左節點)

情況 策略
1)父節點為紅色,叔叔節點也為紅色 (1)將父節點設為黑色;
(2)將叔叔節點設為黑色;
(3)將祖父節點設為紅色;
(4)將祖父節點設為新的當前節點,進入下一次迴圈判斷;
2)父節點為紅色,叔叔節點為黑色,且當前節點是其父節點的右節點 (1)將父節點作為新的當前節點;
(2)以新當節點為支點進行左旋,進入情況3);
3)父節點為紅色,叔叔節點為黑色,且當前節點是其父節點的左節點 (1)將父節點設為黑色;
(2)將祖父節點設為紅色;
(3)以祖父節點為支點進行右旋,進入下一次迴圈判斷;

(如果父節點是祖父節點的右節點,則正好與上面反過來)

情況 策略
1)父節點為紅色,叔叔節點也為紅色 (1)將父節點設為黑色;
(2)將叔叔節點設為黑色;
(3)將祖父節點設為紅色;
(4)將祖父節點設為新的當前節點,進入下一次迴圈判斷;
2)父節點為紅色,叔叔節點為黑色,且當前節點是其父節點的左節點 (1)將父節點作為新的當前節點;
(2)以新當節點為支點進行右旋;
3)父節點為紅色,叔叔節點為黑色,且當前節點是其父節點的右節點 (1)將父節點設為黑色;
(2)將祖父節點設為紅色;
(3)以祖父節點為支點進行左旋,進入下一次迴圈判斷;

讓我們來看看TreeMap中的實現:

/**
 * 插入再平衡
 *(1)每個節點或者是黑色,或者是紅色。
 *(2)根節點是黑色。
 *(3)每個葉子節點(NIL)是黑色。(註意:這裡葉子節點,是指為空(NIL或NULL)的葉子節點!)
 *(4)如果一個節點是紅色的,則它的子節點必須是黑色的。
 *(5)從一個節點到該節點的子孫節點的所有路徑上包含相同數目的黑節點。
 */
private void fixAfterInsertion(Entry<K,V> x) {
    // 插入的節點為紅節點,x為當前節點
    x.color = RED;

    // 只有當插入節點不是根節點且其父節點為紅色時才需要平衡(違背了特性4)
    while (x != null && x != root && x.parent.color == RED) {
        if (parentOf(x) == leftOf(parentOf(parentOf(x)))) {
            // a)如果父節點是祖父節點的左節點
            // y為叔叔節點
            Entry<K,V> y = rightOf(parentOf(parentOf(x)));
            if (colorOf(y) == RED) {
                // 情況1)如果叔叔節點為紅色
                // (1)將父節點設為黑色
                setColor(parentOf(x), BLACK);
                // (2)將叔叔節點設為黑色
                setColor(y, BLACK);
                // (3)將祖父節點設為紅色
                setColor(parentOf(parentOf(x)), RED);
                // (4)將祖父節點設為新的當前節點
                x = parentOf(parentOf(x));
            } else {
                // 如果叔叔節點為黑色
                // 情況2)如果當前節點為其父節點的右節點
                if (x == rightOf(parentOf(x))) {
                    // (1)將父節點設為當前節點
                    x = parentOf(x);
                    // (2)以新當前節點左旋
                    rotateLeft(x);
                }
                // 情況3)如果當前節點為其父節點的左節點(如果是情況2)則左旋之後新當前節點正好為其父節點的左節點了)
                // (1)將父節點設為黑色
                setColor(parentOf(x), BLACK);
                // (2)將祖父節點設為紅色
                setColor(parentOf(parentOf(x)), RED);
                // (3)以祖父節點為支點進行右旋
                rotateRight(parentOf(parentOf(x)));
            }
        } else {
            // b)如果父節點是祖父節點的右節點
            // y是叔叔節點
            Entry<K,V> y = leftOf(parentOf(parentOf(x)));
            if (colorOf(y) == RED) {
                // 情況1)如果叔叔節點為紅色
                // (1)將父節點設為黑色
                setColor(parentOf(x), BLACK);
                // (2)將叔叔節點設為黑色
                setColor(y, BLACK);
                // (3)將祖父節點設為紅色
                setColor(parentOf(parentOf(x)), RED);
                // (4)將祖父節點設為新的當前節點
                x = parentOf(parentOf(x));
            } else {
                // 如果叔叔節點為黑色
                // 情況2)如果當前節點為其父節點的左節點
                if (x == leftOf(parentOf(x))) {
                    // (1)將父節點設為當前節點
                    x = parentOf(x);
                    // (2)以新當前節點右旋
                    rotateRight(x);
                }
                // 情況3)如果當前節點為其父節點的右節點(如果是情況2)則右旋之後新當前節點正好為其父節點的右節點了)
                // (1)將父節點設為黑色
                setColor(parentOf(x), BLACK);
                // (2)將祖父節點設為紅色
                setColor(parentOf(parentOf(x)), RED);
                // (3)以祖父節點為支點進行左旋
                rotateLeft(parentOf(parentOf(x)));
            }
        }
    }
    // 平衡完成後將根節點設為黑色
    root.color = BLACK;
}

插入元素舉例

我們依次向紅黑樹中插入 4、2、3 三個元素,來一起看看整個紅黑樹平衡的過程。

三個元素都插入完成後,符合父節點是祖父節點的左節點,叔叔節點為黑色,且當前節點是其父節點的右節點,即情況2)。

1

情況2)需要做以下兩步處理:

(1)將父節點作為新的當前節點;

(2)以新當節點為支點進行左旋,進入情況3);

2

情況3)需要做以下三步處理:

(1)將父節點設為黑色;

(2)將祖父節點設為紅色;

(3)以祖父節點為支點進行右旋,進入下一次迴圈判斷;

3

下一次迴圈不符合父節點為紅色了,退出迴圈,插入再平衡完成。


未完待續,下一節我們一起探討紅黑樹刪除元素的操作。

現在公眾號文章沒辦法留言了,如果有什麼疑問或者建議請直接在公眾號給我留言。


歡迎關註我的公眾號“彤哥讀源碼”,查看更多源碼系列文章, 與彤哥一起暢游源碼的海洋。

qrcode


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • Python基礎數據類型之int,str,bool及常用方法 ...
  • 回到上次編輯位置 Ctrl + Alt + <- (向後) Ctrl + Alt + -> (向前) 這個快捷鍵有時和電腦桌面快捷鍵衝突。解決辦法: win + D 回到電腦桌面,右鍵—>圖像選項—>快捷鍵—>禁用 ...
  • 死磕 java集合之TreeMap源碼分析(三) 紅黑樹刪除元素的時間複雜度如何? 為什麼刪除元素之後要做平衡? 以什麼樣的形式平衡最省時間? ...
  • 鎖的種類 獨享鎖 VS 共用鎖 獨享鎖:鎖只能被一個線程持有(synchronized) 共用鎖:鎖可以被多個程式所持有(讀寫鎖) 樂觀鎖 VS 悲觀鎖 樂觀鎖:每次去拿數據的時候都樂觀地認為別人不會修改,所以不進行加鎖操作。樂觀鎖適用於多讀的應用類型。(CAS,Atomic) CAS(Compar ...
  • 介紹 本文介紹如何在 CentOS 7(6/6.5)、 Fedora、RHEL 上安裝 Java。Java是一個流行的軟體平臺,允許您運行Java應用程式。 本文涵蓋了以下Java版本的安裝: OpenJDK 8 Oracle Java 8 先決條件 在開始之前,您應該有一個能夠執行 root 許可權 ...
  • [Question]問題描述: 單獨的ListView列表能自動垂直滾動,但當將ListView嵌套在ScrollView後,會和ScrollView的滾動滑塊衝突,造成ListView滑塊顯示不完整。 ...
  • 恢復內容開始 運用jieba庫分詞 一、jieba庫基本介紹 1、jieba庫概述 jieba是優秀的中文分詞第三方庫 - 中文文本需要通過分詞獲得單個的詞語 - jieba是優秀的中文分詞第三方庫,需要額外安裝 - jieba庫提供三種分詞模式,最簡單隻需掌握一個函數 2、jieba分詞的原理 J ...
  • 1.概念 值傳遞:方法調用時,實際傳入的是它的副本,在方法中對值的修改,不影響調用者的值。 引用傳遞:方法調用時,實際傳入的是參數的實際記憶體地址,調用者和調用方法所操作的參數都指向同一記憶體地址,所以方法中操作會影響調用者。 2.問題 ① 值傳遞傳入的值,是它的副本是什麼意思? 列印結果: 0 此處調 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...