日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

数据结构-树与二叉树

發(fā)布時間:2025/3/15 编程问答 22 豆豆
生活随笔 收集整理的這篇文章主要介紹了 数据结构-树与二叉树 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

文章目錄

  • 一:樹
    • (1)樹的概念
    • (2)樹的一些基本術(shù)語
    • (3)樹的表示
      • A:孩子兄弟表示法
      • B:雙親表示法
      • C:孩子表示法
  • 二:二叉樹
    • (1)二叉樹的概念
    • (2)特殊的二叉樹
    • (3)二叉樹的性質(zhì)
    • (4)二叉樹的順序存儲結(jié)構(gòu)
      • A:二叉樹順序存儲結(jié)構(gòu)
      • B:堆
    • (5)鏈式存儲結(jié)構(gòu)
      • A:二叉樹的鏈式存儲結(jié)構(gòu)
      • B:二叉樹的遍歷
        • ①:層次遍歷
        • ②:深度優(yōu)先遍歷
  • 三:二叉樹相關(guān)問題
    • (1)基礎(chǔ)問題
    • (2)相關(guān)oj題
  • 四:相關(guān)代碼

一:樹

(1)樹的概念


樹是一種非線性的數(shù)據(jù)結(jié)構(gòu),是有n個(n?\geqslant? 0,當n為0時叫做空樹)有限結(jié)點組成的一個具有層次關(guān)系的集合。把它稱為樹,是因為它和現(xiàn)實中的數(shù)十分相像,只不過是倒掛的樹

如下,樹中有一個非常特殊的結(jié)點,稱其為根節(jié)點,根節(jié)點沒有前驅(qū),除根結(jié)點外,剩余每個結(jié)點又可以看作是以它為根節(jié)點所組成的子樹,因此每一個結(jié)點都可以說是一個子樹的根節(jié)點,所以樹的定義時具有遞歸性質(zhì)的

(2)樹的一些基本術(shù)語

下面的這些概念,了解即可,不用刻意記憶

術(shù)語描述舉例
結(jié)點A,B,C等都是結(jié)點,結(jié)點不僅包含數(shù)據(jù)元素,而且包含指向子樹的分支。A結(jié)點不僅包含元素A,而且包含三個指向子樹的指針
結(jié)點的度結(jié)點擁有的子樹或分支個數(shù)A結(jié)點有三顆子樹,A的度為3
樹的度樹中各結(jié)點度的最大值A,D結(jié)點的度最大為3,故樹的度為3
葉子結(jié)點度為0的結(jié)點F,G,I,J,K,L,M均為葉子結(jié)點
非葉結(jié)點度不為0的結(jié)點A,B,C,D,E,H,均為非葉結(jié)點
孩子某結(jié)點子樹的根A結(jié)點的孩子為B,C,D
雙親與孩子的定義對應B,C,D的雙親都是A
兄弟同一個雙親的孩子互為兄弟B,C,D互為兄弟
祖先從根到某結(jié)點的路徑上所有的結(jié)點,都是該結(jié)點的祖先K的祖先是A,B,E
子孫以某結(jié)點為根的子樹中的所有結(jié)點D的子孫是H,I,J,M
層次根節(jié)點為第一層,根的孩子是第二層次,以此類推結(jié)點F處在第三層
結(jié)點的深度是指從根節(jié)點到該結(jié)點路徑上的結(jié)點個數(shù)\
結(jié)點的高度從某結(jié)點往下走可能到達多個葉子結(jié)點,對應了通往這些葉子結(jié)點的路徑,其中最長的那條路徑上的結(jié)點的個數(shù)稱其為結(jié)點的高度D的高度為3
樹的高度(深度)樹中結(jié)點的最大層次根節(jié)點的高度就是樹的高度
堂兄弟雙親在同一層的結(jié)點互為堂兄弟G和H互為堂兄弟
有序樹樹中結(jié)點的子樹從左至右是有次序的,不能交換\
無序樹樹中結(jié)點的子樹沒有順序,可以任意交換\
豐滿樹除了最底層外,其他層都是滿的\
森林若干互不相交的樹的集合上面的樹,將根結(jié)點A去除,剩余的就一個森林

(3)樹的表示

很多時候,樹的度是不能夠確定的,也就是說一個結(jié)點有多少個孩子是不可知的。Windows系統(tǒng)的目錄采用的就是樹形結(jié)構(gòu),我們從沒有聽過在一個文件夾里最多可以創(chuàng)建多少個文件等這樣的限制(前提是不受存儲限制)。所以使用單純的線性對應的結(jié)構(gòu)去存儲樹,是不可行的。
樹的存儲結(jié)構(gòu)主要有:孩子兄弟表示法,雙親表示法和孩子表示法

