【數據結構】二叉樹、堆

文章目錄

  • 二叉樹的概念及結構
    • 定義
    • 特殊的二叉樹
    • 核心性質
    • 存儲方式
  • 二叉樹的鏈式存儲
    • 前序遍歷
    • 中序遍歷
    • 后序遍歷
    • 層序遍歷
  • 二叉樹的順序存儲
    • 父子關系的推導
    • 堆(heap)
      • 堆的概念
      • 向上調整算法和向下調整算法
        • 向上調整算法
        • 向下調整算法
      • 堆的創建
      • 堆的插入
      • 堆的刪除
    • 堆的應用
      • 堆排序
      • TOP-K 問題

二叉樹的概念及結構

定義

二叉樹是每個節點最多有兩個子節點的樹結構,分別稱為左子節點右子節點。子樹區分左右順序,即使僅有一個子節點也需明確方向。二叉樹可以為空(無節點)。

特殊的二叉樹

  • 滿二叉樹:所有非葉子節點均有左右子節點,且所有葉子在同一層。
  • 完全二叉樹:除最后一層外,其他層節點全滿,最后一層從左到右連續填充。
  • 平衡二叉樹:任意節點左右子樹高度差不超過1(如AVL樹)。
  • 二叉搜索樹(BST):左子樹節點均小于根,右子樹節點均大于根。

核心性質

  1. 節點關系:
    • 每個節點最多有兩個子節點
    • 若樹的高度為 h,則最大節點數為 2h - 1(即滿二叉樹的情況)
  2. 層次與深度:
    • 根節點位于第一層 (或第零層,依定義不同)
    • 根節點的深度為從根節點到該節點的路徑長度

存儲方式

  1. 鏈式存儲:
    • 節點包含數據、左指針和右指針。
    • 靈活,適合動態增刪。
  2. 順序存儲(數組):
    • 適用于完全二叉樹,父節點下標 i 的左子節點為 2i,右子節點為 2i + 1
    • 非完全二叉樹可能浪費存儲空間。

二叉樹的鏈式存儲

根據定義,我們很容易就知道二叉樹節點分為一個數據域和兩個分別指向左右子樹的指針域,于是就有:

typedef int BTDataType;
typedef struct BinaryTreeNode
{BTDataType data;struct BinaryTreeNode* left;struct BinaryTreeNode* right;
}TreeNode;

對于鏈式結構的二叉樹,我們主要得掌握它的三種深度優先遍歷和一種層序遍歷的方式
對于三種深度優先遍歷,它這個前、中、后指的是根節點的前、中、后。

  1. 前序遍歷(Preorder Traversal 亦稱先序遍歷)——訪問根節點的操作發生在遍歷其左右子樹之前。
  2. 中序遍歷(Inorder Traversal)——訪問根節點的操作發生在遍歷其左右子樹之中(間)。
  3. 后序遍歷(Postorder Traversal)——訪問根節點的操作發生在遍歷其左右子樹之后。

前序遍歷

因為是遞歸調用,所以代碼極其簡單

void PrevOrder(TreeNode* root)
{if (root == NULL){printf("N\n");return;}printf("%d ", root->data); // 訪問根節點PrevOrder(root->left);	   // 訪問左子樹PrevOrder(root->right);	   // 訪問右子樹
}

中序遍歷

void InOrder(TreeNode* root)
{if (root == NULL){printf("N\n");return;}InOrder(root->left);		// 訪問左子樹printf("%d ", root->data);	// 訪問根節點InOrder(root->right);		// 訪問右子樹
}

后序遍歷

void PostOrder(TreeNode* root)
{if (root == NULL){printf("N\n");return;}PostOrder(root->left);		// 訪問左子樹PostOrder(root->right);		// 訪問右子樹printf("%d ", root->data);	// 訪問根節點
}

從代碼中,我們不難看出,這三種遍歷方式僅是訪問根節點的時機不同。

層序遍歷

對于層序遍歷,也就是字面意思,我們需要按層逐層訪問節點。
那么如何做到呢?
這就得使用一個隊列去保存樹中的節點。
首先讓根節點入隊列,當隊列不為空的時候,取出隊列中隊頭節點,訪問該節點的元素。然后判斷該節點是否存在左右節點,存在則入隊列。重復這個過程直到隊列為空

