树&图总结

定义

树(tree)是包含n(n>=1)个结点,(n-1)条边的有穷集,其中:

(1)每个元素称为结点(node);

(2)有一个特定的结点被称为根结点或树根(root)。

(3)除根结点之外的其余数据元素被分为m(m≥0)个互不相交的集合T1,T2,……Tm-1,其中每一个集合Ti(1<=i<=m)本身也是一棵树,被称作原树的子树(subtree)。

树也可以这样定义:树是由根结点和若干颗子树构成的。树是由一个集合以及在该集合上定义的一种关系构成的。集合中的元素称为树的结点,所定义的关系称为父子关系。父子关系在树的结点之间建立了一个层次结构。在这种层次结构中有一个结点具有特殊的地位,这个结点称为该树的根结点,或称为树根。

我们可以形式地给出树的递归定义如下:

单个结点是一棵树,树根就是该结点本身。

设T1,T2,..,Tk是树,它们的根结点分别为n1,n2,..,nk。用一个新结点n作为n1,n2,..,nk的父亲,则得到一棵新树,结点n就是新树的根。我们称n1,n2,..,nk为一组兄弟结点,它们都是结点n的子结点。我们还称T1,T2,..,Tk为结点n的子树。

空集合也是树,称为空树。空树中没有结点。

结点的度:一个结点含有的子结点的个数称为该结点的度;

叶结点或终端结点:度为0的结点称为叶结点;

非终端结点或分支结点:度不为0的结点;

双亲结点或父结点:若一个结点含有子结点,则这个结点称为其子结点的父结点;

孩子结点或子结点:一个结点含有的子树的根结点称为该结点的子结点;

兄弟结点:具有相同父结点的结点互称为兄弟结点;

树的度:一棵树中,最大的结点的度称为树的度;

结点的层次:从根开始定义起,根为第1层,根的子结点为第2层,以此类推;

树的高度或深度:树中结点的最大层次;

堂兄弟结点:双亲在同一层的结点互为堂兄弟;

结点的祖先:从根到该结点所经分支上的所有结点;

子孙:以某结点为根的子树中任一结点都称为该结点的子孙。

森林:由m(m>=0)棵互不相交的树的集合称为森林;

存储方式及模板

动态数组(常用)

cin>>n;
for(int i=1;i<=n;i++){
	cin>>u>>v;
	g[u].push_back(v);
	g[v].push_back(u);
}

邻接矩阵(常用于传递闭包)

cin>>n;
for(int i=1;i<=n;i++){
	cin>>u>>v;
	g[u][v]=true;
}

例题

#10032. 二叉树的序遍历

思路