A:孩子兄弟表示法

樹的孩子兄弟表示法:一個結(jié)點有三個域,一個數(shù)據(jù)域和兩個指針域,第一個指針指向該結(jié)點的第一個孩子結(jié)點,第二個指針域指向該結(jié)點的兄弟結(jié)點。對于其孩子結(jié)點也是如此定義。

B:雙親表示法

樹的雙親表示法:使用一組連續(xù)的存儲空間來存放結(jié)點,結(jié)點按一定順序(一般是從上到下,從左到右)依次存放在數(shù)組中,數(shù)組的下標表示了該結(jié)點的位置,每個結(jié)點有一個數(shù)據(jù)域和一個指針域,指針域保存的是該結(jié)點的雙親結(jié)點在數(shù)組中的下標

C:孩子表示法

樹的孩子表示法其實就是圖的鄰接表存儲結(jié)構(gòu),這里不再詳細敘述了

二:二叉樹

(1)二叉樹的概念


二叉樹應該是這一棵樹:每個結(jié)點最多有兩顆子樹,也即二叉樹的度最大為2,同時二叉樹的子樹有次序之分,不能顛倒

(2)特殊的二叉樹

  • 滿二叉樹:滿二叉樹它的每一層的結(jié)點數(shù)都達到了最大值。如果一個二叉樹有k層,且其結(jié)點總數(shù)為2k-1個,那么它就是滿二叉樹
  • 完全二叉樹:完全二叉樹概念有點抽象。簡單點來說:一個完全二叉樹是由對應的滿二叉樹進行刪除而來的,刪除的時候必須從上到下,從右向左,不能跳著刪除。

(3)二叉樹的性質(zhì)

  • 規(guī)定根節(jié)點層數(shù)為1,則一顆非空二叉樹第i層最多有2i-1個結(jié)點
  • 規(guī)定根節(jié)點的層數(shù)為1,則深度為h的二叉樹最大結(jié)點數(shù)為2h-1個。
    題目:在具有 2n 個結(jié)點的完全二叉樹中,葉子結(jié)點個數(shù)為?

  • 對任意一顆二叉樹,葉子結(jié)點(度為0的結(jié)點)總比度為2的結(jié)點多1個
    推導過程如下:
    /
    首先需要明白度為0的結(jié)點,引出0個分支,度為1的結(jié)點引出1個分支,度為2的結(jié)點引出3個分支,依次類推。 假設一個二叉樹中葉子結(jié)點有n0個,那么它引出0×n0條分支,單分支結(jié)點數(shù)為n1個,它引出1×n1條分支,雙分支結(jié)點數(shù)為n2,它引出2×n2條分支。于是總的結(jié)點數(shù)為n0+n1+n2,總分支數(shù)為n1+2×n2任何一個樹,總分數(shù)=總結(jié)點數(shù)-1,于是由②可知n1+2×n2=n0+n1+n2-1,化簡得到n0=n2+1.
    /
    這條性質(zhì)要注意靈活應用,很多時候不會這樣直接考
    /
    比如某道題問道“二叉樹的總的結(jié)點數(shù)為n,問空指針多少?”我們可以把所有的空指針都看作葉子結(jié)點,也就是說這個二叉樹所有節(jié)點都是雙分支結(jié)點,根據(jù)葉子結(jié)點數(shù)=雙分支結(jié)點數(shù)+1,所以在本樹中,葉子結(jié)點數(shù)(空指針數(shù))=雙分支結(jié)點數(shù)(總的結(jié)點數(shù))+1,也就是n+1.

  • 具有N個結(jié)點的完全二叉樹高度為 ?log2N?\left \lfloor log_{2}N\right \rfloor?log2?N?或者是?log2N+1?\left \lceil log_{2}N+1\right \rceil?log2?N+1?

(4)二叉樹的順序存儲結(jié)構(gòu)

A:二叉樹順序存儲結(jié)構(gòu)

對于一顆完全二叉樹,從根節(jié)點開始,從上到下,從左至右依次編號,按照編號存放在一個數(shù)組里,這就是完全二叉樹的順序存儲結(jié)構(gòu)。比如一個完全二叉樹,根節(jié)點編號為0,那么它的左孩子編號就是2×0+1,右孩子編號就是2×0+2,該二叉樹中任意一個結(jié)點(假設編號為i),其左孩子編號為2×i+1,右孩子為2×i+2.

