數據結構——二叉樹的鏈式結構

?個人主頁日刷百題

系列專欄〖C語言小游戲〗〖Linux〗〖數據結構〗?〖C語言〗

🌎歡迎各位點贊👍+收藏??+留言📝??

一、二叉樹的創建

這里我們使用先序遍歷的思想來創建二叉樹,這里的內容對于剛接觸二叉樹的朋友可能有些難理解,不妨先看完下面的二叉樹各種遍歷再來看創建就會簡單很多,為了保持文章的整體性,先講二叉樹的創建。

當然為了后續內容能夠銜接,我們先手動創建一個固定的樹,就是上面這棵樹,后續內容全部圍繞這棵樹

typedef int DataType;
typedef struct TreeNode
{DataType data;struct  TreeNode* left;struct  TreeNode* right;
}TreeNode;
TreeNode* BuyNode(int x)
{TreeNode* node = (TreeNode*)malloc(sizeof(TreeNode));if (node == NULL){perror("malloc fail:");return NULL;}node->data = x;node->left = node->right = NULL;
}TreeNode* CreatTree()
{TreeNode* node1 = BuyNode(1);TreeNode* node2 = BuyNode(2);TreeNode* node3 = BuyNode(3);TreeNode* node4 = BuyNode(4);TreeNode* node5 = BuyNode(5);TreeNode* node6= BuyNode(6);node1->left = node2;node1->right = node4;node2->left = node3;node4->left = node5;node4->right = node6;return node1;
}

現在開始講如何用前序遍歷方式來進行創建二叉樹,這里給倆種創建方法。

1.1? 返回根節點指針創建

注:我們用前序遍歷方式輸入數字,-1代表空,上面的二叉樹可寫為:1 2 3 -1 -1 -1 4 5 -1 -1 6 -1 -1

TreeNode* CreatTree()
{int i;scanf("%d", &i);if (i == -1){return NULL;}TreeNode* root = (TreeNode*)malloc(sizeof(TreeNode));if (root == NULL){perror("malloc fail:");exit(-1);}root->data = i;root->left =  CreatTree();root->right = CreatTree();return root;
}

注:return root 是不能省略的,遞歸返回時,遇到空返回;或者構建完子數,返回根節點,作為上一級左子樹,構建完子樹,返回根節點,作為上一級右子樹,依次遞歸回去,直到返回整個數的根節點

1.2 二級指針傳參創建

同樣的,依然構建上面的而二叉樹,用前序遍歷方式輸入數字,-1代表空,上面的二叉樹可寫為:1 2 3 -1 -1 -1 4 5 -1 -1 6 -1 -1

void CreatTree(TreeNode** root)
{int i;scanf("%d", &i);if (i == -1){*root = NULL;}else{*root = (TreeNode*)malloc(sizeof(TreeNode));if (*root == NULL){perror("malloc fail:");exit(-1);}(*root)->data = i;CreatTree(&((*root)->left));CreatTree(&((*root)->right));}}

?注:二級指針傳參可以改變一級指針的指向,同樣的,這里創建好根節點后,創造左子樹,在創造右子樹,依次遞歸下去。

二、二叉樹的遍歷

我們先從最簡單的操作----遍歷學起。所謂二叉樹遍歷(Traversal)就是按照某種特定的規則,依次對二叉樹中的結點進行相應的操作,并且每個節點有且只操作一次。訪問結點所做的操作依賴于具體的應用問題。 遍歷是二叉樹上最重要的運算之一,也是二叉樹上進行其它運算的基礎。二叉樹的遍歷分為四種:前序遍歷、中序遍歷、后序遍歷和層序遍歷。

2.1 前序遍歷

前序遍歷(Preorder Traversal)又稱先根遍歷,即先遍歷根結點,再遍歷左子樹,最后遍歷右子樹。而對于子樹的遍歷,也服從上述規則。利用遞歸,我們可以很快地寫出代碼:

// 二叉樹前序遍歷
void PreOrder(TreeNode* root)
{if (root == NULL){printf("NULL ");return ;}printf("%d ", root->data);PreOrder(root->left);PreOrder(root->right);}

便于我們更好的理解,我們畫出遞歸展開圖

2.2 中序遍歷

