【C++游記】AVL樹

??

楓の個人主頁

你不能改變過去,但你可以改變未來

算法/C++/數據結構/C

Hello,這里是小楓。C語言與數據結構和算法初階兩個板塊都更新完畢,我們繼續來學習C++的內容呀。C++是接近底層有比較經典的語言,因此學習起來注定枯燥無味,西游記大家都看過吧~,我希望能帶著大家一起跨過九九八十一難,降伏各類難題,學會C++,我會盡我所能,以通俗易懂、幽默風趣的方式帶給大家形象生動的知識,也希望大家遇到困難不退縮,遇到難題不放棄,學習師徒四人的精神!!!故此得名【C++游記

?話不多說,讓我們一起進入今天的學習吧~~~??

目錄

一、AVL 樹的概念

為什么 AVL 樹高度差不要求為 0?

二、AVL 樹的實現

2.1 AVL 樹的結構

2.2 AVL 樹的插入

2.2.1 插入過程概述

2.2.2 平衡因子更新規則

2.2.3 插入及平衡因子更新代碼實現

2.3 旋轉操作

2.3.1 旋轉原則

2.3.2 右單旋

2.3.3 左單旋

2.3.4 左右雙旋

2.3.5 右左雙旋

2.4 AVL 樹的查找

2.5 AVL 樹平衡檢測

?三、結語


一、AVL 樹的概念

AVL 樹是最先發明的自平衡二叉查找樹,它要么是一顆空樹,要么具備下列性質的二叉搜索樹:左右子樹都是 AVL 樹,且左右子樹的高度差的絕對值不超過 1。AVL 樹是一種高度平衡的搜索二叉樹,通過控制高度差來維持平衡。

AVL 樹得名于它的發明者 G. M. Adelson-Velsky 和 E. M. Landis(兩位前蘇聯科學家),他們在 1962 年的論文《An algorithm for the organization of information》中發表了這一數據結構。

在 AVL 樹實現中,我們引入平衡因子 (balance factor) 的概念。每個結點都有一個平衡因子,其值等于右子樹的高度減去左子樹的高度,因此任何結點的平衡因子只能是 0、1 或 - 1。平衡因子就像一個風向標,能方便我們觀察和控制樹是否平衡。

為什么 AVL 樹高度差不要求為 0?

可能有人會疑惑,為什么 AVL 樹要求高度差不超過 1 而不是 0 呢?0 不是更平衡嗎?

其實并非不想這樣設計,而是在某些情況下無法做到高度差為 0。例如,當樹有 2 個結點、4 個結點等情況時,高度差最好就是 1,無法做到高度差為 0。

AVL 樹的整體結點數量和分布與完全二叉樹類似,高度可以控制在logN,因此增刪查改的效率也可以控制在O(logN),相比普通二叉搜索樹有了本質的提升。

二、AVL 樹的實現

2.1 AVL 樹的結構

AVL 樹的結點結構需要包含鍵值對、左右孩子指針、父節點指針以及平衡因子。具體定義如下:

template<class K, class V>
struct AVLTreeNode
{// 需要parent指針,后續更新平衡因子會用到pair<K, V> _kv;AVLTreeNode<K, V>* _left;AVLTreeNode<K, V>* _right;AVLTreeNode<K, V>* _parent;int _bf; // balance factorAVLTreeNode(const pair<K, V>& kv):_kv(kv), _left(nullptr), _right(nullptr), _parent(nullptr), _bf(0){}
};template<class K, class V>
class AVLTree
{typedef AVLTreeNode<K, V> Node;
public:// 各種成員函數實現...
private:Node* _root = nullptr;
};

2.2 AVL 樹的插入

2.2.1 插入過程概述

AVL 樹插入一個值的過程大致如下:

  1. 按二叉搜索樹規則插入新結點
  2. 從新增結點到根結點的路徑上更新祖先結點的平衡因子(最壞情況要更新到根,有些情況更新到中間即可停止)
  3. 若更新平衡因子過程中沒有出現問題,則插入結束
  4. 若更新過程中出現不平衡,對不平衡子樹進行旋轉處理。旋轉后會降低子樹高度,不會再影響上一層,插入結束