vector<int> breadthFirstTraversal(TreeNode* root) {vector<int> result;		   // 用于存放層序遍歷的結果if (!root) return result;  // 處理空樹情況queue<TreeNode*> q;		   // 創建隊列q.push(root);			   // 根節點入隊列while (!q.empty()) {	   // 直到隊列為空TreeNode* current = q.front();	// 取隊頭節點q.pop();						result.push_back(current->val);	// 將該節點的元素放入數組中if (current->left) q.push(current->left);	// 存在左節點就入隊列if (current->right) q.push(current->right);	// 存在右節點就入隊列}return result;
}

注釋寫的已經很清楚了,應該能看懂吧。
考慮到可能有些同學剛接觸二叉樹,可能看不懂這代碼。沒事,先有個印象就行了,等到后面對于數據結構的理解加深自然就理解了。

二叉樹的順序存儲

普通的二叉樹并不適合使用順序存儲,因為這可能會導致大量的空間浪費。在使用順序存儲時,一定是完全二叉樹。
父子下標關系本質上是完全二叉樹層序遍歷在數組中的直接映射:

  • 左子下標 = 父下標 x 2(根從1開始) 或 父下標 x 2 + 1(根從0開始)
  • 右子下標 = 左子下標 + 1

父子關系的推導

因為數組是從 0 下標開始的,所以這里就以根節點從 0 開始推導

  1. 完全二叉樹的層次遍歷特性
    完全二叉樹的節點按層次遍歷順序連續存儲在數組中,且滿足以下性質:
  • 第 k 層的節點數:2k
  • 第 k 層的起始下標:2k - 1
  • 第 k 層的第 m 個節點的下標:2k - 1 + m
  1. 父子節點位置關系
  • 父節點在第 k 層第 m 個位置:
    • 下標:i = 2k - 1 + m
  • 左子節點在第 k+1 層的第 2m 個位置:
    • 下標:left_child = 2k+1 - 1 + 2m
    • 代入 i = 2 k ? 1 + m i=2^k-1+m i=2k?1+m,化簡得:
      left_child = 2i + 1
  • 右子節點為左子節點下標 + 1
    • right_child = 2i + 2

推導就到這里,數學好點的同學可以使用數學歸納法來證明一下。markdown的語法不太好寫,這里就不再證明了

堆(heap)

說起二叉樹的順序存儲就不得不提堆了。如果你存的數據都是些非常雜亂的,且你對它并沒有做出些什么修正,那你用二叉樹干嘛?搞得這么花里胡哨,不如直接使用數組。而堆就不一樣了。

堆的概念

堆(Heap)是一種特殊的完全二叉樹,具有以下核心性質,可分為 最大堆最小堆 兩種類型:

性質:

  1. 結構性:完全二叉樹
  • 堆在邏輯上是完全二叉樹,所有層(除最后一層)節點全滿,最后一層節點從左到右連續填充。
  • 順序存儲實現:通常用數組存儲,利用下標關系快速定位父子節點:

  1. 堆序性:節點值的有序性
  • 最大堆(Max-Heap):
    • 每個節點的值 ≥ 其子節點的值。
    • 根節點是樹中的最大值。
  • 最小堆(Min-Heap):
    • 每個節點的值 ≤ 其子節點的值。
    • 根節點是樹中的最小值。

  1. 核心操作與性質維護
    堆通過以下操作維護其性質:
    1. 插入(Insert):
      • 新元素插入末尾,通過上浮(Percolate Up)調整位置。
      • 時間復雜度:O(logN)
    2. 刪除堆頂(Extract-Max/Min):
      • 移除堆頂元素,將末尾元素移至堆頂,通過下沉(Percolate Down)調整位置。
      • 時間復雜度:O(logN)

向上調整算法和向下調整算法

在建堆之前,我們需要理解向上調整和向下調整算法。
以小堆為例

向上調整算法

向上調整算法,聽名字就知道是向上比較,那么就是孩子節點(我們選擇的節點)與父節點的比較。
使用場景:插入新元素后,將其從堆尾逐步向上調整到合適位置

void AdjustUp(HDataType* a, int child)
{int parent = (child - 1) / 2;	// 通過下標關系計算出父節點下標while (child > 0)				// 當子節點下標大于 0 時就繼續調整{if (a[child] < a[parent])	// 這里以小堆為例,所以子小于父的時候交換兩節點數據,將小的元素往上調{Swap(&a[parent], &a[child]);child = parent;parent = (child - 1) / 2;}else{break; // 到達合適位置的時候跳出循環}}
}
向下調整算法