中序遍歷(Inorder Traversal)又稱中根遍歷,即先遍歷左子樹,再遍歷根結點,最后遍歷右子樹。同樣,子樹的遍歷規則也是如此。遞歸代碼如下:

// 二叉樹中序遍歷
void InOrder(TreeNode* root)
{if (root == NULL){printf("NULL ");return ;}InOrder(root->left);printf("%d ", root->data);InOrder(root->right);
}

2.3 后序遍歷

后序遍歷(Inorder Traversal)又稱后根遍歷,即先遍歷左子樹,再遍歷右子樹,最后遍歷根結點遞歸代碼如下:

// 二叉樹后序遍歷
void PostOrder(TreeNode* root)
{if (root == NULL){printf("NULL ");return ;}PostOrder(root->left);PostOrder(root->right);printf("%d ", root->data);
}

2.4??層序遍歷

除了先序遍歷、中序遍歷、后序遍歷外,還可以對二叉樹進行層序遍歷。設二叉樹的根節點所在
層數為1,層序遍歷就是從所在二叉樹的根節點出發,首先訪問第一層的樹根節點,然后從左到右訪問第2層上的節點,接著是第三層的節點,以此類推, 自上而下,自左至右逐層訪問樹的結點 的過程就是層序遍歷。
層序遍歷借助隊列實現,思路解析圖如下:
//層序遍歷
void LevelOrder(TreeNode* root)
{Queue pq;QueueInit(&pq);if (root == NULL){QueueDestroy(&pq);return;}QueuePush(&pq,root);while (!QueueEmpty(&pq)){TreeNode* front= QueueFront(&pq);printf("%d ", front->data);QueuePop(&pq);if (front->left!= NULL){QueuePush(&pq, front->left);}if (front->right != NULL){QueuePush(&pq, front->right);}}QueueDestroy(&pq);
}

思考:當然層序遍歷這里有一個變形,我們能不能將二叉樹每一層打印單獨放一行,該怎么做呢?

思路:

(1)設二叉樹的根節點所在層數為1,第一層根節點進隊列,隊列元素個數為1,size==1
(2)每出隊列一次,size--,根節點出完隊列,倆個子節點進隊列,此時隊列元素個數為第二層節點個數,size==2
(3)當我們出隊列size次,把第二層元素出完,隊列剩下的元素是第三層節點,size==QueueSize
以此類推,以size為循環條件,則可實現每層單獨打印一行

void LevelOrder(TreeNode* root)
{Queue pq;QueueInit(&pq);if (root == NULL){QueueDestroy(&pq);return;}QueuePush(&pq,root);int size = 1;while (!QueueEmpty(&pq)){while (size--){TreeNode* front = QueueFront(&pq);printf("%d ", front->data);QueuePop(&pq);if (front->left != NULL){QueuePush(&pq, front->left);}if (front->right != NULL){QueuePush(&pq, front->right);}}size = QueueSize(&pq);printf("\n");}QueueDestroy(&pq);
}

三、二叉樹的結點

3.1 二叉樹的總結點數

一顆二叉樹的結點數我們可以看作是根結點+左子樹結點數+右子樹結點數,那左右子樹的結點數又是多少呢?按照相同的方法繼續拆分,層層遞歸直到左右子樹為空樹,返回空樹的結點數0即可。遞歸代碼如下:

// 二叉樹節點個數
int TreeSize(TreeNode* root)
{return root == NULL? 0 : TreeSize(root->left) + TreeSize(root->right) + 1;
}

3.2 二叉樹的葉子結點數

左右子樹都為空的結點即是葉子結點。這里分為兩種情況:左右子樹都為空和左右子樹不都為空。

(1)當左右子樹都為空時,則這顆樹的葉子結點數為1(根節點)。

(2)當左右子樹不都為空,即根結點不是葉子結點時,這棵樹的葉子結點數就為左子樹葉子結點數+右子樹葉子結點數(空樹沒有葉子結點)。
?

// 二叉樹葉子節點個數
int  TreeLeafSize(TreeNode* root)
{if (root == NULL){return 0;}if (root->left == NULL && root->right == NULL){return 1;}return TreeLeafSize(root->left) + TreeLeafSize(root->right);
}