2.2.2 平衡因子更新規則

更新原則

  • 平衡因子 = 右子樹高度 - 左子樹高度
  • 只有子樹高度變化才會影響當前結點平衡因子
  • 若新增結點在 parent 的右子樹,parent 的平衡因子 ++;若在左子樹,parent 的平衡因子 --
  • parent 所在子樹的高度是否變化決定了是否繼續往上更新

更新停止條件

  1. 若更新后 parent 的平衡因子等于 0(變化為 - 1->0 或 1->0):說明更新前 parent 子樹一邊高一邊低,新增結點插入在低的那邊,插入后 parent 所在子樹高度不變,不會影響 parent 的父親結點的平衡因子,更新結束
  2. 若更新后 parent 的平衡因子等于 1 或 - 1(變化為 0->1 或 0->-1):說明更新前 parent 子樹兩邊一樣高,插入后一邊高一邊低,子樹符合平衡要求但高度增加了 1,會影響 parent 的父親結點的平衡因子,需要繼續向上更新
  3. 若更新后 parent 的平衡因子等于 2 或 - 2(變化為 1->2 或 - 1->-2):說明更新前 parent 子樹一邊高一邊低,新增結點插入在高的那邊,破壞了平衡,需要旋轉處理。旋轉后子樹高度恢復,不需要繼續往上更新
  4. 更新到根結點,若根的平衡因子是 1 或 - 1 也停止
