1021. Deepest Root

1021. Deepest Root 

题目简述:输入一副无向图,判断是否为树,如果是树,则需要找出全部令树高最大的根(很可能不唯一)

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
*/

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值