1.數據結構導論
1.1.基礎定義
樹是一種數據結構,它是由n(n>=1)個有限結點組成一個具有層次關系的集合。把它叫做“樹”是因
為它看起來像一棵倒掛的樹,也就是說它是根朝上,而葉朝下的。
在這種層次結構中有一個結點具有特殊的地位,這個結點稱為該樹的根結點,或稱為樹根。
這是一顆樹
術語 | 描述 |
---|---|
根結點 | 每棵樹都有一個根結點 50為這棵樹的根節點 |
子節點 | 一個節點的后繼節點被稱為子節點 50的子節點為27,75 |
父節點 | 若一個節點含有子節點,則這個節點稱為其子節點的父節點 50是27,75的父節點 |
兄弟節點 | 具有相同父節點的不同節點 61和85是兄弟節點 |
節點的度 | 一個節點含有的子節點的個數稱為該節點的度 75的度為2,61的度為1 |
葉節點 | 度為0的節點稱為葉節點 67、81都是葉子節點 |
樹的深度 | 樹中節點的最大層次 最深的節點為67,深度為4 |
節點的祖先 | 從根到該節點所經分支上的所有節點 50,75,61,71都是67的祖先 |
子孫 | 所有子節點以及子節點的子節點以及… 所有節點都是50的子孫 |
子樹 | 以某節點為根的樹 以75為根的子樹包括7個節點 |
邊 | 父子節點直接存在一條連邊 50和27之間有條邊 |
1.2.樹的特性
一棵樹中任意兩個結點有且僅有唯一的一條路徑連通。
一棵樹如果有n個節點, 那么它一定有n-1條邊。
在一棵樹中添加一條邊將會構成一個回路。
一棵有 n個節點的無向樹,所有節點的度數和為2× (𝑛 − 1) 。
樹形結構以樹和二叉樹最為常用,直觀來看,樹是以分支關系定義的層次結構。樹形結構
中元素之間有着明顯的層次關系,每一個元素可以和下層的多個元素相關, 但只能和上層
中一個元素相關。
1.3.樹與鏈表
鏈表是一棵特殊的樹。我們可以將鏈表的頭節點看作樹的根。樹的每個節點可以有多個
next ,我們稱之為子節點。所以鏈表是一棵每個節點都只有一個子節點的樹。
鏈表 | 樹 |
---|---|
頭節點 | 根結點 |
只有一個后繼節點(next) | 有一個或多個子節點(child) |
雙向鏈表包括前驅結點(pre) | 除了根結點外,每個節點都有唯一的父節點(parent) |
一對一關系 | 一對多關系 |
1.4.樹的基本操作-樹的存儲與創建
在創建一棵樹的時候, 使用什么方法去存儲呢?
可以采用與鏈表類似的方法。但由於子節點的數量不確定, 因此我們想到用vector來存儲
樹的子節點。
在一般樹中,子節點的數量沒有限制,所以常用的存儲方法是使用vector數組G, G[0]保
存編號為0的頂點連接到的所有頂點, 由於n個結點的樹只有n-1條邊, vector數組實際占
用的空間為O(n) 。
每讀到一條邊(u,v) ,如果不知道誰是父親誰是兒子,我們可以先在G[u]中添加一個v ,再
在G[v]中添加一個u 。
如果知道父子關系,可以只在父節點中添加兒子,而不用將邊保存兩份。
vector<int> G[100005]; //每個vector用來記錄所有子節點的編號
int n;
int main()
{
scanf("%d", &n);
for(int i = 1; i < n; i++){
int u, v;
scanf("%d %d",&u, &v);
G[u].push_back(v);
G[v].push_back(u);
}
return 0;
}
1.5.刪除樹的點和邊
刪除節點u時需要刪除該節點的所有邊。
Del(當前節點u){
for(u有連邊的節點v){
從G[v]中刪除u;
}
清空G[u];
}
刪除邊(u,v) 。
Del(邊(u,v)){
從G[v]中刪除u;
從G[u]中刪除v;
}
1.6.樹的遍歷
遍歷樹的方式有很多,最常用的方式是DFS 。
DFS(當前節點u){
for(u的所有子節點v){
DFS(v);
}
}
如果是在不知父子的情況下,保存了雙向邊,那么需要做如下處理。
DFS(當前節點u, 父節點p){
for(u有連邊的節點v){
if(v不是p)
DFS(v, u);
}
}
遍歷方式對比 | |
---|---|
數組 | 從下標0開始遍歷 |
鏈表 | 從頭節點開始遍歷 |
樹 | 從根節點開始遞歸遍歷 |
1.7.例題:葉子節點的數量
給出一棵n個節點的樹,節點編號為1-n(根節點編號為1),求這棵樹葉子結點的數量。
例如:
1─2─4─5
└─3
其中3和5是葉子節點,輸出2。
1.7.1.輸入
第一行:1個數n(1 < n <= 1000),表示樹的節點數量。
后面n-1行:每行2個數x y,表示節點x是節點y的父節點(1 <= x, y <= n)。
1.7.2.輸出
輸出1個數,表示這棵樹有多少個葉子節點。
1.7.3.輸入樣例
5
1 2
1 3
2 4
4 5
1.7.4.輸出樣例
2
1.7.5.分析
可以用一個bool數組t[i]來存儲每一個節點,t[i]表示第i個節點是否有子節點,如果沒有,i就是葉子節點。但要注意輸入 n-1次,判斷t[i]是否為false時要循環n次。t數組要開在外邊,否則在main函數里要memset初始化一下。
1.7.6.代碼
#include <bits/stdc++.h>
using namespace std;
bool t[1010];
int main() {
int n, ans = 0;
cin >> n;
for(int i = 1; i <= n - 1; i++) {
int a, b;
cin >> a >> b;
t[a] = true;
}
for(int i = 1; i <= n; i++) if(!t[i]) ans++;
cout << ans << endl;
return 0;
}