數據結構——鏈式二叉樹知識點以及鏈式二叉樹數據操作函數詳解!!

引言:該博客將會詳細的講解二叉樹的三種遍歷方法:前序、中序、后序,也同時會講到關于二叉樹的數據操作函數。值得一提的是,這些函數幾乎都是建立在一個函數思想——遞歸之上的。這次的代碼其實寫起來十分簡單,用不了幾行就可以解決問題,但是其中的代碼思想和運作方式才是難點。但只要我們搞懂了思想,手撕代碼完全就沒問題了,就讓我們一起加油吧!

更多有關C語言和數據結構知識詳解可前往個人主頁:計信貓

目錄

一,二叉樹的遍歷方式

0,三序前言

1,前序

2,中序

3,后序

二,二叉樹的數據操作函數

1,二叉樹節點個數

2,二叉樹葉子節點個數

3,二叉樹高度?

4,二叉樹第k層節點個數

5,查找值為x的節點

6,二叉樹的銷毀

三,二叉樹的層序遍歷

1,? 層序遍歷

2,判斷完全二叉樹?

四,結語


一,二叉樹的遍歷方式

0,三序前言

? ? ? ? 其實我們所說的三序其實就可以分為前序、中序、后序它們分別表示對一棵二叉樹不同的遍歷方式。我們在這里先對它們的遍歷方式做一次總結:

●前序:根,左子樹,右子樹

●中序:左子樹,根,右子樹

●后序:左子樹,右子樹,根

? ? ? ? 在這里,我們以如下的二叉樹為例子進行三序的介紹:

? ? ? ? 于是我們使用如下的代碼在VS中直接手搓出上圖的二叉樹以方便我們后續對代碼的檢測

typedef int BTDataType;
//二叉樹節點
typedef struct BinaryTreeNode
{BTDataType val;struct BinaryTreeNode* left;struct BinaryTreeNode* right;
}BTNode;
//創建節點
BTNode* BuyNode(BTDataType x)
{BTNode* newnode = (BTNode*)malloc(sizeof(BTNode));newnode->val = x;newnode->left = NULL;newnode->right = NULL;return newnode;
}
//創建二叉樹
BTNode* CreatBinaryTree()
{//創建節點BTNode* node1 = BuyNode(1);BTNode* node2 = BuyNode(2);BTNode* node3 = BuyNode(3);BTNode* node4 = BuyNode(4);BTNode* node5 = BuyNode(5);BTNode* node6 = BuyNode(6);//將節點按照圖示連起來node1->left = node2;node1->right = node4;node2->left = node3;node4->left = node5;node4->right = node6;//node1為根節點return node1;
}

? ? ? ? ?并且,我們仍然使用三文件操作法,如下圖所示:

1,前序

?●前序:根,左子樹,右子樹

? ? ? ? 那么對于該二叉樹,我們應該如何遍歷呢?

?????????按照前序遍歷的要求,我們就可以將一棵二叉樹不斷地劃分為根、左子樹、右子樹

? ? ? ? 只有在我們將node1的左子樹遍歷完之后我們才可以進入右子樹node1的左子樹又可以被視作以node2,并且包含左右子樹的一棵新樹,所以我們又需要對新的樹進行前序遍歷。如此循環下去,其實遞歸的思想也就慢慢體現出來了。

? ? ? ? 所以,以上的被遍歷完之后的結果如下所示,其中N表示NULL空節點:

? ? ? ? ?所以前序遍歷的代碼如下:

//前序遍歷
void PreOrder(BTNode* root)
{if (root == NULL){printf("N ");return;}printf("%d ", root->val);PreOrder(root->left);PreOrder(root->right);
}

2,中序

●中序:左子樹,根,右子樹

? ? ? ? 現在,我們以中序遍歷法遍歷如下二叉樹:

? ? ? ? 所以,我們還是將這棵二叉樹不斷地分解為根,左子樹,右子樹三個部分。但是我們這一次就會改變順序,我們需要先遍歷完左子樹后再進行的遍歷,最后才進行右子樹的遍歷。而以node1根節點左子樹又可以繼續被細分為以上三個部分,所以我們又需要進行中序遍歷,直到某個節點的左子樹被遍歷完(為空),方可進行的遍歷

? ? ? ? 所以,以上的中序遍歷完之后的結果如下:

? ? ? ? 所以中序遍歷的代碼入下:?

//中序遍歷
void MidOrder(BTNode* root)
{if (root == NULL){printf("N ");return;}MidOrder(root->left);printf("%d ", root->val);MidOrder(root->right);
}

3,后序

