这里给出树的直径的证明:
主要是利用了反证法:
假设 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是一直往下走,再回来。