之所以這里要限定為完全二叉樹,并不是說普通的二叉樹不能用數(shù)組存儲。既然一顆二叉樹要用數(shù)組存,那么它就一定要發(fā)揮數(shù)組的優(yōu)勢,比如說通過索引確定孩子的編號,而對于一個普通的二叉樹說來說,其結(jié)點分布可能是不均勻的,所以要使用數(shù)組存儲普通的二叉樹,必須要把這個樹轉(zhuǎn)化為其對應的完全二叉樹的形態(tài),勢必會造成大量空間的浪費

其實,數(shù)組在這里的真正用處是存儲堆,因為堆就是完全二叉樹。

B:堆

堆和堆排序的內(nèi)容,篇幅較長,見下面這篇文章

數(shù)據(jù)結(jié)構(gòu)-堆與堆排序

(5)鏈式存儲結(jié)構(gòu)

A:二叉樹的鏈式存儲結(jié)構(gòu)

使用二叉鏈表可以有效的存儲二叉樹。二叉鏈表中的每一個結(jié)點,有一個數(shù)據(jù)域和兩個指針域,這兩個指針域分別指向該結(jié)點的左孩子和右孩子

二叉鏈表結(jié)構(gòu)定義如下

typedef struct BTNode {stuct BTNode* lchild;//左孩子指針stuct BTNode* rchild;//右孩子指針DataType val;//數(shù)據(jù)域 }

B:二叉樹的遍歷

如果要用一個詞來概括二叉樹的話,那么我選擇“遍歷”。遍歷可謂是二叉樹中最重要的運算,是其他一切運算的前提。

所謂遍歷,通俗的理解就是全部訪問一遍,在單鏈表那一章為什么不過度強調(diào)遍歷這個詞呢,因為單鏈表的遍歷就只有一種情況。對于二叉樹來講,其邏輯結(jié)構(gòu)的特點,導致了它有不同的遍歷方法,每個遍歷又有其特定的應用場景。

遍歷從大的方面來講可以分為兩種:廣度優(yōu)先遍歷(BFS),深度優(yōu)先遍歷(DFS)。對于二叉樹來說,它有四種遍歷方法,層次遍歷屬于BFS,先序,中序,后序遍歷屬于DFS

①:層次遍歷

一句話概括:自上而下,先左后右

②:深度優(yōu)先遍歷

以下動圖來自天勤計算機考研

天勤計算機考研

大部分人其實都知道一些二叉樹的遍歷的口訣,例如先序遍歷是“根左右”,中序遍歷“左根右”,或許遍歷左右根。通過這樣的口訣,也能夠很快的寫出樹的各種遍歷方式,但如果問到為什么是這樣,很多人卻無法說清楚。

呈現(xiàn)出不同的遍歷的方式的原因在于二叉樹的遞歸結(jié)構(gòu)和訪問時機的不同
如下圖,這三種遍歷方式本質(zhì)是一樣的,每個結(jié)點都會經(jīng)歷三次訪問,二叉樹默認遞歸時其實就是先序遍歷,也就是先根節(jié)點,再左,后右。在其遞歸的過程,在不同時機訪問,就會造成不同的遍歷結(jié)果。

先序遍歷是指:第一次遇到這個結(jié)點訪問,第二次遇到或第三次遇到跳過該結(jié)點直接訪問下一個結(jié)點

中序遍歷是指:第一次遇到結(jié)點跳過,第二次再遇到結(jié)點訪問,第三次遇到跳過


后序遍歷是指:前兩次遇見結(jié)點不訪問,最后一次遇見時訪問

三:二叉樹相關(guān)問題

二叉樹不像鏈表,棧隊列那些結(jié)構(gòu)一樣,對于二叉樹,考察的主要對于它的應用。二叉樹的應用一定與遞歸離不開,遞歸代碼簡單,但是十分抽象。遞歸包括兩點:子問題和返回條件,要寫出遞歸代碼,首先不能把問題看的太復雜,要把問題細化,細化成最小的問題。對于二叉樹來說,其子問題就是劃分左子樹和右子樹,返回條件為空樹
為了方便說明,建立這樣一顆樹

(1)基礎(chǔ)問題

1:三種遍歷(遞歸)
下面的三種遍歷采用遞歸的方式,遞歸的方法代碼短小精悍,但是理解起來較為抽象

  • 先序遍歷


    leetcode中的這道先序遍歷的題,也可以用遞歸去做

二叉樹前序遍歷

  • 中序遍歷

  • 后序遍歷

2:求解樹的結(jié)點個數(shù)


更優(yōu)的寫法(分治法思想)

3:求解樹的葉子結(jié)點個數(shù)
假如沒有結(jié)點(root=NULL),那么就是0,假如只有一個結(jié)點A(葉子結(jié)點),那么它就是葉子結(jié)點,然后A的葉子結(jié)點就是其左子樹+右子樹

4:求解樹的高度
如果沒有結(jié)點(root=NULL),那么高度為0,如果只有一個結(jié)點A(葉子結(jié)點),那么高度為1,所以A的高度就是A的左子樹高度和右子樹高度之比,取大者+1

5:求二叉樹第K層的結(jié)點個數(shù)
也是一個遞歸過程。比如要求某個樹的第三層的結(jié)點個數(shù),那么就等于求其子樹的第二層的結(jié)點個數(shù),再等價于求其子樹的子樹的第一層的結(jié)點個數(shù)。傳入下一層時,讓k-1,同時傳入的是其孩子結(jié)點,這樣一旦root不等于空,k不等于1,就能一直遞歸下去,直到k=1時,不斷向上返回即可

6:找尋某個結(jié)點
如果root為空,返回NULL,如果root的值是X,則返回root;否則,該節(jié)點找不到,該去其孩子結(jié)點找,先定義一個左尋找結(jié)點,遞歸進入左孩子,如果左孩子找不到則什么也不返回,如果左孩子返回則不執(zhí)行右孩子,否則再去右孩子找。如果左右孩子都不返回,那么這棵樹沒有該節(jié)點

7:銷毀二叉樹
后序銷毀

8:二叉樹的層次遍歷
二叉樹的層次遍歷需要借助到隊列。先將根節(jié)點入隊列,然后如果隊列不空,就出隊,出隊時看其孩子是否存在,如果存在就將其孩子入隊列,然后迭代即可。

所以下面的操作中,會借助到隊列的相關(guān)操作,具體見
數(shù)據(jù)結(jié)構(gòu)-線性表之棧和隊列
同時需要注意,隊列中結(jié)點的數(shù)據(jù)域,也即val,可以設置為BTNode*,也就是數(shù)據(jù)存儲的是樹節(jié)點指針。

9:判斷是否為完全二叉樹
判斷是否為完全二叉樹可以從層序遍歷入手

所以,首先讓其按照層序遍歷次序入隊,同時出隊,如果遇到NULL,跳出循環(huán),接著再對后半部分進行出隊,如果出隊的過程中出現(xiàn)非空元素,那么它就不是完全二叉樹,反之則是

如下

(2)相關(guān)oj題

二叉樹oj題

四:相關(guān)代碼

BinaryTree.h

#pragma once #include <stdio.h> #include <stdlib.h> #include <assert.h> #include <stdbool.h>typedef struct BTNode {char val;struct BTNode* _lchild;struct BTNode* _rchild; }BTNode;typedef struct ListNode {struct ListNode* _next;BTNode* val;//這里不用直接把樹的結(jié)點入隊列,直接入指針最方便 }ListNode; typedef struct Queue {ListNode* _front;ListNode* _rear; }queue; typedef BTNode* DataType;BTNode* CreatNode(char x);//申請結(jié)點void PreOrder(BTNode* root);//前序遍歷 void InOrder(BTNode* root);//中序遍歷 void PostOrder(BTNode* root);//后序遍歷 int TreeSize(BTNode* root);//求樹的結(jié)點 int LeafSize(BTNode* root);//求葉子結(jié)點數(shù) int TreeDeep(BTNode* root);//求樹的深度 int TreeLevelSize(BTNode* root,int k);//求樹的第K層的結(jié)點個數(shù) BTNode* TreeFind(BTNode* root, char x);//查找某個結(jié)點 void DestoryTree(BTNode* root);//銷毀void level(BTNode* root);//層次遍歷 int isCompleteTree(BTNode* root);//是否為完全二叉樹

BinaryTree.c

#include "BinaryTree.h"void QueuePush(queue* pq, DataType x) {assert(pq);ListNode* NewNode = (ListNode*)malloc(sizeof(ListNode));assert(NewNode);NewNode->val = x;NewNode->_next = NULL;if (pq->_rear == NULL)//插入第一個節(jié)點,rear和front要同時指向newnode{pq->_rear = NewNode;pq->_front = NewNode;}else//正常情況{pq->_rear->_next = NewNode;pq->_rear = NewNode;} } BTNode* QueuePop(queue* pq) {assert(pq);assert(pq->_front);if (pq->_front == pq->_rear)//注意特殊情況,隊列中只剩一個元素,需要特殊處理,不然發(fā)生錯誤{BTNode* receive = pq->_front->val;//用于返回樹節(jié)點ListNode* release = pq->_front;//釋放結(jié)點pq->_front = NULL;pq->_rear = NULL;free(release);return receive;}else//正常情況{BTNode* receive = pq->_front->val; ListNode* release = pq->_front;pq->_front = pq->_front->_next;free(release);return receive;}}void PreOrder(BTNode* root) {if (root == NULL){printf("NULL ");return;}printf("%c ", root->val);PreOrder(root->_lchild); PreOrder(root->_rchild); } void InOrder(BTNode* root) {if (root == NULL){printf("NULL ");return;}InOrder(root->_lchild);printf("%c ", root->val);InOrder(root->_rchild); } void PostOrder(BTNode* root) {if (root == NULL){printf("NULL ");return;}PostOrder(root->_lchild);PostOrder(root->_rchild);printf("%c ", root->val); }BTNode* CreatNode(char x) {BTNode* NewNode = (BTNode*)malloc(sizeof(BTNode));NewNode->_lchild = NULL;NewNode->_rchild = NULL;NewNode->val = x;}int TreeSize(BTNode* root) {if (root == NULL)return 0;elsereturn 1+ TreeSize(root->_lchild) + TreeSize(root->_rchild);} int LeafSize(BTNode* root) {if (root == NULL)return 0;if (root->_lchild == NULL && root->_rchild == NULL)return 1;return LeafSize(root->_lchild) + LeafSize(root->_rchild);} int TreeDeep(BTNode* root) {if (root == NULL)return 0;if (root->_lchild == NULL && root->_rchild == NULL)return 1;return TreeDeep(root->_lchild) > TreeDeep(root->_rchild) ? TreeDeep(root->_lchild) + 1 : TreeDeep(root->_rchild) + 1;} int TreeLevelSize(BTNode* root, int k) {if (root == NULL)return 0;if (k == 1)return 1;return TreeLevelSize(root->_lchild, k - 1) + TreeLevelSize(root->_rchild, k - 1); } BTNode* TreeFind(BTNode* root, char x) {if (root == NULL)return NULL;if (root->val == x)return root;BTNode* findnode_left = TreeFind(root->_lchild, x);if (findnode_left)return findnode_left;BTNode* findnode_right = TreeFind(root->_rchild, x);if(findnode_right)return findnode_right;return NULL; } void DestoryTree(BTNode* root) {if (root == NULL)return;DestoryTree(root->_lchild);DestoryTree(root->_rchild);free(root);}void level(BTNode* root) {queue q;q._front = NULL;q._rear = NULL;//隊列的初始化BTNode* p;//中轉(zhuǎn)結(jié)點QueuePush(&q, root);//首先入根節(jié)點while (q._front != NULL)//隊列不空,進行出隊列{p = QueuePop(&q);//出來一個訪問一個,然后看其依次看其左右孩子是否存在,如果存在則入隊列printf("%c ", p->val);if (p->_lchild != NULL){QueuePush(&q, p->_lchild);}if (p->_rchild != NULL){QueuePush(&q, p->_rchild);} } }//返回1是完全,返回不是完全 int isCompleteTree(BTNode* root) {queue q;q._front = NULL;q._rear = NULL;if (root == NULL)return 1;//空樹是完全二叉樹QueuePush(&q, root);while (q._front != NULL){BTNode* midlle_node = QueuePop(&q);if (midlle_node == NULL)break;//如果為空,說明層次遍歷結(jié)束QueuePush(&q, midlle_node->_lchild);QueuePush(&q, midlle_node->_rchild);}while (q._front != NULL){BTNode* middle_front = QueuePop(&q);if (middle_front!=NULL)//如果后半部分出現(xiàn)了非空,那么就肯定不是完全二叉樹{return 0;}}return 1;}

test.c

#include "BinaryTree.h"void test() {BTNode* A = CreatNode('A');BTNode* B = CreatNode('B');BTNode* C = CreatNode('C');BTNode* D = CreatNode('D');BTNode* E = CreatNode('E');A->_lchild = B;A->_rchild = C;B->_lchild = D;C->_lchild = E;//printf("A高度為%d\n", TreeDeep(A));//printf("B高度為%d\n", TreeDeep(B));//printf("A的第二層的高度為%d\n", TreeLevelSize(A, 1));//printf("結(jié)點的D的地址是%p\n", D);//printf("調(diào)用函數(shù)找到的D的地址是%p\n", TreeFind(A, 'D'));if(isCompleteTree(A)==1)printf("是完全二叉樹\n");elseprintf("不是完全二叉樹");}int main() {test(); }

總結(jié)

以上是生活随笔為你收集整理的数据结构-树与二叉树的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。