2.2.3 插入及平衡因子更新代碼實現
bool Insert(const pair<K, V>& kv)
{if (_root == nullptr){_root = new Node(kv);return true;}Node* parent = nullptr;Node* cur = _root;while (cur){if (cur->_kv.first < kv.first){parent = cur;cur = cur->_right;}else if (cur->_kv.first > kv.first){parent = cur;cur = cur->_left;}else{return false; // 鍵已存在,插入失敗}}// 插入新結點cur = new Node(kv);if (parent->_kv.first < kv.first){parent->_right = cur;}else{parent->_left = cur;}cur->_parent = parent;// 更新平衡因子while (parent){// 更新當前parent的平衡因子if (cur == parent->_left)parent->_bf--;elseparent->_bf++;// 根據平衡因子判斷后續操作if (parent->_bf == 0){// 平衡因子為0,子樹高度不變,更新結束break;}else if (parent->_bf == 1 || parent->_bf == -1){// 平衡因子為1或-1,子樹高度增加,繼續向上更新cur = parent;parent = parent->_parent;}else if (parent->_bf == 2 || parent->_bf == -2){// 平衡因子為2或-2,子樹不平衡,需要旋轉處理break;}else{// 平衡因子異常,程序出錯assert(false);}}// 若需要旋轉處理if (parent){if (parent->_bf == 2){if (parent->_right->_bf == 1){// 右右情況,左單旋RotateL(parent);}else if (parent->_right->_bf == -1){// 右左情況,右左雙旋RotateRL(parent);}else{assert(false);}}else if (parent->_bf == -2){if (parent->_left->_bf == -1){// 左左情況,右單旋RotateR(parent);}else if (parent->_left->_bf == 1){// 左右情況,左右雙旋RotateLR(parent);}else{assert(false);}}}return true;
}

2.3 旋轉操作

2.3.1 旋轉原則

旋轉操作需要遵循兩個原則:

  1. 保持搜索樹的規則(即旋轉后仍滿足二叉搜索樹的性質)
  2. 讓旋轉的樹從不平衡變為平衡,同時降低旋轉樹的高度

旋轉總共分為四種:左單旋、右單旋、左右雙旋、右左雙旋。

2.3.2 右單旋

右單旋適用于 "左左" 情況,即 parent 的平衡因子為 - 2,且其左孩子的平衡因子為 - 1。

旋轉核心步驟

  1. 將 parent 的左孩子(subL)的右子樹(subLR)變為 parent 的左子樹
  2. 將 parent 變為 subL 的右子樹
  3. 更新相關結點的父指針
  4. 若 parent 是根結點,則更新根指針為 subL;否則將 subL 與 parent 的父結點連接
  5. 重置 parent 和 subL 的平衡因子為 0

右單旋代碼實現

void RotateR(Node* parent)
{Node* subL = parent->_left;Node* subLR = subL->_right;// 將subL的右子樹變為parent的左子樹parent->_left = subLR;if (subLR)subLR->_parent = parent;// 保存parent的父節點Node* parentParent = parent->_parent;// 將parent變為subL的右子樹subL->_right = parent;parent->_parent = subL;// 處理與parent的父節點的連接if (parentParent == nullptr){// parent是根節點_root = subL;subL->_parent = nullptr;}else{if (parent == parentParent->_left)parentParent->_left = subL;elseparentParent->_right = subL;subL->_parent = parentParent;}// 重置平衡因子parent->_bf = subL->_bf = 0;
}
2.3.3 左單旋

左單旋適用于 "右右" 情況,即 parent 的平衡因子為 2,且其右孩子的平衡因子為 1。

旋轉核心步驟

  1. 將 parent 的右孩子(subR)的左子樹(subRL)變為 parent 的右子樹
  2. 將 parent 變為 subR 的左子樹
  3. 更新相關結點的父指針
  4. 若 parent 是根結點,則更新根指針為 subR;否則將 subR 與 parent 的父結點連接
  5. 重置 parent 和 subR 的平衡因子為 0

左單旋代碼實現

void RotateL(Node* parent)
{Node* subR = parent->_right;Node* subRL = subR->_left;// 將subR的左子樹變為parent的右子樹parent->_right = subRL;if (subRL)subRL->_parent = parent;// 保存parent的父節點Node* parentParent = parent->_parent;// 將parent變為subR的左子樹subR->_left = parent;parent->_parent = subR;// 處理與parent的父節點的連接if (parentParent == nullptr){// parent是根節點_root = subR;subR->_parent = nullptr;}else{if (parent == parentParent->_left)parentParent->_left = subR;elseparentParent->_right = subR;subR->_parent = parentParent;}// 重置平衡因子parent->_bf = subR->_bf = 0;
}
2.3.4 左右雙旋

左右雙旋適用于 "左右" 情況,即 parent 的平衡因子為 - 2,且其左孩子的平衡因子為 1。

旋轉核心步驟

  1. 先以 parent 的左孩子(subL)為旋轉點進行左單旋
  2. 再以 parent 為旋轉點進行右單旋
  3. 根據 subL 的右孩子(subLR)的平衡因子,調整相關結點的平衡因子

左右雙旋代碼實現

void RotateLR(Node* parent)
{Node* subL = parent->_left;Node* subLR = subL->_right;int bf = subLR->_bf; // 保存subLR的平衡因子,用于后續調整// 先對subL進行左單旋RotateL(parent->_left);// 再對parent進行右單旋RotateR(parent);// 根據subLR的平衡因子調整各節點的平衡因子if (bf == 0){subL->_bf = 0;subLR->_bf = 0;parent->_bf = 0;}else if (bf == -1){subL->_bf = 0;subLR->_bf = 0;parent->_bf = 1;}else if (bf == 1){subL->_bf = -1;subLR->_bf = 0;parent->_bf = 0;}else{assert(false); // 平衡因子異常}
}
2.3.5 右左雙旋

右左雙旋適用于 "右左" 情況,即 parent 的平衡因子為 2,且其右孩子的平衡因子為 - 1。

旋轉核心步驟

  1. 先以 parent 的右孩子(subR)為旋轉點進行右單旋
  2. 再以 parent 為旋轉點進行左單旋
  3. 根據 subR 的左孩子(subRL)的平衡因子,調整相關結點的平衡因子

右左雙旋代碼實現

void RotateRL(Node* parent)
{Node* subR = parent->_right;Node* subRL = subR->_left;int bf = subRL->_bf; // 保存subRL的平衡因子,用于后續調整// 先對subR進行右單旋RotateR(parent->_right);// 再對parent進行左單旋RotateL(parent);// 根據subRL的平衡因子調整各節點的平衡因子if (bf == 0){subR->_bf = 0;subRL->_bf = 0;parent->_bf = 0;}else if (bf == 1){subR->_bf = 0;subRL->_bf = 0;parent->_bf = -1;}else if (bf == -1){subR->_bf = 1;subRL->_bf = 0;parent->_bf = 0;}else{assert(false); // 平衡因子異常}
}

2.4 AVL 樹的查找

AVL 樹的查找操作與普通二叉搜索樹的查找邏輯相同,由于 AVL 樹的高度平衡特性,查找效率為O(logN)。

查找代碼實現

Node* Find(const K& key)
{Node* cur = _root;while (cur){if (cur->_kv.first < key){cur = cur->_right;}else if (cur->_kv.first > key){cur = cur->_left;}else{// 找到對應鍵,返回節點return cur;}}// 未找到return nullptr;
}

2.5 AVL 樹平衡檢測

為了驗證實現的 AVL 樹是否合格,我們可以通過檢查左右子樹高度差以及結點的平衡因子來進行驗證。

平衡檢測相關代碼

// 計算樹的高度
int _Height(Node* root)
{if (root == nullptr)return 0;int leftHeight = _Height(root->_left);int rightHeight = _Height(root->_right);return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;
}// 檢測是否為平衡樹
bool _IsBalanceTree(Node* root)
{// 空樹也是AVL樹if (nullptr == root)return true;// 計算當前節點的平衡因子(右子樹高度 - 左子樹高度)int leftHeight = _Height(root->_left);int rightHeight = _Height(root->_right);int diff = rightHeight - leftHeight;// 檢查平衡因子是否符合要求if (abs(diff) >= 2){cout << root->_kv.first << "高度差異常" << endl;return false;}// 檢查記錄的平衡因子是否與計算值一致if (root->_bf != diff){cout << root->_kv.first << "平衡因子異常" << endl;return false;}// 遞歸檢查左右子樹return _IsBalanceTree(root->_left) && _IsBalanceTree(root->_right);
}

測試代碼

void TestAVLTree1()
{
AVLTree<int, int> t;
// 常規的測試?例
//int a[] = { 16, 3, 7, 11, 9, 26, 18, 14, 15 };
// 特殊的帶有雙旋場景的測試?例
int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 };
for (auto e : a)
{
t.Insert({ e, e });
}
t.InOrder();
cout << t.IsBalanceTree() << endl;
}

?三、結語

今日C++到這里就結束啦,如果覺得文章還不錯的話,可以三連支持一下。感興趣的寶子們歡迎持續訂閱小楓,小楓在這里謝謝寶子們啦~小楓の主頁還有更多生動有趣的文章,歡迎寶子們去點評鴨~C++的學習很陡,時而巨難時而巨簡單,希望寶子們和小楓一起堅持下去~你們的三連就是小楓的動力,感謝支持~

?

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/920853.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/920853.shtml
英文地址,請注明出處:http://en.pswp.cn/news/920853.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

音視頻學習(六十二):H264中的SEI

什么是SEI? 在 H.264 視頻編碼標準中&#xff0c;補充增強信息&#xff08;Supplemental Enhancement Information&#xff0c;SEI&#xff09; 是一種特殊的 NAL&#xff08;網絡抽象層&#xff09;單元。它不像序列參數集&#xff08;SPS&#xff09;或圖像參數集&#xff0…

docker run 后報錯/bin/bash: /bin/bash: cannot execute binary file總結

以下方法來源于AI&#xff0c;個人僅驗證了第三條便成功執行 1. 鏡像與宿主機架構不匹配 比如&#xff1a; 你是 x86_64 的機器&#xff0c;但鏡像是 ARM64 的&#xff08;或反之&#xff09;。在 PC 上拉了樹莓派用的鏡像。查看鏡像架構 docker inspect <image_name> | …

【Redisson 加鎖源碼解析】

Redisson 源碼解析 —— 分布式鎖實現過程 在分布式系統中&#xff0c;分布式鎖 是非常常見的需求&#xff0c;用來保證多個節點之間的互斥操作。Redisson 是 Redis 的一個 Java 客戶端&#xff0c;它提供了對分布式鎖的良好封裝。本文將從源碼角度剖析 Redisson 的分布式鎖實現…

uni-app支持單多選、搜索、查詢、限制能否點擊組件

<template><view class="multi-select-container" :class="{ single-select: !multiple, no-search: !searchable }"><!-- 當組件被禁用時,直接顯示選中的內容 --><view class="disabled-display" v-if="disabled &a…

TFT屏幕:STM32硬件SPI+DMA+隊列自動傳輸

看了網上的很多的SPIDMA的代碼&#xff0c;感覺都有一些缺陷&#xff0c;就是基本都是需要有手動等待DMA完成的這個操作&#xff0c;我感覺這種等待操作在很大程度上浪費了時間&#xff0c;那么我加入的“隊列”就是一種將等待時間利用起來的方法。原本的SPIDMA的操作邏輯如下圖…

AI操作系統語言模型設計 之1 基于意識的Face-Gate-Window的共軛路徑的思維-認知-情感嵌套模型

摘要&#xff08;AI生成&#xff09;本文提出了一種創新的AI操作系統語言模型設計框架&#xff0c;將人類意識活動的分層結構映射到人工智能系統中。該模型包含三個嵌套層次&#xff1a;理性思維層&#xff08;Face層&#xff09;&#xff1a;采用雙面膠隱喻&#xff08;A/B面&…

瘋狂星期四文案網第57天運營日記

網站運營第57天&#xff0c;點擊觀站&#xff1a; 瘋狂星期四 crazy-thursday.com 全網最全的瘋狂星期四文案網站 運營報告 今日訪問量 今日搜索引擎收錄情況

SQLark:一款面向信創應用開發者的數據庫開發和管理工具

SQLark 是一款面向信創應用開發者的數據庫開發和管理工具&#xff0c;用于快速查詢、創建和管理不同類型的數據庫系統&#xff0c;現已支持達夢、Oracle、MySQL、PostgreSQL 數據庫。 SQLark 提供了對多種數據庫的連接支持&#xff0c;實現跨平臺數據庫管理的無縫切換&#xff…

BigDecimal——解決Java浮點數值精度問題:快速入門與使用

在Java開發中&#xff0c;涉及金額計算、科學計數或需要高精度數值處理時&#xff0c;你是否遇到過這樣的困惑&#xff1f;用double計算0.1加0.2&#xff0c;結果竟不是0.3&#xff1b;用float存儲商品價格&#xff0c;小數點后兩位莫名多出幾位亂碼&#xff1b;甚至在金融系統…

wpf之WrapPanel

前言 WrapPanel類似winform中的FlowLayoutPanel&#xff0c;采用流式布局。 1、Orientation 該屬性指定WrapPanel中子空間布局的方向&#xff0c;有水平和垂直方向兩種 1&#xff09;Horizontal 水平方向 子元素Button按照水平方向排列&#xff0c;如果一行排滿了自動換下一…

Woody:開源Java應用性能診斷分析工具

核心價值 Woody是一款專注于Java應用性能問題診斷的工具&#xff0c;旨在幫助開發者 定位高GC頻率問題&#xff0c;識別內存分配熱點分析CPU使用率過高的代碼路徑追蹤接口耗時瓶頸&#xff0c;定位內部操作耗時占比診斷鎖競爭問題&#xff0c;支持精準優化針對特定業務接口/請…

《山東棒球》板球比賽規則·棒球1號位

? Baseball vs Cricket 終極科普&#xff5c;規則異同發展史全解&#xff01;Hey sports babes&#xff01;別再傻傻分不清棒球?和板球&#xff01;全網最清晰雙運動對照指南來啦&#xff5e;? 棒球 Baseball&#xff5c;美式激情風暴Core Goal核心目標擊球員&#xff08;Ba…

【游戲開發】Houdini相較于Blender在游戲開發上有什么優劣勢?我該怎么選擇開發工具?

在游戲開發中&#xff0c;Houdini與Blender的選擇需結合項目規模、技術需求和團隊資源綜合考量。以下是兩者的核心優劣勢對比及決策建議&#xff1a; 一、核心優劣勢對比 Houdini的優勢與局限 優勢&#xff1a;程序化內容生成的統治力 Houdini的節點系統&#xff08;如VEX語言、…

基于開源AI智能名片鏈動2+1模式S2B2C商城小程序的用戶活躍度提升與價值挖掘策略研究

摘要&#xff1a;本文聚焦于在開源AI智能名片鏈動21模式S2B2C商城小程序環境下&#xff0c;探討如何提高用戶活躍度并挖掘用戶價值。在用戶留存的基礎上&#xff0c;通過分析該特定模式與小程序的特點&#xff0c;提出一系列針對性的策略&#xff0c;旨在借助開源AI智能名片以及…

《投資-41》- 自然=》生物=》人類社會=》商業=》金融=》股市=》投資,其層層疊加構建中內在的相似的規律和規則

從自然到投資的層層遞進中&#xff0c;盡管各領域看似差異巨大&#xff0c;但內在遵循著相似的規律和規則。這些規律體現了“底層邏輯的普適性”&#xff0c;即不同系統在動態平衡、資源分配、信息傳遞和反饋調節等方面具有共性。以下是關鍵規律的解析&#xff1a;1. 能量流動與…

VSCode中調試python腳本

VSCode中安裝以下插件 ms-python.python&#xff1a;python調試ms-python.vscode-pylance&#xff1a;代碼跳轉&#xff08;非必要&#xff09; 配置launch.json 在當前工作區&#xff0c;按此路徑.vscode\launch.json新建launch.json文件&#xff0c;并配置以下參數&#x…

動作指令活體檢測通過動態交互驗證真實活人,保障安全

在當今社會&#xff0c;人臉識別技術已深入日常生活的方方面面&#xff0c;從手機解鎖、移動支付到遠程開戶、門禁考勤&#xff0c;人臉識別技術已無處不在。然而&#xff0c;這項技術也面臨著嚴峻的安全挑戰&#xff1a;打印照片、播放視頻、制作3D面具等簡單的“欺騙手段”都…

KingbaseES數據庫:開發基礎教程,從部署到安全的全方位實踐

KingbaseES數據庫&#xff1a;開發基礎教程&#xff0c;從部署到安全的全方位實踐 KingbaseES數據庫&#xff1a;開發基礎教程&#xff0c;從部署到安全的全方位實踐&#xff0c;本文圍繞 KingbaseES 數據庫開發核心基礎展開。先介紹三種部署模式&#xff0c;即單機、雙機熱備、…

安裝nodejs安裝node.js安裝教程(Windows Linux)

文章目錄Linux**一、下載 Node.js**1. **訪問官網**&#xff1a;2. **選擇版本**&#xff1a;**二、安裝 Node.js****方法 1&#xff1a;使用包管理器&#xff08;推薦&#xff09;****Ubuntu/Debian 系統**1. **更新包列表**&#xff1a;2. **安裝 Node.js**&#xff1a;3. **…

shell腳本函數介紹

1. 函數 (Functions)定義與優勢函數是可重復使用的功能模塊優勢&#xff1a;代碼復用&#xff0c;直接調用解決問題分類內置函數&#xff1a;編程語言自帶的函數&#xff08;如 print&#xff09;自定義函數&#xff1a;程序員自己編寫的函數定義語法# 方式一 function 函數名(…