【數據結構】二叉樹(Binary Tree)

文章目錄

  • 一、樹的概念及結構
  • 二、二叉樹的概念及結構
    • 1.二叉樹的概念
    • 2.特殊的二叉樹
    • 3.二叉樹的性質
  • 三、二叉樹的存儲
    • 順序存儲
    • 鏈式存儲
  • 四、二叉樹的實現
    • 1.創建二叉樹
    • 2.二叉樹的遍歷
      • 前序遍歷
      • 中序遍歷
      • 后序遍歷
      • 層序遍歷
      • 根據遍歷順序創建二叉樹
    • 3.二叉樹的基本操作
      • 1.總結點個數
      • 2.二叉樹高度
      • 3.第K層結點個數
      • 4.查找
      • 5.判斷二叉樹是否是完全二叉樹
    • 4.銷毀二叉樹

一、樹的概念及結構

是一種非線性的數據結構,它是由n(n>=0)個有限結點組成一個具有層次關系的集合。它看起來像一棵倒掛的樹,根朝上,而葉子朝下,所以我們把它叫做為樹。
在這里插入圖片描述

根結點:沒有前驅結點,是當前樹中所有結點的祖先。

除根節點外,其余結點被分成一棵結構與樹類似的子樹。每棵子樹的根結點有且只有一個前驅,可以有0個或多個后繼。因此,樹是遞歸定義的。

g)

節點的度:一個節點含有的子樹的個數稱為該節點的度; 如上圖:A的度為3。

葉子節點:度為0的節點稱為葉子節點; 如上圖:E、F、G為葉子節點。

雙親節點或父節點:若一個節點含有子節點,則這個節點稱為其子節點的父節點; 如上圖:A是B的父節點,B是E的父節點。

孩子節點或子節點:一個節點含有的子樹的根節點稱為該節點的子節點; 如上圖:B是A的子節點,F是D的子節點。

兄弟節點:具有相同父節點的節點互稱為兄弟節點; 如上圖:B、C、D是兄弟節點。

樹的度:一棵樹中,最大的節點的度稱為樹的度; 如上圖:樹的度為3.

節點的層次:從根開始定義起,根為第1層,根的子節點為第2層,以此類推;

樹的高度或深度:樹中節點的最大層次/深度; 如上圖:樹的高度為3

堂兄弟節點:雙親在同一層的節點互為堂兄弟;如上圖:E、F互為堂兄弟節點

節點的祖先:從根到該節點所經分支上的所有節點;如上圖:A是所有節點的祖先

子孫:以某節點為根的子樹中任一節點都稱為該節點的子孫。如上圖:所有節點都是A的子孫

森林:由m(m>0)棵互不相交的樹的集合稱為森林;

二、二叉樹的概念及結構

1.二叉樹的概念

二叉樹是一種特殊的樹,每個結點最多只能有兩個子結點,即二叉樹不存在度大于2的結點。
二叉樹可以分為三個部分:根、左子樹、右子樹,每顆子樹又可以劃分為這三個部分,一直遞歸到葉子結點,葉子結點是其本身的根結點。
二叉樹的子樹有左右之分,次序不能顛倒,因此二叉樹是有序樹。
在這里插入圖片描述

二叉樹有五種基本形態:空樹(B的右子樹)、只有根節點(D、E、F)、只有左子樹(B)、只有右子樹、左右子樹(A、C)都存在。

2.特殊的二叉樹

滿二叉樹:除了葉子結點之外的所有結點都有兩個子結點。如果一個滿二叉樹的層數為K,那么結點總數是20 + 21 + … + 2k-1 = 2k - 1個。

在這里插入圖片描述

完全二叉樹:除了最后一層,完全二叉樹的每一層從左到右都是滿的。也就是說,如果完全二叉樹的層數為 h,那么前 h-1 層一定是一個滿二叉樹。
滿二叉樹是一種特殊的完全二叉樹。

在這里插入圖片描述

斜二叉樹:除了葉子結點外,所有結點都只有一個結點,且是一個方向的結點,要么都只有左結點,要么都只有右結點。