●后序:左子樹,右子樹,根

? ? ? ? 如果我們使用后序遍歷法遍歷如下二叉樹,又會是怎樣一個結果呢?

? ? ? ? 所以我們仍然以之前學到的方式進行遞歸類推先遍歷完node1的左子樹,后遍歷node1的右子樹,最后在遍歷根node1。而在遍歷node1的子樹時,它的子樹又可以被視為以node2node4根節點新樹,故我們又對其再進行后序遍歷,直到遇到空節點

? ? ? ? 所以我們模擬遍歷之后的結果如下圖所示:

? ? ? ? 那么,后序遍歷的代碼入下:

//后序遍歷
void BackOrder(BTNode* root)
{if (root == NULL){printf("N ");return;}BackOrder(root->left);BackOrder(root->right);printf("%d ", root->val);
}

二,鏈式二叉樹的數據操作函數

1,二叉樹節點個數

? ? ? ? 當我們想要知道一棵二叉樹中有多少個節點的時候,我們就可以使用這個函數。

//求二叉樹中的節點個數
int TreeSize(BTNode* root);

? ? ? ? 在這個函數中,我們還是使用到了遞歸的思想。我們可以將一棵總節點個數分為一個根節點與它的左右兩子樹的節點和。然后它的子樹又可以再次使用這個方法將它的子樹拆分,直到遇到NULL遞歸結束,返回值為0。所以我們的代碼如下:

//求二叉樹中的節點個數
int TreeSize(BTNode* root)
{//遇到空,遞歸結束開始返回if (root == NULL){return 0;}//將二叉樹拆分為根和左右兩子樹之和return TreeSize(root->right) + TreeSize(root->left) + 1;
}

2,二叉樹葉子節點個數

? ? ? ? 這個函數用于求的一棵二叉樹之中的葉子節點的個數。

//求二叉樹中葉子節點的個數
int TreeLeafSize(BTNode* root)

?????????首先,我們可以確定的是,當一個節點左右兩子節點全為NULL時,那么這個節點就是葉節點而當我們想要找到一棵二叉樹中的葉節點個數時,我們就可以將總的葉節點的個數拆分為左右兩子樹的葉節點之和。然后我們不停以以上方式進行拆分,直到找到葉節點遞歸結束,返回值為1。而要注意,空樹葉節點個數為0,要特殊討論。所以按照以上的思想進行代碼實現如下:

//求二叉樹中葉子節點的個數
int TreeLeafSize(BTNode* root)
{//空樹沒有葉節點if (root == NULL){return 0;}//找到了葉節點,遞歸結束,返回1if (root->left == NULL && root->right == NULL){return 1;}//開始遞歸return TreeLeafSize(root->left) + TreeLeafSize(root->right);
}

3,二叉樹高度?

? ? ? ? 二叉樹的高度其實就是二叉樹的,也就是它的最大高度

//求二叉樹的高度
int TreeHeight(BTNode* root);

? ? ? ? 當我們想要找到一棵樹的最大高度的時候,其實我們就可以將這棵的高度拆分為左子樹與右子樹中高度的較大值加上1(其中1代表了根也占一層高度)。如此遞歸下去,當我們遞歸葉節點時,就遞歸結束,返回值為1。當然最后我們也需要注意,空樹的高度為0。所以我們的代碼如下:

//求二叉樹的高度
int TreeHeight(BTNode* root)
{//空樹高度為0if (root == NULL){return 0;}int left = TreeHeight(root->left);int right = TreeHeight(root->right);return left > right ? left + 1 : right + 1;
}

4,二叉樹第k層節點個數

? ? ? ? 該函數則專門用于求出一棵二叉樹第k層節點個數

//求二叉樹第k層的節點個數
int TreeLevelKSize(BTNode* root, int k);

? ? ? ? 對于這個函數的實現,還是請出我們的老朋友——遞歸。對于一棵二叉樹,求它第k層節點個數時,我們就可以將這個問題視為求其根節點的左右子樹的(k-1)層的節點個數之和直到k持續減一,使k==1時,說明該層就是我們的第k層,此時遞歸結束,返回值為1就可以了。?所以我們的代碼如下:

//求二叉樹第k層的節點個數
int TreeLevelKSize(BTNode* root, int k)
{//空樹則返回0if (root == NULL){return 0;}if (k == 1){return 1;}//根節點的左右子樹的(k-1)層的節點個數之和return TreeLevelKSize(root->left, k - 1) + TreeLevelKSize(root->right, k - 1);
}

5,查找值為x的節點

? ? ? ? 此函數用于查找二叉樹中值為x的節點,并返回該節點的地址。