向下調整需要一直調整到葉子節點

void AdjustDown(HDataType* a, int n, int parent)
{// 假設左孩子小int child = parent * 2 + 1;while (child < n) // child >= n說明孩子不存在,調整到葉子了{// 找出小的那個孩子,確保child指向的是小的孩子節點if (child + 1 < n && a[child + 1] < a[child]){++child;}if (a[child] < a[parent])	// 孩子節點比父節點小就進行交換{Swap(&a[child], &a[parent]);parent = child;child = parent * 2 + 1;}else{break;	// 到合適的位置跳出循環}}
}

堆的創建

建堆有兩種方法:

  • 一種是不斷插入新節點(新節點肯定是葉子節點啦),插入一個節點就對其進行一次向上調整,直到建好堆。
  • 還有一種是將已知的數組視為一個堆,從最后一個非葉子節點開始進行向下調整,直到調整到根節點。

那么這兩種建堆方式推薦使用哪種呢?
推薦使用向下調整的方式建堆!

為什么呢?因為更快。
這里很明確的告訴你向上調整建堆的時間復雜度是 O(Nlog N),而向下調整建堆的時間復雜度是 O(N)。
具體的證明這里就不寫了,有興趣的同學可以自己去推導一下,這里只說明個大概。

向上調整建堆,越深的節點,它要調整的次數越多(根節點到它的距離),并且越深的節點數量越多(在滿二叉樹的情況下)。
而向下調整建堆,越深的節點,它要調整的次數越少(它到葉子節點的距離),并且越深的節點數量越多。
那么在二者數量相同的情況下,向上調整:
(一層里)數量多的節點要調整的次數多,(一層里)數量少的節點調整的次數少。
向下調整:
(一層里)數量多的節點要調整的次數少,(一層里)數量少的節點調整的次數多。
很明顯向下調整的次數更少,并且向下調整本身要調整的節點數量就小于向上調整要調整的節點數量,很明顯向下調整建堆更快。

堆的插入

插入位置是在葉子節點,直接將其向上調整

void HeapPush(Heap* ph, HDataType x)
{assert(ph);if (ph->size == ph->capacity){int newcapacity = ph->capacity == 0 ? 4 : ph->capacity * 2;HDataType* tmp = (HDataType*)realloc(ph->a, newcapacity * sizeof(HDataType));if (tmp == NULL){perror("realloc fail!\n");return;}ph->a = tmp;ph->capacity = newcapacity;}ph->a[ph->size++] = x;AdjustUp(ph->a, ph->size - 1);
}

堆的刪除

因為堆刪除只能刪除堆頂元素。
將堆頂元素和堆尾元素交換,刪除掉原先的堆頂元素,再將新的堆頂元素向下調整即可。
解釋:
以小堆為例,原先堆頂元素是整個堆中最小的,而堆尾元素肯定是大于原先的堆頂的,且會大于中間層的幾個元素,將其放到堆頂,而向下調整又是將父節點的兩個子節點中更小的那個調整上去,自然就能夠保證堆頂為新的最小元素。

void HeapPop(Heap* ph)
{assert(ph);assert(ph->size > 0);Swap(&ph->a[0], &ph->a[ph->size - 1]);ph->size--;AdjustDown(ph->a, ph->size, 0);
}

堆的應用

堆排序

很明顯,是將數組排序,那么只要對需要排序的數組進行向下調整建堆,分為兩個步驟:

  1. 建堆
    • 升序:建大堆
    • 降序:建小堆
  2. 利用堆刪除的思想來進行排序

這兩個步驟都是使用了向下調整算法,所以其實理解了向下調整算法,那么堆排序也挺簡單的。

TOP-K 問題

找出 n 個元素中,前 K 大的元素。
直接建堆再取 K 次堆頂即可。
當然,如果 n 的值非常非常大,也可以維護一個 K 大小的堆,對整個數據遍歷一遍,最后剩下來的 K 個數據便是我們所需要的答案。

我記得前幾年的藍橋杯省賽中 c++ b、c組中就有幾題需要用到這個,就自己實現一下 node 節點,再不斷地對堆頂進行操作,出堆,入堆,有興趣的話也可以自己去找來看看。

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

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