在這里插入圖片描述

3.二叉樹的性質

1.任意二叉樹第 i 層最多有2i-1個結點。 ( i ≥ \geq 1)
2.深度為 h 的任意二叉樹的最大結點總數為2h-1。( h ≥ \geq 1)
3.任意二叉樹中,度為0的葉子結點個數永遠比度為2的結點個數多一個。
4.對于一個有n個結點的滿二叉樹,其深度 h= l o g 2 log_2 log2?(n+1)(向上取整)

三、二叉樹的存儲

二叉樹一般可以使用兩種結構存儲,一種順序結構,一種鏈式結構。

順序存儲

二叉樹的順序結構存儲就是使用數組來存儲。按照層序遍歷的順序(從上到下,從左到右)依次將結點存儲在數組中,如果某個結點的左結點或者右結點不存在,則對應的數組的位置就為空,不存儲元素。
在這里插入圖片描述

  • 二叉樹順序存儲在物理上是一個數組,在邏輯上是一顆二叉樹。
  • 二叉樹以數組方式存儲,父子結點下標的關系:左結點下標 = 2*父結點下標+1,右結點下標 = 2*父結點下標+2,父結點下標 = (任意孩子結點下標-1)/ 2。
  • 由上圖可以看出,如果是非完全二叉樹用數組來表示,則會存在空間浪費,所以使用數組一般只適合表示完全二叉樹。而對于一般的二叉樹,常用鏈式存儲結構。
  • 鏈式存儲

    二叉樹的鏈式存儲結構是指,用鏈表來表示一棵二叉樹,即用鏈來指示元素的邏輯關系。 通常的方法是鏈表中每個結點由三個域組成,數據域和左右指針域,左右指針分別用來存儲該結點左孩子和右孩子所在結點的存儲地址 。

    在這里插入圖片描述

    四、二叉樹的實現

    接下來我們用鏈表來實現二叉樹的創建與遍歷。

    二叉樹定義

    typedef char BTDataType;
    typedef struct BinaryTreeNode
    {BTDataType data;//數據struct BinaryTreeNode* left;//左孩子結點struct BinaryTreeNode* right;//右孩子結點
    }BTNode;
    

    1.創建二叉樹

    在這里插入圖片描述

    以上圖為例建立一棵二叉樹。
    以下代碼并不是真正創建二叉樹的方式,只是為了方便大家理解,所以手動快速創建一棵簡單的二叉樹。等后面學完二叉樹遍歷之后,我們再講解二叉樹的真正創建方式。

    //動態申請結點
    BTNode* BuyNode(BTDataType x)
    {BTNode* node = (BTNode*)malloc(sizeof(BTNode));if (NULL == node){perror("malloc");return NULL;}node->data = x;node->left = node->right = NULL;return node;
    }
    //創建二叉樹
    BTNode* CreatTree()
    {BTNode* node1 = BuyNode('A');BTNode* node2 = BuyNode('B');BTNode* node3 = BuyNode('C');BTNode* node4 = BuyNode('D');BTNode* node5 = BuyNode('E');BTNode* node6 = BuyNode('F');node1->left = node2;node1->right = node3;node2->right = node4;node3->left = node5;node4->left = node6;return node1;
    }
    

    2.二叉樹的遍歷

  • 前面我們講過,二叉樹分為:根、左子樹、右子樹。每個結點的左右子樹又可以劃分為這三個部分,直到最后一層的葉子結點,葉子結點是其本身的根結點,左右結點為空。根據這一特點,可以看出,二叉樹定義是遞歸式的。
  • 二叉樹遍歷是按照某種特定的規則,依次訪問每個節點,并且每個節點只訪問一次。根據根節點的訪問順序分為三種遍歷方式:前序遍歷、中序遍歷和后序遍歷。
  • 在這里插入圖片描述

    前序遍歷

    前序遍歷的訪問順序:根結點、左子樹、右子樹。
    其中,對于每棵子樹,訪問順序也是根結點、左子樹、右子樹,一直遞歸到空結點。

    在這里插入圖片描述

    在這里插入圖片描述相同顏色框的是相同根節點的左右子樹

    比如上圖的前序遍歷訪問順序為:A B NULL D F NULL NULL NULL C E NULL NULL NULL
    而我們一般不打印空結點,所以前序遍歷結果為:A B D F C E

    //前序遍歷
    void PreOrder(BTNode* root)
    {if (NULL == root){//printf("NULL ");return;}printf("%c ", root->data);//打印根節點的值PreOrder(root->left);//遍歷子樹PreOrder(root->right);//遍歷右子樹
    }
    

    中序遍歷

    中序遍歷的訪問順序:左子樹、根結點、右子樹。
    還是拿上圖示例,中序遍歷訪問順序為:NULL B NULL F NULL D NULL A NULL E NULL C NULL
    去掉空結點后,中序遍歷結果為:B F D A E C
    在這里插入圖片描述

    在這里插入圖片描述

    //中序遍歷
    void InOrder(BTNode* root)
    {if (NULL == root){//printf("NULL ");return;}InOrder(root->left);printf("%c ", root->data);InOrder(root->right);
    }
    

    后序遍歷

    中序遍歷的訪問順序:左子樹、右子樹、根結點。

    在這里插入圖片描述

    在這里插入圖片描述上圖二叉樹的后序遍歷訪問順序為:NULL NULL NULL F NULL D B NULL NULL E NULL C A
    去掉空結點后,后序遍歷結果為:F D B E C A

    //后序遍歷
    void PostOrder(BTNode* root)
    {if (NULL == root){//printf("NULL ");return;}PostOrder(root->left);PostOrder(root->right);printf("%c ", root->data);
    }
    

    層序遍歷

    二叉樹的層次遍歷最好理解,從二叉樹的根結點開始,從上到下每一層按照從左到右的順序遍歷。比如上圖中二叉樹的層序遍歷為:A B C D E F

    前面三種遍歷都是以遞歸的形式,而層序遍歷可以用隊列來實現非遞歸遍歷。前面我們已經用C語言寫過隊列(點擊跳轉文章)結構,可以在vs中直接將隊列的相關代碼copy過來使用,在源文件和頭文件中直接添加現有項即可。

    并將隊列中的類型改為二叉樹類型

    //typedef int QDataType;
    typedef struct BinaryTreeNode* QDataType;
    

    具體過程是:先將當前節點入隊列訪問當前結點,再將當前結點出隊列。然后將當前結點的左結點和右結點按順序入隊,如果為空結點則不用入隊,直到隊列為空。

    //層序遍歷
    void LevelOrder(BTNode* root)
    {assert(root);Queue q;QueueInit(&q);QueuePush(&q, root);while (!QueueEmpty(&q)){BTNode* front = QueueFront(&q);QueuePop(&q);printf("%c ", front->data);if(front->left != NULL)QueuePush(&q, front->left);if (front->right != NULL)QueuePush(&q, front->right);}QueueDestroy(&q);printf("\n");
    }
    

    根據遍歷順序創建二叉樹

    數組a存儲前序遍歷的順序,比如通過前面我們知道二叉樹的前序遍歷結果為A B NULL D F NULL NULL NULL C E NULL NULL NULL ;我們將NULL替換成字符#表示空。數組下標用指針接收,否則遞歸函數中不會改變實參。

    //根據前序遍歷的順序二叉樹創建
    BTNode* BTreeCreate_PreOrder(BTDataType* a, int* pi)
    {// 通過前序遍歷的數組"AB#DF###CE###"構建二叉樹if (a[*pi] == '#'){(*pi)++;//數組下標+1return NULL;}BTNode* node = BuyNode(a[*pi]);//根據數組元素構建二叉樹結點(*pi)++;node->left = BTreeCreate_PreOrder(a, pi);node->right = BTreeCreate_PreOrder(a, pi);return node;
    }
    

    根據中序/后序順序來構建二叉樹同理。

    int main()
    {char a[] = "AB#DF###CE###";int i = 0;BTNode* root = BTreeCreate_PreOrder(a, &i);//BTNode* root = BTreeCreate();PreOrder(root);printf("\n");InOrder(root);printf("\n");PostOrder(root);printf("\n");return 0;
    }
    

    3.二叉樹的基本操作

    在前面二叉樹遞歸遍歷的基礎上,我們可以實現一些二叉樹的基本操作。

    1.總結點個數

    轉換成子問題:二叉樹總結點個數=1(根節點個數)+ 左子樹的結點個數 + 右子樹的節點個數。很容易想到遞歸。

    //二叉樹的總結點個數
    int BTreeSize(BTNode* root)
    {if (NULL == root){return 0;}return 1 + BTreeSize(root->left) + BTreeSize(root->right);
    }
    

    2.二叉樹高度

    當前樹的高度 = 左右子樹中比較高的高度+1

    //二叉樹的高度
    int BTreeLevel(BTNode* root)
    {if (NULL == root)//空結點則返回0{return 0;}int leftLevel = BTreeLevel(root->left);//左子樹的高度int rightLevel = BTreeLevel(root->right);//右子樹的高度return leftLevel > rightLevel ? leftLevel + 1 : rightLevel + 1;
    }
    

    最好不要寫成下面這樣,會造成重復遞歸,判斷時遍歷左右子樹遞歸兩次,返回結果時又遞歸一次,棧幀開銷會很大。

    return BTreeLevel(root->left) > BTreeLevel(root->right) ? BTreeLevel(root->left) + 1 : BTreeLevel(root->right) + 1;
    

    3.第K層結點個數

    求第K層結點個數,轉換成子問題:求左子樹第K-1層結點個數 + 右子樹第K-1層結點個數

    //第k層結點的個數
    int BTreeLevelKSize(BTNode* root, int k)
    {assert(k > 0);//默認根節點為第一層,所以不存在第0層if (NULL == root)//空結點{return 0;}if (1 == k)//遍歷到第k層{return 1;}return BTreeLevelKSize(root->left, k-1)+BTreeLevelKSize(root->right, k-1);
    }
    

    4.查找

    //查找值為x的結點
    BTNode* BTreeFind(BTNode* root, BTDataType x)
    {if (NULL == root)return NULL;if (root->data == x)return root;BTNode* left = BTreeFind(root->left, x);if (left)//左子樹找到則返回return left;//左子樹未找到,只可能存在于右子樹return BTreeFind(root->right, x);
    }
    

    5.判斷二叉樹是否是完全二叉樹

    完全二叉樹按層序走,非空結點一定是連續的。每次將當前結點的左右結點入隊列,不用判空,空結點也入隊列。遇到第一個空結點時,停止入隊,開始判斷隊列中的元素是否都為空,若出現非空結點,說明不是完全二叉樹,反之則是完全二叉樹。

    //判斷二叉樹是否是完全二叉樹
    bool BTreeComplete(BTNode* root)
    {assert(root);Queue q;QueueInit(&q);QueuePush(&q, root);while (!QueueEmpty(&q)){BTNode* front = QueueFront(&q);QueuePop(&q);//完全二叉樹按層序走,非空結點一定是連續的if (NULL == front)//遍歷到第一個空結點則退出循環,進入下個while循環判斷{break;}else{//不為空則將左右孩子結點入隊QueuePush(&q, front->left);QueuePush(&q, front->right);}}//如果是完全二叉樹,則隊列中元素全是NULLwhile (!QueueEmpty(&q)){BTNode* front = QueueFront(&q);QueuePop(&q);//有非空結點則說明非空結點不是完全連續,說明不是完全二叉樹if (front != NULL){QueueDestroy(&q);//返回之前及時釋放空間,防止內存泄露return false;}}QueueDestroy(&q);return true;
    }
    

    4.銷毀二叉樹

    根節點最后釋放,否則無法訪問左右子樹。

    //二叉樹銷毀(后序遍歷)
    void BTreeDestroy(BTNode* root)
    {if (NULL == root)return;BTreeDestroy(root->left);BTreeDestroy(root->right);free(root);root = NULL;
    }
    

    完整代碼:鏈式二叉樹

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

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

相關文章

ctfshow之_萌新web9至web10

一、訪問在線靶場ctfshow 1、web9 如下圖所示,進入_萌新賽的web9問題,題目提醒flag在config.php中: 如上圖所示,可以get傳參,且傳入的參數需要正則匹配system、exec、highlight,且不區分大小寫&#xff0…

C++設計模式|創建型 5.原型模式

1.什么是原型模式? 原型模式?種創建型設計模式,該模式的核?思想是基于現有的對象創建新的對象,?不是從頭開始創建。 在原型模式中,通常有?個原型對象,它被?作創建新對象的模板。新對象通過復制原型對象的屬性和狀…

Mac IDEA 自動補全mybatis sql語句

導航 Mac IDEA 自動補全mybatis sql語句一、點擊IDEA 右側Database選項二、選擇添加對應數據庫三、輸入數據庫信息和方案四、輸入數據庫信息和方案五、成功 Mac IDEA 自動補全mybatis sql語句 背景: 想在Mapper中,能夠實現自動檢索數據庫表和對應的字段…

QT日志類SimpleQtLogger的簡單記錄

在現代軟件開發中,日志記錄是必不可少的部分。它不僅幫助開發者在調試和維護軟件時了解程序的運行狀態,還能提供關鍵的錯誤信息。對于使用Qt框架開發應用程序的開發者來說,選擇一個合適的日志庫至關重要。本文將詳細介紹Qt日志庫SimpleQtLogg…

web前端之sass中的顏色函數、active按鈕激活、hover鼠標懸浮、disabled禁用、scss循環、css

MENU 效果圖htmlsassscss編譯后的css頁面css 效果圖 注意查看藍色按鈕。 html <div class"box"><button class"btn type_1">按鈕</button><button class"btn type_2">按鈕</button><button class"btn ty…

一文讀懂通用漏洞評分系統CVSS4.0:順帶理清CVE、CWE及其與CVSS之間的關系

事件響應和安全團隊論壇 (FIRST&#xff0c;Forum of Incident Response and Security Teams) 于 2023 年 11 月 1 日正式推出第四版通用漏洞評分系統 (CVSS 4.0&#xff0c;Common Vulnerability Scoring System version 4.0)。CVSS 4.0 是評估計算機系統安全漏洞嚴重性的行業…

C++ 多態性

一 多態性的分類 編譯時的多態 函數重載 運算符重載 運行時的多態 虛函數 1 運算符重載的引入 使用C編寫程序時&#xff0c;我們不僅要使用基本數據類型&#xff0c;還要設計新的數據類型-------類類型。 一般情況下&#xff0c;基本數據類型的運算都是運算符來表達&#x…

【C++】詳解C++的模板

目錄 概念 ?編輯 語法 函數模板 類模板 非類型模板參數 模板的特化 函數模板特化 類模板特化 全特化 偏特化 分離編譯 概念 模板是C中非常厲害的設計&#xff0c;模板把通用的邏輯剝離出來&#xff0c;讓不同的數據類型可以復用同一種模板的邏輯&#xff0c;甚至可以…

Flutter 中的 DataTable 小部件:全面指南

Flutter 中的 DataTable 小部件&#xff1a;全面指南 在Flutter的Material組件庫中&#xff0c;DataTable是一個用于展示數據的表格組件&#xff0c;它允許開發者以一種結構化和可滾動的方式展示數據集。DataTable非常適合展示詳細信息&#xff0c;如表格數據、統計數據或配置…

PHP黑魔法之md5繞過

php本身是一種弱語言,這個特性決定了它的兩個特點: 輸入的參數都是當作字符串處理變量類型不需要聲明,大部分時候都是通過函數進行類型轉化php中的判斷有兩種: 松散比較:只需要值相同即可,類型不必相同,不通類型比較會先轉化為同類型,比如全數字字符串和數字比較,會比…

凸優化理論學習三|凸優化問題(一)

系列文章目錄 凸優化理論學習一|最優化及凸集的基本概念 凸優化理論學習二|凸函數及其相關概念 文章目錄 系列文章目錄一、優化問題&#xff08;一&#xff09;標準形式的優化問題&#xff08;二&#xff09;可行點和最優點&#xff08;三&#xff09;局部最優點&#xff08;四…

《Python編程從入門到實踐》day28

# 昨日知識點回顧 安裝Matplotlib 繪制簡單的折線圖 # 今日知識點學習 15.2.1 修改標簽文字和線條粗細 # module backend_interagg has no attribute FigureCanvas. Did you mean: FigureCanvasAgg? # 解決辦法&#xff1a;matplotlib切換圖形界面顯示終端TkAgg。 #…

使用Three.js繪制快速而逼真的水

本文將利用GPUComputationRenderer來實現水波紋的繪制&#xff0c;相似的案例可以看threejs官方的GPGPU Water示例。更多精彩內容盡在數字孿生平臺。 什么是 GPGPU GPGPU代表通用圖形處理單元&#xff08;General-Purpose Graphic Processing Unit&#xff09;&#xff0c;意思…

1146 -Table ‘performance schema.session variables‘ doesn‘t exist的錯誤解決

一、問題出現 今天在本地連數據庫的時候&#xff0c;發現這個問題&#xff0c;哎呦我擦&#xff0c;差點嚇死了 二、解決辦法 1&#xff09;找文件 用everything搜一下MySQL Server 5.7 然后去Windows服務找一下MySQL配置文件的具體路徑 如果知道那最好&#xff0c;不知道那…

寶塔8.1.0去除綁定用戶

非要綁定手機號&#xff0c;確實很煩 1&#xff0c;/www/server/panel/BTPanel __init__.py if not public.is_bind():return redirect(/bind, 302) 將is_bind的路由全部注釋 2&#xff0c;/www/server/panel/class下 panelPlugin.py 注釋異常&#xff0c; 新增 softLis…

SSL協議

SSL 安全傳輸協議&#xff08;安全套接層&#xff09; 也叫TLS ---- 傳輸層安全協議 SSL的工作原理&#xff1a;SSL協議因為是基于TCP協議工作的&#xff0c;通信雙方需要先建立TCP會話。因為SSL協議需要進行安全保證&#xff0c;需要協商安全參數&#xff0c;所以也需要建立…

【MySQL】7.MySQL性能優化的六大核心策略

數據庫的性能對整個應用的響應速度和用戶體驗起著至關重要的作用。MySQL&#xff0c;作為廣泛使用的開源關系型數據庫&#xff0c;提供了豐富的性能優化手段。從資源優化、查詢優化到結構、配置、代碼乃至架構優化&#xff0c;每一個層面的調整都可能帶來性能的飛躍。本文將深入…

springboot房屋租賃系統

摘要 房屋租賃系統&#xff1b;為用戶提供了一個房屋租賃系統平臺&#xff0c;方便管理員查看及維護&#xff0c;并且可以通過需求進行設備信息內容的編輯及維護等&#xff1b;對于用戶而言&#xff0c;可以隨時進行查看房屋信息和合同信息&#xff0c;并且可以進行報修、評價…

清理緩存簡單功能實現

在程序開發中&#xff0c;經常會用到緩存&#xff0c;最常用的后端緩存技術有Redis、MongoDB、Memcache等。 而有時候我們希望能夠手動清理緩存&#xff0c;點一下按鈕就把當前Redis的緩存和前端緩存都清空。 功能非常簡單&#xff0c;創建一個控制器類CacheController&#xf…

SpringBoot PowerMockito 私有/靜態/方法/屬性

SpringBoot PowerMockito 私有/靜態/方法/屬性 1 PrepareForTest2 待測試類3 測試類 1 PrepareForTest PrepareForTest 是 PowerMockito 提供的一個注解&#xff0c;用于告訴 PowerMockito 哪些類需要被修改以允許使用 PowerMockito 的功能。 PowerMockito 主要用于修改 Java…