一、查找樹根
樹根的入度爲0。輸入數據時記錄即可。
一本通: 1336:【例3-1】找樹根和孩子
二、DFS序
void dfs(int x){
a[++m]=x; //a數組存儲dfs序
v[x]=1; //記錄點x被訪問過
for (int i=h[x]; i!=-1; i=ne[i]){
int y=ne[i];
if (v[y]) continue;
dfs(y);
}
a[++m]=x;
}
dfs序的特點:每個節點 x 的編號在序列中恰好出現再次。設這兩次出現的位置爲L[x]與R[x],那麼閉區間 【 L[x], R[x] 】就是以x爲根的子樹的dfs序。 這使我們在很多與樹相關的問題中,可以通過dfs 序把子樹統計轉化爲序列上的區間統計。
三、樹的深度
一本通: 1338:【例3-3】醫院設置
樹中各個節點的深度是一種自頂向下的統計信息。起初,我們已知節點的深度爲0 。若節點 x 的深度爲 d[x] , 則它的子節點 y 的深度就是 d[y] = d[x] +1。在深度優先遍歷的過程中結合自頂向下的遞推,就可以求出每個節點的深度d 。
void dfs(int x) {
v[x]=1;
for (int i=h[x]; i!=-1; i=ne[i]) {
int y=ne[i];
if (v[y]) continue;
d[y]=d[x]+1; //從父節點 x到子節點y遞推,計算深度
dfs(y) ;
}
}
四、樹的重心
給定一顆樹,樹中包含n個結點(編號1~n)和n-1條無向邊。
請你找到樹的重心,並輸出將重心刪除後,剩餘各個連通塊中點數的最大值。
重心定義:重心是指樹中的一個結點,如果將這個點刪除後,剩餘各個連通塊中點數的最大值最小,那麼這個節點被稱爲樹的重心。
輸入格式
第一行包含整數n,表示樹的結點數。
接下來n-1行,每行包含兩個整數a和b,表示點a和點b之間存在一條邊。
輸出格式
輸出一個整數m,表示重心的所有的子樹中最大的子樹的結點數目。
數據範圍
1 ≤ n ≤
輸入樣例
9
1 2
1 7
1 4
2 8
2 5
4 3
3 9
4 6
輸出樣例:
4
#include <bits/stdc++.h>
using namespace std;
const int N=100000+10;
//因爲是雙向邊
int h[N],e[2*N],ne[2*N],idx,ans=N;
bool b[N];//每個結點的訪問標記
int n;
int sum[2*N];
//加入a到b的一條邊
void add(int a,int b){
e[idx]=b;
ne[idx]=h[a];
h[a]=idx++;
}
void dfs(int u){
b[u]=true;//標記u這個點被搜過
//size是表示將u點去除後,剩下的子樹中數量的最大值;
//sum表示以u爲根的子樹的點的多少,初值爲1,因爲已經有了u這個點
int size=0;
sum[u]=1;
for (int i=h[u];i!=-1;i=ne[i]){
int j=e[i];
if (!b[j]){
dfs(j); //s是以j爲根節點的子樹中點的數量
size=max(size,sum[j]);
sum[u]=sum[u]+sum[j];
}
}
//n-sum表示的是減掉u爲根的子樹,整個樹剩下的點的數量
size=max(size,n-sum[u]);
ans=min(ans,size);
}
int main(){
cin>>n;
memset(h,-1,sizeof(h));
for (int i=0; i<n-1; i++){
int a,b;
scanf("%d%d",&a,&b);
add(a,b);
add(b,a);
}
dfs(1);
cout<<ans<<endl;
return 0;
}
五、圖的連通性劃分
通過多次的深度優先遍歷,可以劃分出一張無向圖中的各個連通塊。同理,對一個森林進行深度優先遍歷,可以劃分出森林中的每顆樹。如下面的代碼所示,cnt就是無向圖包含的連通塊的個數,v數組標記了每個點屬於哪一個連通塊。
void dfs(int x) {
v[x]=cnt;
for (int i=h[x]; i; i=ne[i]) {
int y=ne[i];
if (v[y]) continue;
dfs(y);
}
}
for (int i=1; i<=n; i+) {
if (!v[i]) {
cnt++;
dfs(i);
}
}
六、求樹的直徑
樹的直徑概念:一顆樹上存在的最長路徑。樹中距離最遠的兩個結點之間相隔的距離
方法:兩次dfs
先從任意一點P出發,找離它最遠的點Q,再從點Q出發,找離它最遠的點W,W到Q的距離就是樹的直徑
(另外 一種方法是DP)
-----------------------------------------------------------------------------------------------------------
證明如下:
①若P已經在直徑上,根據樹的直徑的定義可知Q也在直徑上且爲直徑的一個端點
②若P不在直徑上,我們用反證法,假設此時WQ不是直徑,AB是直徑
--->若AB與PQ有交點C,由於P到Q最遠,那麼PC+CQ>PC+CA,所以CQ>CA,易得CQ+CB>CA+CB,即CQ+CB>AB,與AB是直徑矛盾,不成立,如下圖(其中AB,PQ不一定是直線,畫成直線是爲了方便):
--->若AB與PQ沒有交點,M爲AB上任意一點,N爲PQ上任意一點。首先還是NP+NQ>NQ+MN+MB,同時減掉NQ,得NP>MN+MB,易知NP+MN>MB,所NP+MN+MA>MB+MA,
即NP+MN+MA>AB,與AB是直徑矛盾,所以這種情況也不成立,如下圖:
//轉自網絡
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define maxn 100000
using namespace std;
inline int read()
{
int x=0;
bool f=1;
char c=getchar();
for(; !isdigit(c); c=getchar()) if(c=='-') f=0;
for(; isdigit(c); c=getchar()) x=(x<<3)+(x<<1)+c-'0';
if(f) return x;
return 0-x;
}
struct node
{
int u,v,w,nex;
}edge[2*maxn+10];
int n,m,d[maxn+10],head[maxn+10],f_num,cnt=0,ans;
inline void add(int x,int y,int z)
{
cnt++;
edge[cnt].u=x;
edge[cnt].v=y;
edge[cnt].w=z;
edge[cnt].nex=head[x];
head[x]=cnt;
}
inline void dfs(int x,int fa)
{
if(ans<d[x])
{
ans=d[x];
f_num=x;
}
for(int i=head[x];i!=-1;i=edge[i].nex)
{
int j=edge[i].v;
if(j==fa)continue;
d[j]=d[x]+edge[i].w;
dfs(j,x);
}
}
int main()
{
memset(head,-1,sizeof(head));
n=read();m=read();
for(int i=1;i<=m;i++)
{
int x,y,z;
x=read();y=read();z=read();
add(x,y,z);
add(y,x,z);
}
dfs(1,0);
ans=0;
d[f_num]=0;
dfs(f_num,0);
printf("%d",ans);
return 0;
}
七、dfs的樹和圖搜索的基本代碼
void dfs(int x){
v[x]=1;
for (int i=h[x]; ~i; i=ne[i]){ //i爲-1時取反爲0, 非-1的i取反都不爲0
int y=ne[i];
if (v[y]) continue;
dfs(y);
}
}