这里给出树的直径的证明:
主要是利用了反证法:
假设 s-t这条路径为树的直径,或者称为树上的最长路
现有结论,从任意一点u出发搜到的最远的点一定是s、t中的一点,然后再从这个最远点开始搜,就可以搜到另一个最长路的端点,即用两遍广搜就可以找出树的最长路
证明:
假设 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矛盾 (见下图)
首先明确,假如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是一直往下走,再回来。