poj2831 树的直径(bfs/dfs)

这里给出树的直径的证明:

  主要是利用了反证法:

  假设 s-t这条路径为树的直径,或者称为树上的最长路
  现有结论,从任意一点u出发搜到的最远的点一定是s、t中的一点,然后再从这个最远点开始搜,就可以搜到另一个最长路的端点,即用两遍广搜就可以找出树的最长路
  证明:
     1.设u为s-t路径上的一点,结论显然成立,否则设搜到的最远点为T则   dis(u,T) >dis(u,s)     且  dis(u,T)>dis(u,t)   则最长路不是s-t了,与假设矛盾
     2.设u不为s-t路径上的点
        首先明确,假如u走到了s-t路径上的一点,那么接下来的路径肯定都在s-t上了,而且终点为s或t,在1中已经证明过了
        所以现在又有两种情况了:
          1:u走到了s-t路径上的某点,假设为X,最后肯定走到某个端点,假设是t ,则路径总长度为dis(u,X)+dis(X,t)
          2:u走到最远点的路径u-T与s-t无交点,则dis(u-T) >dis(u,X)+dis(X,t);显然,如果这个式子成立,
          则dis(u,T)+dis(s,X)+dis(u,X)>dis(s,X)+dis(X,t)=dis(s,t)最长路不是s-t矛盾 (见下图)
 
求树的直径用2次bfs和2次dfs都可以。

用两次bfs代码:

#include<bits/stdc++.h>
using namespace std;
#define maxn 10010  //最多有10000个村庄,最多有几条边?最多有n-1条边 

struct node{
	int to;
	int val;
	int next;//下一条要遍历的边。 
	//node(int t,int n,int v):to(t),next(n),val(v){}
}edge[maxn];

int head[maxn],dis[maxn],vis[maxn];
int num=1;
int us,maxm;

void addEdge(int from ,int to,int value){
	edge[num].to=to;
	edge[num].val=value;
	edge[num].next=head[from];//模拟前插式链表 ,如果一开始是0,表示的是新加的这条边是从当前点
	//出发的第一条。 
	head[from]=num++;//head中存储的为更新后的边,所以老边后访问。 
}
void bfs(int u){
//	for(int i=head[u];i;i=edge[i].next){
//		
//		
//		
//	}找到那条离1最远的点。就要把1到每个点的距离算出来 
	maxm=0;
	memset(vis,0,sizeof(vis));
	memset(dis,0,sizeof(dis));
	//int first;
	queue<int> que;//放进去的是点!!! 
	que.push(u);
	vis[u]=1;
	while(!que.empty()){//每弹出来一个,这个的子节点都要计算距离 
		u=que.front();que.pop();
		
		for(int i=head[u];i!=-1;i=edge[i].next){
			int v=edge[i].to;
			if(!vis[v]){
				dis[v]=dis[u]+edge[i].val;
				if(dis[v]>maxm){
					us=v;
					maxm=dis[v];
				}					
				que.push(v);
				vis[v]=1;
			}
		}
	}
}

int main(){
	int a,b,c;
	//freopen("3.txt","r",stdin);
	memset(head,-1,sizeof(head));
	while(scanf("%d%d%d",&a,&b,&c)==3){
		addEdge(a,b,c);//必须得是双向的边 
		addEdge(b,a,c);
	}
    bfs(1);
    bfs(us);
    cout<<maxm;
    return 0;
}

bsf队列里放的是点的编号。因为我需要判断,所以一开始home应该设置为0都行.

弹出来树点的编号,根据head来确定首先访问从当前点开始的那条边,i就指向了下一条,如果i是0,表示next=0,也就是没有后边的边了。当然这个也是需要vis数组的,如果没有就会重复放,因为边是双向的。而且在算距离的时候,应该是u的距离加上当前边的长度(一开始我想的是原来的长度加上边的长度?什么鬼啊。。。。)在比较的过程中记录最大值即可。

下边是dfs:

#include<cstdio>
#include<iostream>
#include<queue>
#include<cstring>
using namespace std;
#define maxn 10010  //最多有10000个村庄,最多有几条边?最多有n-1条边 

struct node{
	int to;
	int val;
	int next;//下一条要遍历的边。 
	//node(int t,int n,int v):to(t),next(n),val(v){}
}edge[maxn];

int head[maxn],dis[maxn],vis[maxn];
int num=1;
int us=0,maxm=0;

void addEdge(int from ,int to,int value){
	edge[num].to=to;
	edge[num].val=value;
	edge[num].next=head[from];
	head[from]=num++;
}
void dfs(int u){
	vis[u]=1;
	for(int i=head[u];i!=0;i=edge[i].next){//相对比较简单啦。
		int v=edge[i].to;
		if(!vis[v]){//没有被访问过就标记,计算。然后取访问它的,
			vis[v]=1;
			dis[v]=dis[u]+edge[i].val;
			if(dis[v]>maxm){
				us=v;
				maxm=dis[v];
			}
			dfs(v);
		} 
	} 
	
}

int main(){
	int a,b,c;
	freopen("3.txt","r",stdin);
	while(scanf("%d%d%d",&a,&b,&c)==3){
		addEdge(a,b,c); 
		addEdge(b,a,c);
	}
	maxm=0;
	memset(vis,0,sizeof(vis));
	memset(dis,0,sizeof(dis));
    dfs(1);
    
    maxm=0;
	memset(vis,0,sizeof(vis));
	memset(dis,0,sizeof(dis));
    dfs(us);
    cout<<maxm;
    return 0;
}
PS:bfs是用队列实现,dfs是用递归。并且bfs是扩散,dfs是一直往下走,再回来。


猜你喜欢

转载自blog.csdn.net/huanting74/article/details/79721231
今日推荐