使用dfs遍历整棵树,再将dfs序输出(前序则最先输出,中序于中间输出,后序则最后输出,符合根左右、左根右和左右根的顺序

Code

#include<bits/stdc++.h>
using namespace std;
int n;
int l,r;
vector<int>v[30];
//第一次写代码的时候函数调用错了,故一定要注意细节qwq
void dfs(int x){//前序遍历
	if(x==0)return;
	cout<<x<<" ";
	dfs(v[x][0]);
	dfs(v[x][1]);
}
void dfs1(int x){//中序遍历
	if(x==0)return;
	dfs1(v[x][0]);
	cout<<x<<" ";
	dfs1(v[x][1]);
}
void dfs2(int x){//后序遍历
	if(x==0)return;
	dfs2(v[x][0]);
	dfs2(v[x][1]);
	cout<<x<<" ";
}
int main(){
	cin>>n;
	for(int i=1;i<=n;i++){
		cin>>l>>r;
		v[i].push_back(l);
		v[i].push_back(r);
	}
	dfs(1);//从根结点开始遍历(题目中说了根结点一定为1)
	cout<<endl;
	dfs1(1);
	cout<<endl;
	dfs2(1); 
	return 0;
}

#10033. 二叉树的叶节点和

思路

由于叶结点的特点是,除了根结点外,度数必为1,则可编写代码

Code

#include<bits/stdc++.h>
using namespace std;
int n;
int l,r;
int a[10000010];
int ans;
int main(){
	cin>>n;
	if(n==1){
		cout<<1;
		return 0;
	}
	for(int i=1;i<=n;i++){
		cin>>l>>r;
		if(l!=0){
			a[l]++;
			a[i]++;
		}
		if(r!=0){
			a[r]++;
			a[i]++;
		}
	}
	//之前这里就因把根结点算了进去,所以这里要注意qwq
	for(int i=2;i<=n;i++){
		if(a[i]==1){
			ans+=i;
		}
	}
	cout<<ans;
	return 0;
}

#210756. 树上两点距离

思路

可以使用dfs遍历全树,如到了给定的结束点,则保存深度并回溯

Code

#include<bits/stdc++.h>
using namespace std;
int n;
int x,y;
int a,b,ans;
vector<int>nodes[1010];
void dfs(int x,int from,int dep){
	if(x==b){
		ans=dep;
		return;
	}
	int len=nodes[x].size();
	for(int i=0;i<len;i++){
		int v=nodes[x][i];
		if(v!=from){
			dfs(v,x,dep+1);
		}
	}
}
int main(){
	cin>>n;
	for(int i=1;i<n;i++){
		cin>>x>>y;
		nodes[x].push_back(y);
		nodes[y].push_back(x);
	}
	cin>>a>>b;
	dfs(a,0,0);
	cout<<ans;
	return 0;
}

#210777. 树的深度

思路

直接使用dfs遍历全树,每进入一次dfs,就进行ans=max(ans,dep),最后输出ans即可

Code

#include<bits/stdc++.h>
using namespace std;
int n;
int x,y;
int a[1010],b,ans;
vector<int>nodes[1010];
void dfs(int x,int from,int dep){
	ans=max(ans,dep);
	int len=nodes[x].size();
	for(int i=0;i<len;i++){
		int v=nodes[x][i];
		if(v!=from){
			dfs(v,x,dep+1);
		}
	}
}
int main(){
	cin>>n;
	for(int i=1;i<n;i++){
		cin>>x>>y;
		nodes[x].push_back(y);
		nodes[y].push_back(x);
	}
	dfs(1,0,1);
	cout<<ans;
	return 0;
}

#210776. 每个点的子树大小

思路

使用dfs遍历树,如果符合遍历条件,则使用f数组记录每个结点的子树的结点数(f数组初始化为1)

Code

#include<bits/stdc++.h>
using namespace std;
int n;
int x,y;
vector<int>nodes[1010];
int f[1010];
void dfs(int x,int from){
	int len=nodes[x].size();
	for(int i=0;i<len;i++){
		int v1=nodes[x][i];
		if(v1!=from){
			dfs(v1,x);
			f[x]+=f[v1];
		}
	}
}
int main(){
	cin>>n;
	for(int i=1;i<n;i++){
		cin>>x>>y;
		nodes[x].push_back(y);
		nodes[y].push_back(x);
	}
	for(int i=1;i<=n;i++){
		f[i]=1;
	}
	dfs(1,0);
	for(int i=1;i<=n;i++){
		cout<<f[i]<<" ";
	} 
	return 0;
}

定义

图论〔Graph Theory〕是数学的一个分支。它以图为研究对象。图论中的图是由若干给定的点及连接两点的线所构成的图形,这种图形通常用来描述某些事物之间的某种特定关系,用点代表事物,用连接两点的线表示相应两个事物间具有这种关系。

存储方式及模板

动态数组(常用)

cin>>n;
for(int i=1;i<=n;i++){
	cin>>u>>v;
	g[u].push_back(v);
	g[v].push_back(u);
}

邻接矩阵(常用于传递闭包)

cin>>n;
for(int i=1;i<=n;i++){
	cin>>u>>v;
	g[u][v]=true;
}

由上,我们可以发现图的存储方式与树的存储方式一致,我们称之为树图同存


例题

#1612. 【Usaco2008 Jan】 Cow Contest奶牛的比赛

思路

一道使用传递闭包解决的典型题目

Code

#include<bits/stdc++.h>
using namespace std;
int n,m,a,b,ans;
bool f[110][110];
int main(){
	cin>>n>>m;
	for(int i=1;i<=m;i++){
		cin>>a>>b;
		f[a][b]=true;
	}
	for(int k=1;k<=n;k++){
		for(int i=1;i<=n;i++){
			for(int j=1;j<=n;j++){
				f[i][j]|=f[i][k]&f[k][j];
			}
		}
	}
	for(int i=1;i<=n;i++){
		int x=0;
		int y=0;
		for(int j=1;j<=n;j++){
			if(f[j][i])x++;
			if(f[i][j])y++;
		}
		if(x+y==n-1)ans++;
	}
	cout<<ans;
	return 0;
}

#10068. 珍珠

思路

使用传递闭包,最后统计如果某一颗珍珠的重量关系不在中间,则计数器++,最后输出计数器即可

Code

#include<bits/stdc++.h>
using namespace std;
const int N=110;
int g[N][N],cnt;
int main(){
    int n,m;
    cin>>n>>m;
    for(int i=1;i<=m;i++){
        int x,y;
        cin>>x>>y;
        g[x][y]=true;
    }
	for(int k=1;k<=n;k++){
		for(int i=1;i<=n;i++){
			for(int j=1;j<=n;j++){
				g[i][j]|=g[i][k]&g[k][j];
			}
		}
	}
    for(int i=1;i<=n;i++){
    	for(int j=1;j<=n;j++){
            g[0][j]+=g[i][j];
            g[i][0]+=g[i][j];
        }
	}
    for(int i=1;i<=n;i++){
    	if(g[0][i]>n/2||g[i][0]>n/2){
        	cnt++;
		}
	}
    cout<<cnt;
	return 0;
}

#10292. 刻录光盘

思路

通过传递闭包将子树合并到一起,最后输出根结点个数

Code

#include<bits/stdc++.h>
using namespace std;
int u,v,ans,n,a[1010];
bool f[1010][1010];
int main() {
    cin>>n;
    for(int i=1;i<=n;i++){
		int j=0;
		do{
			cin>>j;
			if(j==0)break;
			f[i][j]=true; 
		}while(j!=0);
    }
	for(int k=1;k<=n;k++){//传递闭包
		for(int i=1;i<=n;i++){
			for(int j=1;j<=n;j++){
				f[i][j]|=f[i][k]&f[k][j];
			}
		}
	}
	for(int i=1;i<=n;i++)a[i]=i;
	for(int i=1;i<=n;i++){
		for(int j=1;j<=n;j++){
			if(f[i][j]){
				a[j]=a[i];
			}
		}
	}
	for(int i=1;i<=n;i++){
		if(a[i]==i){
			ans++;
		}
	}
	cout<<ans;
	return 0;
}

#10126. 图的连通块

思路

使用dfs遍历全图,如未被遍历过,则计数器++,并使用dfs继续遍历,与二维数组使用dfs求连通块十分相似

Code

#include<bits/stdc++.h>
using namespace std;
int u,v,ans;
bool vis[10010];
vector<int>g[10010];
void dfs(int x){
	vis[x]=true;
	int len=g[x].size();
	for(int i=0;i<len;i++){
		int v=g[x][i];
		if(!vis[v]){
			dfs(v);
		}
	}
}
int main() {
    int m,n,count,num;
    cin>>n>>m;
    for (int i = 1; i <= m; i++) {
        cin>>u>>v;
        g[u].push_back(v);
        g[v].push_back(u);
    }
    for(int i=1;i<=n;i++){
    	if(!vis[i]){
    		ans++;
    		dfs(i);
		}
	}
	cout<<ans; 
	return 0;
}
她透过我的血,看到了另一抹殷红
原文地址:https://www.cnblogs.com/zhangbeini/p/13543182.html