//查找值為x的節點
BTNode* TreeFind(BTNode* root, BTDataType x);

? ? ? ? 既然我們想要找到值為x節點時,那遍歷這個二叉樹就必不可少了!所以我們就可以使用之前學到的三序遍歷之一的前序遍歷法我們先遍歷,若根節點不是x節點我們就對它的左子樹進行遍歷,最后再對右子樹遍歷。若二叉樹為空或者不存在x節點我們都返回NULL。所以我們的代碼方法如下:

//查找值為x的節點
BTNode* TreeFind(BTNode* root, BTDataType x)
{//為空樹就返回NULLif (root == NULL){return NULL;}//先遍歷根節點if (root->val == x){return root;}//再遍歷左子樹BTNode* ret1 = TreeFind(root->left, x);if (ret1)//ret1不為空,說明x存在于左子樹{return  ret1;}BTNode* ret2 = TreeFind(root->right, x);if (ret2)//ret2不為空,說明x存在于右子樹{return ret2;}//整棵二叉樹都不存在x節點,返回NULLreturn NULL;
}

6,二叉樹的銷毀

? ? ? ? 當我們要結束對二叉樹的操作時,我們就需要對二叉樹進行銷毀操作,防止內存泄漏。

//二叉樹的銷毀
void TreeDestroy(BTNode* root);

? ? ? ? 在我們對二叉樹進行遍歷銷毀時,我們就應該選擇后序遍歷,將根節點進行最后銷毀?因為一旦將根節點先行銷毀之后,那么我們就找不到根節點所對應的左右子樹了,就無法進行后續銷毀。所以代碼如下:

//二叉樹的銷毀
void TreeDestroy(BTNode* root)
{if (root == NULL)//遇到空說明遍歷結束,開始返回{return;}TreeDestroy(root->left);TreeDestroy(root->right);free(root);
}

三,鏈式二叉樹的層序遍歷

1,? 層序遍歷

? ? ? ? 二叉樹的層序遍歷屬于廣度優先遍歷(BFS),而二叉樹的層序遍歷其實就是對二叉樹進行一層一層的遍歷,完成二叉樹的層序遍歷需要用到我們之前學到的隊列的知識。

? ? ? ? 現在我們對隊列進行一定的改裝。以前我們所學到的隊列中儲存的為int類型的值,而這時候我們將int改變為二叉樹節點的指針。經過以上操作之后,那么這個隊列里所儲存的就是二叉樹節點的指針了。

typedef struct BinaryTreeNode* QDataType;
//二叉樹的層序遍歷
void TreeLevelOrder(BTNode* root);

?????????我們現在以以下的二叉樹為例子:

? ? ? ? 我們首先在隊列中插入根節點

? ? ? ? 然后我們取出根節點,并且同時在隊列中加入其子節點2和4

? ? ? ? 后我們取出節點2并且再次于隊列中加入節點2對應的子節點(空則不進入隊列)

? ? ? ? 以此類推,直到當我們取出的節點為空時,那么此時對于這個二叉樹的層序遍歷就結束了。而在遍歷的過程中,我們始終遵循一個思想,就是上層帶下層。?所以在此思想的基礎之上,我們便可以寫出層序遍歷的代碼:

//二叉樹的層序遍歷
void TreeLevelOrder(BTNode* root)
{assert(root);//創建一個隊列Queue q;//初始化隊列QueueInit(&q);//向隊列中插入第一個元素QueuePush(&q, root);while (!QueueEmpty(&q)){//取出隊首節點并且打印BTNode* front = QueueFront(&q);QueuePop(&q);printf("%d ", front->val);//加入隊首節點的非空子節點if (front->left){QueuePush(&q, front->left);}if (front->right){QueuePush(&q, front->right);}}
}

2,判斷完全二叉樹?

? ? ? ? 當我們需要判斷一棵樹是否為完全二叉樹時我們就可以使用該函數,它的返回值為布爾類型

//完全二叉樹的判斷
bool TreeComplete(BTNode* root);

? ? ? ? 在寫該函數時,我們則會用到以下的思路:

1,層序遍歷這個二叉樹,并且空指針也進入隊列

2,取出隊首節點遇到第一個空節點時,就開始進行判斷,如果隊列里面節點全為空就為完全二叉樹,后面有非空節點就不為完全二叉樹

? ? ? ? 讓我們結合下列的例子進行理解:

? ? ? ? 我們以之前我們學到的層序遍歷法來遍歷這個二叉樹,如下圖所示:

? ? ? ? 現在就是我們取出第一個空節點的時候,此時我們就對隊列里的元素進行判斷,如果隊列里邊全為空指針,那么這個二叉樹就為完全二叉樹,否則就不為完全二叉樹。而由我們的例子可見,該二叉樹就為完全二叉樹。?

? ? ? ? 所以我們就可以將此方法轉變為如下的代碼:

//完全二叉樹的判斷
bool TreeComplete(BTNode* root)
{assert(root);//創建一個隊列Queue q;//初始化隊列QueueInit(&q);//向隊列中插入第一個元素QueuePush(&q, root);while (!QueueEmpty(&q)){//取出隊首節點BTNode* front = QueueFront(&q);QueuePop(&q);//若取出的節點為空,就跳出循環,并對隊列中的節點進行判空if (front == NULL){break;}//不管是否為空,節點的子節點都加入隊列QueuePush(&q, front->left);QueuePush(&q, front->right);}//判斷隊列中的節點while (!QueueEmpty(&q)){//取出隊首節點BTNode* front = QueueFront(&q);QueuePop(&q);//隊列中有節點不為空,則不為完全二叉樹if (front != NULL){QueueDestroy(&q);return false;}}QueueDestroy(&q);return true;
}

四,結語

? ? ? ? 其實到了這里,關于二叉樹的基本知識我們就學完了。在二叉樹中,遞歸知識就十分的重要,也比較燒腦,所以在學習這部分知識的時候我們就需要多畫圖進行理解

? ? ? ? 后續我們也將慢慢進入排序的學習,一起加油吧!

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

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

相關文章

告別紅色波浪線:tsconfig.json 配置詳解

使用PC端的朋友,請將頁面縮小到最小比例,閱讀最佳! tsconfig.json 文件用于配置 TypeScript 項目的編譯選項。如果配不對,就會在項目中顯示一波又一波的紅色波浪線,警告你這些地方的類型聲明存在問題。 一般我們遇到這…

在沒有dubbo-admin情況下如何判斷zk中注冊的dubbo服務是否注冊成功

通常我們都是通過dubbo-admin來查看dubbo服務是否注冊成功,那么如果沒有部署dubbo-admind的情況下,我們如何來判斷dubbo服務是否注冊成功: 一、首先我們進入到zookeeper bin目錄下使用以下指令連接到zk: ./zkCli.sh -server ip:port ip&…

Linux文件系統原理

Linux文件系統 馮諾依曼在1945年提出計算機的五大組成部分 運算器:CPU 控制器:CPU 存儲器:內存和硬盤 輸入設備:鼠標、硬盤 輸出設備:顯示器一、硬盤結構 機械硬盤結構 扇區:硬盤的最小存儲單位&#xff…

Transformer講解大綱,寫PPT的可參考

前言 在這個信息如星辰般璀璨的時代,我們被無數的語言和文字包圍。它們如同夜空中閃爍的繁星,每一顆都蘊藏著獨特的故事和知識。然而,如何解讀這些星辰的秘密,如何將它們的光芒匯聚成智慧的海洋,成為了我們這個時代的挑戰。今天,我們將一起探索一種名為Transformer的神秘…

【路徑規劃】基于遺傳算法GA實現最短距離 多起點多終點多旅行商問題求解附Matlab代碼

基于遺傳算法GA實現最短距離 多起點多終點多旅行商問題求解 研究背景:研究步驟:研究方法和技術路線:代碼研究背景: 多起點多終點多旅行商問題是旅行商問題(TSP)的一個擴展,該問題要求確定多個旅行商從各自的起點出發,分別經過一系列目標點最終回到各自的終點,使得總路…

IOT技術怎么落地?以寶馬,施耐德為例

物聯網技術 物聯網(IoT)技術正逐漸成為數字化工廠轉型的核心驅動力。本文將通過實際案例,探討IoT技術如何促進制造業的數字化轉型,提高生產效率,降低成本,并提升產品質量。 1. 物聯網技術簡介 物聯網技術通…

vue 模擬隨機經緯度(小數點后保留6位),直接可用

1.隨機生成經緯度 // 隨機生成經緯度的方法function generateRandomLatLng(latitudeRange, longitudeRange) {const randomLat (Math.random() * latitudeRange.max latitudeRange.min).toFixed(6)const randomLng (Math.random() * longitudeRange.max longitudeRange.mi…

MySQL數據庫基礎:使用、架構、SQL語句、存儲引擎

文章目錄 什么是數據庫CS模式 基本使用安裝鏈接服務器服務器、數據庫、表關系簡單使用數據庫在Linux下的體現 MySQL架構連接器層客戶端層服務層存儲引擎層物理存儲層 SQL分類存儲引擎 什么是數據庫 mysql:數據庫服務的客戶端mysqld:數據庫服務的服務器端…

