题目简述:输入一副无向图,判断是否为树,如果是树,则需要找出全部令树高最大的根(很可能不唯一)
5
1 2
1 3
1 4
2 5
五个节点的树,这里3 4 5作为根都可以得到最高的树。
暴力做法:把每一个节点作为根求一次树高。 DFS求树高很方便,代码简洁。复杂度O(n)。因此n个节点,总的复杂度就是O(n^2)
后台的例子没有卡暴力做法,因此是可以过的。
#include <cstdio>
#include <cstring>
#include <vector>
#include <algorithm>
using std::vector;
int n;
vector<int> G[10001]; //邻接表
int result[10001]={0}; //保存每个节点作为根的最大深度
int isvisited[10000];
void travesal(int st)
{
isvisited[st] = true;
for( int i=0; i<G[st].size(); i++)
{
if(isvisited[ G[st][i] ] == 0)
{
travesal( G[st][i] );
}
}
}
int checkTree() //判断是否树,返回连通集数目
{
int count=0;
memset(isvisited, 0, sizeof(isvisited));
for( int i=0; i<n; i++)
{
if( isvisited[i] == 0)
{
travesal(i);
count++;
}
}
return count;
}
int calHeight( int st, int h ) //计算树高,返回最大的树高。
{
int maxH=h, t;
isvisited[st] = true;
for( int i=0; i<G[st].size(); i++)
{
if(isvisited[ G[st][i] ] == 0)
{
t = calHeight( G[st][i], h+1 );
if ( maxH < t) maxH = t;
}
}
return maxH;
}
int main()
{
int a,b;
scanf("%d", &n);
for (int i=0; i<n-1; i++ )
{
scanf("%d %d", &a, &b);
a--, b--;
G[a].push_back(b);
G[b].push_back(a);
}
int kcomponent = checkTree();
if ( kcomponent > 1)
{
printf("Error: %d components\n", kcomponent);
}
else
{
for( int i=0; i<n; i++)
{
memset(isvisited, 0, sizeof(isvisited));
result[i] = calHeight(i, 1); //节点i作为根对应的树高
}
int* target = std::max_element(result, result+n);
for( int i=0; i<n; i++)
{
if( *target == result[i] )
{
printf("%d\n", i+1);
}
}
}
return 0;
}
但是这题其实有更巧妙地做法,涉及到一个新概念: 图的直径。
令树高最大,也就是从根到叶的路径最长。
当时看到了这篇文章 树的直径, 通过分类讨论+反证法。得出结论: 只需要两次遍历,就可以计算出图的直径。
1、从任一点u出发,计算出最远点的集合 L;
2、从L中任意选一点L1,找到最远点的集合P;
3、P∪L 就是全部所求的根。
步骤一中,得到的集合L 必定都是叶子,否则不可能是从u出发得到的最远点。
同时L中的点肯定是图中最长路径的起点。
这点通过反证法证明: 假设最长路径是s-t, 如果L不在st路径中,那么总是可以构造出一条比st还要长的路径。矛盾。 因此L必定在st路径中。
而且L都是叶子(度为一的节点)因此必定是st的起点或者终点。
步骤二中,从L中选一点L1, 找到最远点的集合P;
u是任意选择的,有时候可能离某个合适的根太近,所以集合L中没有包括这个根。
步骤三,P∪L 就是全部所求的根。
这点其实不那么显然的,我看了其他博客的说法都没有说明这一点。
P是根据L中一点 L1 计算出来的。
那么L2 L3 这些点不需要考虑吗? 分别求出L2对应的最远点P2, L3对应的最远点P3. 然后再全部汇总呢?
确实是不需要考虑L中其他点的,因为根据其他点求出来的最远点集合必定还是在集合 P∪L 之中
同样还是用反证法+ 分类讨论。
如果存在点P2 使得 L2 - P2是最长路径,并且 P2 不在集合P∪L中,
分两种情况讨论:
1、最初的点u在最长路径 s-t 中, 可以构造出比L1-P更长的路径L1 - u - L2
先看一些已知条件: U-L1 路径长为D, U-P1为d, 因为L是u求出的最远点,所以D>d.
L1 L2的位置是不可能如下图的。否则从L1出发计算出的最远点一定是L2的。
因此L1 L2的关系应该是如下图:即使如此,P2 无论在哪,还是会在L1的最远点之中。
2、最初的点u不在最长路径 s-t 中
同样的, L1 L2 的相对位置不可能下图的。否则最长路就是L1 - L2了
因此只可能是下图这种
综上,只需要两次遍历就可以一个不漏的找到全部根。
#include <cstdio>
#include <cstring>
#include <vector>
#include <queue>
#include <algorithm>
using std::vector;
using std::queue;
int n;
vector<int> G[10001], result;
int isvisited[10000], distance[10000];
void travesal(int st)
{
isvisited[st] = true;
for( int i=0; i<G[st].size(); i++)
{
if(isvisited[ G[st][i] ] == 0)
{
travesal( G[st][i] );
}
}
}
int checkTree()
{
int count=0;
memset(isvisited, 0, sizeof(isvisited));
for( int i=0; i<n; i++)
{
if( isvisited[i] == 0)
{
travesal(i);
count++;
}
}
return count;
}
int BFS( int st ) //从st节点开始BFS,distance数组保存n个节点到st的距离
{
memset(isvisited, 0, sizeof(isvisited));
int last=st, level=1;
queue<int> q;
q.push(st);
isvisited[st] = true;
distance[ st ] = level;
while( q.empty() == false)
{
int head = q.front();
//printf("====%d %d\n", head+1, level);
for( int i=0; i<G[head].size(); i++)
{
if( isvisited[ G[head][i] ] ) continue;
q.push( G[head][i] );
isvisited[ G[head][i] ] = true;
distance[ G[head][i] ] = level+1;
}
if( last == head)
{
level++;
last = q.back();
}
q.pop();
}
//printf("%d\n", level);
return level-1;
}
int main()
{
int a,b;
scanf("%d", &n);
for (int i=0; i<n-1; i++ )
{
scanf("%d %d", &a, &b);
a--, b--;
G[a].push_back(b);
G[b].push_back(a);
}
int kcomponent = checkTree();
if ( kcomponent > 1)
{
printf("Error: %d components\n", kcomponent);
}
else
{
int D = BFS( 0 );
for( int i=0; i<n; i++) //第一次BFS得到集合 L
{
if( D == distance[i]) result.push_back( i );
}
int d = BFS( result[0] );
for( int i=0; i<n; i++)
{
if( d == distance[i]) result.push_back( i ); //从L中任选一点进行BFS得到集合P
}
sort(result.begin(), result.end());
result.erase( unique(result.begin(), result.end()), result.end() ); //排序去重, 这里用到了unique返回值
for( int i=0; i<result.size(); i++)
{
printf("%d\n", result[i]+1);
}
}
return 0;
}
/*
5
1 2
1 3
1 4
2 5
7
1 2
1 6
1 7
2 5
2 3
3 4
7
5 2
5 6
5 7
2 1
2 3
3 4
*/