3.3 二叉樹第k層結點數

一顆樹第k層的結點數我們可以拆分為其左子樹第k-1層結點+右子樹第k-1層結點(注:當前節點為第一層)

(1)若為空樹,無論哪層節點數都是0

(2)若不是空樹且k==1,則只有一個節點(根節點)

// 二叉樹第k層節點個數
int TreeLevelKSize(TreeNode* root, int k)
{assert(k > 0);if (root!=NULL&&k == 1){return 1;}if (root == NULL){return 0;}if (k > 1){return TreeLevelKSize(root->left, k - 1) + TreeLevelKSize(root->right, k - 1);}
}
// 判斷二叉樹是否是完全二叉樹
bool TreeComplete(TreeNode* root)
{Queue pq;QueueInit(&pq);if (root == NULL){QueueDestroy(&pq);return;}QueuePush(&pq, root);while (!QueueEmpty(&pq)){TreeNode* front = QueueFront(&pq);QueuePop(&pq);if (front == NULL){break;}QueuePush(&pq, front->left);QueuePush(&pq, front->right);}while (!QueueEmpty(&pq)){TreeNode* front = QueueFront(&pq);QueuePop(&pq);if (front != NULL){return false;}}QueueDestroy(&pq);return true;
}

四、二叉樹的查找

二叉樹的查找本質上就是一種遍歷,只不過是將之前的打印操作換為查找操作而已。我們可以使用前序遍歷來進行查找:

(1)先比較根結點是否為我們要查找的結點,如果是,返回根節點地址

(2)如果不是,遍歷左子樹,如果左子樹是,直接返回節點地址;不是則遍歷右子樹,如果右子樹是,直接返回節點地址,不是返回空,說明左右子樹都沒找到。

// 二叉樹查找值為x的節點
TreeNode* TreeFind(TreeNode* root, DataType x)
{if (root == NULL){return NULL;}if (root->data == x){return root;}TreeNode* node1 = TreeFind(root->left, x);if (node1){return node1;}TreeNode* node2 = TreeFind(root->right, x);if (node2){return node2;}return NULL;
}

五、二叉樹的高度/深度

樹中結點的最大層次稱為二叉樹的高度。因此,一顆二叉樹的高度我們可以看作是

1(根結點)+左右子樹高度的較大值。層層遞歸下去直到遇到空樹返回0即可,

這里值得注意的是:不要寫成

return TreeHeight(root->left)>TreeHeight(root->right) ? TreeHeight(root->left)+1:TreeHeight(root->right)+1;
}
這里比較好左右子樹較大的一顆后,又會從新遞歸較大那顆子樹高度,會造成重復計算,時間復雜度增高。

我們要保存好左右子樹層數,避免重復計算,代碼如下:

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

六、完全二叉樹的判斷

這里的思路利用了層序遍歷,不同的是,將空節點指針也入隊列,當我們遇到第一個空節點指針則退出循環,然后對隊列進行檢測,若第一個空節點指針以后全都是空,則為完全二叉樹,反之,不為完全二叉樹。

注:當在隊列遇到第一個空節點指針時,二叉樹中空節點指針之后所有非空節點指針全部進隊列

思路解析圖如下:

代碼如下:

// 判斷二叉樹是否是完全二叉樹
bool TreeComplete(TreeNode* root)
{Queue pq;QueueInit(&pq);if (root == NULL){QueueDestroy(&pq);return;}QueuePush(&pq, root);while (!QueueEmpty(&pq)){TreeNode* front = QueueFront(&pq);QueuePop(&pq);if (front == NULL){break;}QueuePush(&pq, front->left);QueuePush(&pq, front->right);}while (!QueueEmpty(&pq)){TreeNode* front = QueueFront(&pq);QueuePop(&pq);if (front != NULL){return false;}}QueueDestroy(&pq);return true;
}

?七、二叉樹的銷毀

7.1 一級指針傳參銷毀

同樣的,和創建節點一樣,我們給出倆個銷毀方式:

(1)一種是傳一級指針方式,這種方式不是改變根節點的指向,需要在銷毀函數結束后,將root置為NULL