PLC_博圖系列?R_TRIG:檢測信號上升沿

PLC_博圖系列?R_TRIG:檢測信號上升沿 文章目錄 PLC_博圖系列?R_TRIG:檢測信號上升沿背景介紹R_TRIG: 檢測信號上升沿說明參數示例 關鍵字: PLC、 西門子、 博圖、 Siemens 、 R_TRIG 背景介紹 這是一篇關于PLC編程的文章&a…

[ C++ ] 類和對象( 中 ) 2

目錄 前置和后置重載 運算符重載和函數重載 流插入流提取的重載 全局函數訪問類私有變量 友員 const成員 取地址及const取地址操作符重載 前置和后置重載 運算符重載和函數重載 流插入流提取的重載 重載成成員函數會出現順序不同的情況(函數重載形參順序必須相…

數據結構(五)樹與二叉樹

2024年5月26日一稿(王道P142) 基本概念 術語 性質 二叉樹 5.2.2 二叉樹存儲結構

Spring從零開始學使用系列(三)--Spring框架中@Value注解和配置管理詳解

如果各位老爺覺得可以,請點贊收藏評論,謝謝啦!! 文章中涉及到的圖片均由AI生成 公眾號在最下方!!! 目錄 1. 如何在Spring中使用Value注解 1.1 基本用法 1.2提供默認值 2. 如何配置和使用Prop…

嵌入式進階——數碼管2

🎬 秋野醬:《個人主頁》 🔥 個人專欄:《Java專欄》《Python專欄》 ??心若有所向往,何懼道阻且長 文章目錄 驅動封裝封裝的一些疑問數字走馬燈實現擴展知識 驅動封裝 根據前面的內容可以將代碼進行封裝,封裝后作為一個獨立的整…

貪心題目總結

1. 最長遞增子序列 我們來看一下我們的貪心策略體現在哪里??? 我們來總結一下: 我們在考慮最長遞增子序列的長度的時候,其實并不關心這個序列長什么樣子,我們只是關心最后一個元素是誰。這樣新來一個元素之后&#xf…

HTML5 Web組件技術應用

目錄 Custom ElementsShadow DOMHTML TemplatesHTML ImportsHTML5 Web Components技術是一組相關標準和API的集合,旨在增強Web開發中的組件化能力,允許開發者創建可重用、封裝良好的自定義UI組件,這些組件擁有獨立的視圖層(樣式)、邏輯(行為)和結構(模板)。Web Compon…

【Week-R1】RNN實現心臟病預測,基于tensorflow框架

文章目錄 一、什么是RNN?二、準備環境和數據2.1 導入數據 三、構建模型四、訓練和預測五、其他(1)sklearn模塊導入報錯:ModuleNotFoundError: No module named sklearn(2)優化器改為SGD,accurac…

類和對象2

三、C對象模型和this指針 3.1 成員變量和成員函數分開存儲 在C中&#xff0c;類內的成員變量和成員函數分開存儲&#xff0c;只有非靜態成員變量才屬于類的對象上 #define _CRT_SECURE_NO_WARNINGS 1 #include <iostream> #include <string.h> using namespace …

Linux系統之GoAccess實時Web日志分析工具的基本使用

Linux系統之GoAccess實時Web日志分析工具的基本使用 一、GoAccess介紹1.1 GoAccess簡介1.2 GoAccess功能1.3 Web日志格式 二、本地環境介紹2.1 本地環境規劃2.2 本次實踐介紹 三、檢查本地環境3.1 檢查本地操作系統版本3.2 檢查系統內核版本3.3 檢查系統鏡像源3.4 更新軟件列表…

JavaFX安裝與使用

前言 最近學習了javafx,開始時在配置環境和導包時遇到了一些麻煩,關于網上很多方法都嘗試過了,現在問題都解決了,和大家分享一下我是怎么實現javafx的配置,希望大家可以通過這個方法實現自己的環境配置! &#x1f648;個人主頁: 心.c &#x1f525;文章專題:javafx &#x1f49…

如何在linux命令行(終端)執行ipynb 文件。可以不依賴jupyter

1.安裝 runipy pip install runipy 2.終端運行 runipy <YourNotebookName>.ipynb 在終端命令行執行shell腳本&#xff0c;&#xff08;也可以在crontab 中執行&#xff09;&#xff1a; (base) [recommendapp-0-5-B-006 script]$ cat run1.sh #!/bin/bashcd /home/recom…