相關文章

Vue3響應式原理那些事

文章目錄 1 響應式基礎:Proxy 與 Reflect1.1 Proxy 代理攔截1.2 Reflect 確保 `this` 指向正確1.2.1 修正 `this` 指向問題1.2.2 統一的操作返回值1.3 與 Vue2 的對比2 依賴收集與觸發機制2.1 全局依賴存儲結構:WeakMap → Map → Set2.2 依賴收集觸發時機2.3 依賴收集核心實…

精選10個好用的WordPress免費主題

10個好用的WordPress免費主題 1. Astra Astra 是全球最受歡迎的WordPress免費主題。它功能豐富&#xff0c;易于使用&#xff0c;SEO友好&#xff0c;是第一個安裝量突破100萬的非默認主題&#xff0c;并獲得了5000多個五星好評。 它完美集成了Elementor、Beaver&#xff0c;…

【SaaS多租架構】數據隔離與性能平衡

SaaS多租戶架構:數據隔離與性能平衡 一、技術背景及發展二、技術特點:數據隔離與性能優化的雙核心三、技術細節:實現路徑與關鍵技術四、實際案例分析五、未來發展趨勢結語一、技術背景及發展 多租戶架構是云計算與SaaS(軟件即服務)模式的核心技術,其核心目標是通過共享基…

部署GM DC Monitor 一體化監控預警平臺

1&#xff09;首先在官網下載鏡像文件 廣目&#xff08;北京&#xff09;軟件有限公司廣目&#xff08;北京&#xff09;軟件有限公司https://www.gm-monitor.com/col.jsp?id1142&#xff09;其次進行部署安裝&#xff0c;教程如下&#xff1a; 1. 基礎環境要求 1) 系統&…

Webug4.0靶場通關筆記15- 第19關文件上傳(畸形文件)

目錄 第19關 文件上傳(畸形文件) 1.打開靶場 2.源碼分析 &#xff08;1&#xff09;客戶端源碼 &#xff08;2&#xff09;服務器源碼 3.滲透實戰 &#xff08;1&#xff09;構造腳本 &#xff08;2&#xff09;雙寫繞過 &#xff08;3&#xff09;訪問腳本 本文通過《…

架構思維:構建高并發讀服務_熱點數據查詢的架構設計與性能調優

文章目錄 一、引言二、熱點查詢定義與場景三、主從復制——垂直擴容四、應用內前置緩存4.1 容量上限與淘汰策略4.2 延遲刷新&#xff1a;定期 vs. 實時4.3 逃逸流量控制4.4 熱點發現&#xff1a;被動 vs. 主動 五、降級與限流兜底六、前端&#xff0f;接入層其他應對七、模擬壓…

寶塔面板運行docker的jenkins

1.在寶塔面板裝docker&#xff0c;以及jenkins 2.ip:端口訪問jenkins 3.獲取密鑰&#xff08;點擊日志&#xff09; 4.配置容器內的jdk和maven環境&#xff08;直接把jdk和maven文件夾放到jenkins容器映射的data文件下&#xff09; 點擊容器-->管理-->數據存儲卷--.把相…

C語言 ——— 函數

目錄 函數是什么 庫函數 學習使用 strcpy 庫函數 自定義函數 寫一個函數能找出兩個整數中的最大值 寫一個函數交換兩個整型變量的內容 牛刀小試 寫一個函數判斷一個整數是否是素數 寫一個函數判斷某一年是否是閏年 寫一個函數&#xff0c;實現一個整型有序數組的二分…

筆記本電腦升級計劃(2017———2025)

ThinkPad T470 (2017) vs ThinkBook 16 (2025) 完整性能對比報告 一、核心硬件性能對比 1. CPU性能對比&#xff08;i5-7200U vs Ultra9-285H&#xff09; 參數i5-7200U (2017)Ultra9-285H (2025)提升百分比核心架構2核4線程 (Skylake)16核16線程 (6P8E2LPE)700%核心數制程工…

具身系列——PPO算法實現CartPole游戲(強化學習)

完整代碼參考&#xff1a; https://gitee.com/chencib/ailib/blob/master/rl/ppo_cartpole.py 執行結果&#xff1a; 部分訓練得分&#xff1a; (sd) D:\Dev\traditional_nn\feiai\test\rl>python ppo_cartpole_v2_succeed.py Ep: 0 | Reward: 23.0 | Running: 2…