void TreeDestroy(TreeNode* root)//出來將root=NULL
{if (root == NULL){return;}TreeDestroy(root->left);TreeDestroy(root->right);free(root);}

7.2?二級指針傳參銷毀?

(2)另一種是傳二級指針,直接在函數內部將每一個銷毀的節點指針置為NULL.

void TreeDestroy(TreeNode** root)
{if (*root == NULL){return;}TreeDestroy(&(*root)->left);TreeDestroy(&(*root)->right);free(*root);*root = NULL;
}

?總結:本篇文章將二叉樹的基礎知識差不多囊括了,后續的話還需要大量練習做鞏固加強,遞歸比較抽象難以理解,需要動手畫遞歸展開圖進行幫助理解。

希望大家閱讀完可以有所收獲,同時也感謝各位鐵汁們的支持。文章有任何問題可以在評論區留言,百題一定會認真閱讀!

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

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

相關文章

iClient3D 加載天地圖服務

1 對國家天地圖,通過TiandituImageryProvider影像服務提供者加載地圖; var TiandituimageryLayernew Cesium.TiandituImageryProvider({ mapStyle: Cesium.TiandituMapsStyle[value],token: "4a00a1dc5387b8ed8adba3374bd87e5e"})viewer.imag…

nginx 的概念、高并發處理及詳細參數配置

NGINX是一個開源的高性能Web服務器,負載均衡器和反向代理服務器。它特別適用于高并發的Web應用,能夠有效地處理數千并發連接,同時具備低資源消耗和高性能的特點。在這里,我將重點介紹NGINX的高并發處理能力和參數配置。 高并發處…

云原生(Cloud Native)——概念,技術,背景,優缺點,實踐例子

云原生(Cloud Native)是一種構建和運行應用程序的方法,這些應用程序充分利用云計算的優勢。云原生應用程序通常設計為在現代、動態的環境中運行,如公共云、私有云和混合云。這種方法強調微服務架構、容器化、自動化、易于管理和可…

QT 信號與槽 connect 三種寫法

先看下示例: QPushButton *btn new QPushButton;// 方式一:老式寫法connect(btn, SIGNAL(clicked()), this, SLOT(close()));// 方式二:Qt5后新寫法connect(btn, &QPushButton::clicked, this, &MainWindow::close);// 方式三&#…

Word插件-好用的插件-一鍵設置字體--大珩助手

常用字體 整理了論文、公文常用字體 整理了常用的論文字體,可一鍵設置當前節或選擇的文字的字體 字體設置 包含字體選擇、字體顏色 特殊格式 包含首字下沉、段落分欄、統一寬度、雙行合一、上標切換、下標切換、轉為全角、轉為半角、挖詞填空、當前日期、大寫金…

LabVIEW開發遠程結構健康監測系統

LabVIEW開發遠程結構健康監測系統 工程師依賴于振動監測來評估建筑物、橋梁和其他大型結構的完整性。傳統的振動監測工具在數據收集上存在限制,無法長時間收集高保真波形。隨著內存存儲、處理器速度和寬帶無線通信技術的進步,出現了對能夠長時間收集并實…

Navicat 技術指引 | 適用于 GaussDB 分布式的查詢功能

