樹是n(n >= 0)個結點的有限集。n=0時稱為空樹。在任意一顆非空樹中:
1、有且僅有一個特定的稱為根(Root)的結點;
2、當n>1時,其余結點可分為m(m >0)個互不相交的有限集T1、T2、....... 、Tm,其中每一個集合本身又是一顆樹,并且稱為根的子樹(SubTree);
3、m>0時,子樹的個數沒有限制,但它們一定是互不相交的。
結點分類
結點擁有的子樹數稱為結點的度 (Degree) 。度為 0的結點稱為葉結點(Leaf) 或終端結點;度不為0的結點稱為非終端結點或分支結點。除根結點之外,分支結點也稱為內部結點。樹的度是樹內各結點的度的最大值。下圖這棵樹結點的度的最大值是結點
D的度,為3,所以樹的度也為3。
結點間關系
線性表和樹的比較
樹的存儲結構
充分利用順序存儲和鏈式存儲結構的特點,完全可以實現對樹的存儲結構的
表示。三種不同的表示法:雙親表示法、孩子表示法、孩子兄弟表示法。
雙親表示法
在每個結點中,附設 一個指示器指示其雙親結點到鏈表中的位置
這樣的存儲結構,我們可以根據結點的 parent 指針很容易找到它的雙親結點,所
用的時間復雜度為 O(1),直到 parent 為-1時,表示找到了樹結點的根。可如果我們
要知道結點的孩子是什么,對不起,請遍歷整個結構才行。
這真是麻煩,能不能改進一下呢?
當然可以。我們 增加一個結點最左邊孩子的域,不妨叫包長子域,這樣就可以很
容易得到結點的孩子。如果沒有孩子的結點,這個長子域就設置為-1:
另外一個問題場景,我們很關注各兄弟之間的關系,雙親表示法無法體現這樣的
關系,那我該怎么辦?嗯,可以增加一個右兄弟域來體現兄弟關系,也就是說,每
個結點 如果它存在右兄弟,則記錄下右兄弟的下標。同樣的,如果右兄弟不存在,則賦值為 -1:
孩子表示法
每個結點有多個指針域,其中每個指針指向一棵子樹的根結點,我們把這種方法叫做多重鏈表表示法
孩子表示法:把每個結點的孩子結點排列起來,以單鏈表作存儲結構,則 n個結點有 n個孩子鏈表,如果是葉子結點則此單鏈表為空。然后那個指針又組成一個線性表,采用順序存儲結構,存放進一個一維數組中。(怎么感覺和HashMap的原理很像)
雙親孩子表示法
二叉樹
二叉樹( Bina Tree) n(n >= 0) 個結點的有限集合,該集合或者為空集(稱為空二叉樹),或者由一個根結點和兩棵互不相交的、分別稱為根結點的左子樹和右子樹的二叉樹組成。
特點
- 每個結點最多有兩棵子樹,所以二叉樹中不存在度大于2的結點。注意不是只有兩棵子樹,而是最多有。沒有子樹或者有一棵子樹都是可以的
- 左子樹和右子樹是有順序的,次序不能任意顛倒。就像人是雙手、雙腳,但顯然左手、左腳和右手、右腳是不一樣的,右手戴左手套、右腳穿左鞋都會極其別扭和受。
- 即使樹中某結點只有一棵子樹,也要區分它是左子樹還是右子樹。
5中基本形態
- 空二叉樹
- 只有一個根節點
- 根節點只有左子樹
- 根節點只有右子樹
- 根節點既有左子樹又有右子樹
特殊二叉樹
- 斜樹 :左斜樹 &右斜樹
- 滿二叉樹:在一顆二叉樹中,如果所有分支結點都存在左子樹和右子樹,并且所有 葉子都在同一層上,這樣的二叉樹稱為滿二叉樹
特點:- 葉子只能出現在最下一層。出現在其它層就不可能達成平衡。
- 非葉子結點的度一定是2 。否則就是"缺胳膊少腿" 了
-
在同樣深度的二叉樹中,滿二叉樹的結點個數最多,葉子數最多。
image.png
3.完全二叉樹:對一棵具有 n個結點的二叉樹按層序編號,如果編號為 i(l<= i <=n) 的結點與同樣深度的滿二叉樹中編號為i的結點在二叉樹中位置完全相同,則這棵 叉樹稱為完全二叉樹。
滿二叉樹一定是完全二叉樹,但完全二叉樹不一定是滿二叉樹。可以理解為完全二叉樹是滿二叉樹中截取的一部分。
特點:
1. 葉子結點只能出現在最下兩層。
2. 最下層的葉子一定集中在左部連續位置
3. 倒數二層,若有葉子結點,一定都在右部連續位置。
4. 如果結點度為 1,則該結點只有左孩子,即不存在只有右子樹的情況。
5. 同樣結點數的 叉樹,完全二叉樹的深度最小。
二叉樹的性質
在二叉樹的第i層上最多有2^(i-1) 個節點 。(i>=1)
深度為k的二叉樹至多有 2^k - 1個結點
-
對任何一顆二叉樹 ,如果其終端結點數(葉子結點樹)為n0,度為2 的結點數為n2,則n0=n2+1。
推導過程 根據兩個公式:- n=n0+n1+n2 n表示二叉樹中的節點總個數,n1表示度數為1的節點個數
- n-1=2n2+n1 通過觀察二叉樹我們可知,除了根節點之外,其余的任何節點都有一個入口分支,其他節點都有一個入口分支,那么節點的總分支數等于節點個數減一,度數為2的節點有2個出口分支,度數為一的有1個出口分支,度數為0的節點沒有出口分支 所以總的分支個數為 2n2+n1
-
.在完全二叉樹中,具有n個節點的完全二叉樹的深度為[log2n]+1,其中[log2n]+1是向下取整。
image.png 若對含 n 個結點的完全二叉樹從上到下且從左至右進行 1 至 n 的編號,則對完全二叉樹中任意一個編號為 i 的結點:
(1) 若 i=1,則該結點是二叉樹的根,無雙親, 否則,編號為 [i/2] 的結點為其雙親結點;
(2) 若 2i>n,則該結點無左孩子, 否則,編號為 2i 的結點為其左孩子結點;
(3) 若 2i+1>n,則該結點無右孩子結點, 否則,編號為2i+1 的結點為其右孩子結點。
二叉樹的順序存儲結構
模擬二叉樹存儲結構:不存在得到結點用^代替
二叉樹鏈表
二叉樹每個結點最多有兩個孩子,所以為她設計一個數據域和兩個指針域
結構示意圖
二叉樹遍歷
二叉樹的遍歷是指從根結點出發,按照某種次序依次訪問二叉樹中所有結點。使得每個結點被訪問一次且僅被訪問一次。
分類:
1.前序遍歷:規則是若二叉樹為空,則空操作返回,否則先訪問根結點,然后前序遍歷左子樹,再前序遍歷右子樹。
2.中序遍歷:規則是若樹為空,則空操作返回,否則從根結點開始(注意并不是先訪問根結點) ,中序遍歷根結點的左子樹,然后是訪問根結點,最后中序遍歷右子樹。
3.后序遍歷:規則是若樹為空,則空操作返回,否則從左到右先葉子后結點的方式遍歷訪問左右子樹,最后是訪問根結點 。
4.層序遍歷:規則是若樹為空,則空操作返回,否則從樹的第一層,也就是根結點開始訪問,從上而下逐層遍歷,在同一層中 ,按從左到右的順序對結點逐個訪問。
理由:對于計算機來說,它只有循環、判斷等方式來處理出具,也就是說,它只會處理線性序列,而我們剛才提到的四種遍歷方法,其實都是在把樹中的結點變成某種意義的線性序列,這就給程序的實現帶來了好處。
算法實現
前序遍歷
void preOrderTraverse(Node N){
if(N == null)
return;
printf("%s",N.data);
preOrderTraverse(N.lchild);//先遍歷左子樹
preOrderTraverse(N.rchild);//再遍歷又子樹
}
中序遍歷
void inOrderTraverse(Node N){
if(N == null)
return;
inOrderTraverse(N.lchild);//先遍歷左子樹
printf("%s",N.data);
inOrderTraverse(N.rchild);//再遍歷又子樹
}
后序遍歷
void postOrderTraverse(Node N){
if(N == null)
return;
postOrderTraverse(N.lchild);//先遍歷左子樹
postOrderTraverse(N.rchild);//再遍歷又子樹
printf("%s",N.data);
}
二叉樹的建立
#include<iostream>
using namespace std;
typedef struct node
{
struct node *leftChild;
struct node *rightChild;
char data;
}BiTreeNode, *BiTree;
void createBiTree(BiTree &T)
{
char c;
cin >> c;
if('#' == c)
T = NULL;
else
{
T = new BiTreeNode;
T->data = c;
createBiTree(T->leftChild);
createBiTree(T->rightChild);
}
}
int main()
{
BiTree T;
createBiTree(T);
return 0;
}
線索二叉樹
**指向前驅和后繼的指針稱為線索,加上線索的二叉鏈表稱為線索鏈衰,相應的二叉樹就稱為線索二叉樹
**
如果所用的二叉樹需經常遍歷或查找結點時需要某種遍歷序列中的前驅和后繼,那么采用線索二叉鏈表的存儲結構就是非常不錯的選擇
樹的轉換
將樹轉化為二叉樹
- 加線。在所有兄弟結點之間加一條連線
- 去錢。對樹中每個結點,只保留它與第一個孩子結點的連線,刪除色與其他孩
子結點之間的連線。 - 層次調整。以樹的根結點為軸心,將整棵樹順時針旋轉一定的角度,使之結構
層次分明。注意第一個孩子是二叉樹結點的左孩子,兄弟轉換過來的孩子是結點的右孩子。
森林轉化為二叉樹
- 把每個樹轉換為二叉樹
-
第一棵二叉樹不動,從第二棵二叉樹開始,依次把后一棵二叉樹的根結點作為
前一棵二叉樹的根結點的右孩子,用線連接起來。當所有的二叉樹連接起來后就得到了由森林轉換來的二叉樹。
image.png
二叉樹轉換為樹
- 加線。若某結點的左孩子結點存在,則將這個左孩子的右孩子結點、右孩子的
右孩子結點、右孩子的右孩子的右孩子結點…,反正就是左孩子的n個右孩子結點都作為此結點的孩子。將該結點與這些右孩子結點用線連接起來。 - 去錢。刪除原二叉樹中所有結點與其右孩子結點的連線。
-
層次調整。使之結構層次分明。
image.png
二叉樹轉換為森林
- 從根結點開始,若右孩子存在,則把與右孩子結點的連線刪除,再查看分離后的二叉樹,若右孩子存在,則連續刪除除……,直到所有右孩子連線都刪除為止,得到分離的二叉樹。
-
再將每棵分離后的二叉樹轉換為樹即可。
image.png
樹與森林的遍歷
樹
- 先根遍歷樹,即先訪問樹的根結點,然后依次先根遍歷根的每棵子樹。
- 后根遍歷,即先依次后根遍歷每棵子樹,然后再訪問根結點。
森林
- 前序遍歷: 先訪問森林中第一棵樹的根結點,然后再依次先根遍歷根的每棵子樹,再依放用同樣方式遍歷除去第一棵樹的剩余樹構成的森林。
- 后序遍歷: 是先訪問森林中第一棵樹,后根遍歷的方式遍歷每棵子樹,然后再
訪問根結點,再依次同樣方式遍歷除去第一棵樹的剩余樹構成的森林。