Python項目源碼60:電影院選票系統1.0(tkinter)

1.功能特點&#xff1a;通常選票系統應該允許用戶選擇電影、場次、座位&#xff0c;然后顯示總價和生成票據。好的&#xff0c;我得先規劃一下界面布局。 首先&#xff0c;應該有一個電影選擇的列表&#xff0c;可能用下拉菜單Combobox來實現。然后場次時間&#xff0c;可能用…

【全隊項目】智能學術海報生成系統PosterGenius--圖片布局生成模型LayoutPrompt(2)

&#x1f308; 個人主頁&#xff1a;十二月的貓-CSDN博客 &#x1f525; 系列專欄&#xff1a; &#x1f3c0;大模型實戰訓練營_十二月的貓的博客-CSDN博客 &#x1f4aa;&#x1f3fb; 十二月的寒冬阻擋不了春天的腳步&#xff0c;十二點的黑夜遮蔽不住黎明的曙光 目錄 1. 前…

Linux的時間同步服務器(附加詳細實驗案例)

一、計時方式的發展 1.古代計時方式? 公元前約 2000 年&#xff1a;古埃及人利用光線留下的影子計時&#xff0c;他們修建高聳的大型方尖碑&#xff0c;通過追蹤方尖碑影子的移動判斷時間&#xff0c;這是早期利用自然現象計時的典型方式 。?商朝時期&#xff1a;人們開發并…

【無需docker】mac本地部署dify

環境安裝準備 #安裝 postgresql13 brew install postgresql13 #使用zsh的在全局添加postgresql命令集 echo export PATH"/usr/local/opt/postgresql13/bin:$PATH" >> ~/.zshrc # 使得zsh的配置修改生效 source ~/.zshrc # 啟動postgresql brew services star…

(5)概述 QT 的元對象系統里的類的調用與聯系,及訪問接口

&#xff08;1&#xff09; QT 的元對象系統&#xff0c;這幾個字大家都知道&#xff0c;那么 QT 的元對象系統里都包含哪些內容呢&#xff0c;其訪問接口是如何呢&#xff1f; 從 QObject 類的實現里&#xff0c;從其數據成員里就可以看出來&#xff1a; QT 里父容器可以釋放其…

打包 Python 項目為 Windows 可執行文件:高效部署指南

Hypackpy 是一款由白月黑羽開發的 Python 項目打包工具&#xff0c;它與 PyInstaller 等傳統工具不同&#xff0c;通過直接打包解釋器環境和項目代碼&#xff0c;并允許開發者修改配置文件以排除不需要的內容&#xff0c;從而創建方便用戶一鍵運行的可執行程序。以下是使用 Hyp…

MySQL JOIN詳解:掌握數據關聯的核心技能

一、為什么需要JOIN&#xff1f; 在關系型數據庫中&#xff0c;數據通常被拆分到不同的表中以提高存儲效率。當我們需要從多個表中組合數據時&#xff0c;JOIN操作就成為了最關鍵的技能。通過本文&#xff0c;您將全面掌握MySQL中7種JOIN操作&#xff0c;并學會如何在實際場景中…

Kdump 收集器及使用方式

以下是 Linux 系統中 Kdump 轉儲收集器的詳細說明及其使用方法&#xff0c;涵蓋核心工具、配置方法及實際示例&#xff1a; 一、Kdump 收集器分類及作用 Kdump 的核心功能是通過 捕獲內核 生成內存轉儲文件&#xff08;vmcore&#xff09;&#xff0c;其核心收集器包括&#…

Error: error:0308010C:digital envelope routines::unsupported 高版本node啟動低版本項目運行報錯

我的問題就是高版本node啟動舊版本項目引起的問題&#xff0c;單獨在配置 package.json文件中配置并運行就可以&#xff0c;大概意思就是設置node的openssl "scripts": {"dev": "SET NODE_OPTIONS--openssl-legacy-provider && vue-cli-servi…

松下機器人快速入門指南(2025年更新版)

松下機器人快速入門指南&#xff08;2025年更新版&#xff09; 松下機器人以其高精度、穩定性和易用性在工業自動化領域廣泛應用。本文將從硬件配置、參數設置、手動操作、編程基礎到維護保養&#xff0c;全面講解松下機器人的快速入門方法&#xff0c;幫助新手快速掌握核心操…