Navicat Premium(16.3.3 Windows 版或以上)正式支持 GaussDB 分布式數據庫。GaussDB 分布式模式更適合對系統可用性和數據處理能力要求較高的場景。Navicat 工具不僅提供可視化數據查看和編輯功能,還提供強大的高階功能(如模型、結…

深入了解對象與內置構造函數

1. 深入對象 1.1 創建對象的三種方式 1.2 構造函數 語法約定: 總結 構造函數可以快速創建多個對象大寫字母開頭的函數使用new關鍵字將對象實例化構造函數不需要返回值自動返回新的對象 new實例化的執行過程 創建空對象this指向對象執行代碼,追加新…

使用wire重構商品微服務

一.wire簡介 Wire 是一個輕巧的Golang依賴注入工具。它由Go Cloud團隊開發,通過自動生成代碼的方式在編譯期完成依賴注入。 依賴注入是保持軟件 “低耦合、易維護” 的重要設計準則之一。 此準則被廣泛應用在各種開發平臺之中,有很多與之相關的優秀工…

使用pyftpdlib組件實現FTP文件共享

目錄 一、引言 二、技術背景 三、實現邏輯 1、創建FTP服務器: 2、實現文件共享: 3、設置用戶權限: 4、處理異常: 5、優化與擴展: 四、代碼實現 五、測試與評估 測試用例: 評估方法:…

React/Vue/Svelte 前端項目中開始使用TailwindCSS

背景 TailwindCSS 近年來在前端圈非常流行,它擺脫了原有的CSS限制,以靈活實用為賣點,用戶通過各種class組合即可構建出漂亮的用戶界面。對于初學者而言,可能需要一些上手成本,一旦掌握實用技巧后,Tailwind…

Unity中Batching優化的GPU實例化整理總結

文章目錄 前言一、GPU Instancing的支持1、硬件支持2、Shader支持3、腳本支持 二、我們來順著理一下GPU實例化的使用步驟1、GPU實例化前的C#代碼準備2、在 appdata 和 v2f 中定義GPU實例化ID3、在頂點著色 和 片元著色器 設置GPU Instance ID,使實例化對象頂點位置正…

Docker的資源控制

Docker的資源控制: 對容器使用宿主機的資源進行限制。 CPU 內存 磁盤I/O(讀寫性能) docker使用linux自帶的功能cgroup control groups是linux內核系統提供的一種可以限制,記錄,隔離進程組所使用的物理資源的一種機制。 docker借助這個機制…

go grpc高級用法

文章目錄 錯誤處理常規用法進階用法原理 多路復用元數據負載均衡壓縮數據 錯誤處理 gRPC 一般不在 message 中定義錯誤。畢竟每個 gRPC 服務本身就帶一個 error 的返回值,這是用來傳輸錯誤的專用通道。gRPC 中所有的錯誤返回都應該是 nil 或者 由 status.Status 產…

如何克服微服務測試的挑戰,并最大化收益

多年來,微服務一直是行業趨勢,但組織卻未能從該方法中獲益,并因發布失敗而苦苦掙扎。這些失敗通常歸結為測試服務之間的接口以獲得預期的質量、安全性和性能的困難。 最終,未能以足夠穩健的方式測試這些 API。一線希望是遺留 SOA…

cookie總結

cookie和session: 一、Cookie和Session二、使用Cookie保存用戶上次的訪問時間。三、Cookie常用方法總結亂碼問題解決: 一、Cookie和Session 會話:用戶從打開瀏覽器到關閉的整個過程就叫1次會話。 比如有的網站登錄過一次,下次再進…

Gitleaks - 一款高效的Github倉庫敏感信息泄露查詢工具

Gitleaks - 一款高效的Github倉庫敏感信息泄露查詢工具 1.工具概述2.安裝3.參數解析4.使用1.工具概述 Gitleaks 是一種 SAST 工具,用于檢測和防止 git 存儲庫中的硬編碼機密,如密碼、API 密鑰和令牌 Gitleaks 是一個開源工具,用于檢測和防止簽入 Git 存儲庫的機密(密碼/A…

使用 Kubernetes 為 CI/CD 流水線打造高效可靠的臨時環境

介紹 在不斷發展的科技世界中,快速構建高質量的軟件至關重要。在真實環境中測試應用程序是及早發現和修復錯誤的關鍵。但是,在真實環境中設置 CI/CD 流水線進行測試可能既棘手又昂貴。 Kubernetes 是一個流行的容器編排平臺,提供臨時環境解決…

【qt】Qt+OpenCv讀取帶有中文路徑的圖片

【opencv4.5.1版本】下載exe解壓即可。。。https://opencv.org/releases/page/2/ 【qt5.15.2】 pro文件 QT core guigreaterThan(QT_MAJOR_VERSION, 4): QT widgetsCONFIG c17# You can make your code fail to compile if it uses deprecated APIs. # In order to …

YOLOv8配置文件yolov8.yaml解讀

🍨 本文為🔗365天深度學習訓練營 中的學習記錄博客🍖 原作者:K同學啊 | 接輔導、項目定制 位置 該文件的位置位于 ./ultralytics/cfg/models/v8/yolov8.yaml 模型參數配置 # Parameters nc: 80 # number of classes scales: #…