关于图的割点

在一个无向图中,若删除某一个节点,使得图分成若干个不相联通的子图,那么,这个节点就是图的割点。就像下图所示:

节点1、节点3和节点4,都是这个图的割点。为什么呢?你想啊,把节点1、节点3和节点4遮住,会怎么样?节点2永远也没法去到6 5 8 7 那些,同理,遮住节点4后,7,8也没法去到6 5 了。这样,就形成了一个个独立子图了。

那么,我们怎么知道这个点,就是割点呢?

①、用两个DFS,删除某个节点后,对其后面的节点进行DFS,如果在【不同过该节点】的情况下,不能回到以前的节点,那么,被删除的节点就是割点啦。删除?怎么删除,其实只需一个book数组,book[i]=1;DFS后,book[i]=0;就OK 啦。。起到一个临时标记,也就起到了"删除"的效果了。最后,book[i]=0;是要把删除的节点补回来。我们没理由弄破一个图吧?    ̄□ ̄||  最后,该算法的复杂度是O(N(N+M)); 怎样?很复杂吧?有没有其他优秀的算法呢?

②、只需一个DFS,不过要增加两个数组:

一个是num[];//记录的是这个顶点在遍历时,是第几个被访问到的

一个是low[];//表示一个节点,在不经过其父亲时,能回到最早的祖先是谁

::>_<:: 。。什么是父亲?祖先又是谁?这里我们把问题抽象化一下,在上图中,假设我们从1号顶点开始DFS,那么,1顶点能直接去到的顶点,我们称其为1号顶点的儿子,那么,一号顶点就是其父亲。这里需要记住一点:【父亲的num[i]】总是比儿子的少,因为辈分越大,数值越小,我们可以理解为,我爷爷是第一代传人,爸爸是第二代,我是第三代。记住这点很重要哦⊙▂⊙。

那么,假如一个顶点cur,在不经过其父亲顶点father的时候,总能回到更早的顶点(father的祖先),那么,这个father就不是割点。就比如图中的5号顶点,(根据上面,此时的father=5,cur=4),如果删除了5号顶点,cur还是能够通过6号顶点回到3顶点,甚至能回到1号顶点,此时,cur就能回到father的祖先了,此时,father=5并不是图的割点。

相反,如果一个顶点cur,在不经过其父亲顶点father的时候,总不能回到更早的顶点(father的祖先),那么,这个father就是割点啦!比如图中的4号顶点。

那么,我们怎么判断它不能回到自己【父亲的祖先呢】?其实只需要low[cur]>=num[father];就是,儿子能回到最早的祖先,最多最多,也只是它的爸爸。也就是,我不问过我爸爸,我连我爷爷是谁都不知道,那么,爸爸起到连通我和我爷爷的作用,所以,我爸爸就是割点了。 ̄□ ̄||。  我们再来形象理解下这句代码low[cur]>=num[father];我们用辈分来论事,记得我上面说过一句话很重要的吗?拉上去看看吧。这里,我先说个小故事。

话说,在古代,风家人才辈出,能才能武,祖祖祖祖祖祖祖祖爷也还在世上。 ̄□ ̄||。要知道,在这些高人辈出的家族里,有些长辈不是你想见就能见的,有些族人一生在世,没见过爷爷一面也有可能,因为高手一般都是闭关的。。但是如果经过父亲的推荐,那就不同了。一般儿子是绝对知道自己的父亲的,这点不用我说了吧。 ̄□ ̄||。所以cur的father就向他的父亲(就是cur的爷爷)说:我儿子cur天生神力,资质颇高,父亲你就点拔他一些武功吧,爷爷说,"哦,好好好,报名费5000大洋!"这样,cur就能知道他爷爷是谁了。但是,如果cur的父亲father不是伯乐,不知道cur是千里马,那就麻烦了,cur会被埋没的。就这样,cur一辈子平平淡淡,真是伤仲永哇,cur的父亲father,成了cur的"割点"了,就是,cur在不经过他爸爸father的推荐下,不能知道他的爷爷,爷爷爷,爷爷爷爷是谁,那么此时,cur只认识他的爸爸。说来说去,你还没告诉我low[cur]>=numer[father]是什么嘛!我该怎么判断cur是否只认识他爸爸呢?(请把这两句话看3遍)。没错,你想到了,low[cur]>=number[father];就是表明cur最多只认识他的爸爸。low[cur]==number[father]的时候,就表明cur知道自己的爸爸。为什么是?>=而不是<=呢?我们看看辈分?爷爷是第一辈,爸爸是第二辈,我是第三辈。如果我知道我爷爷是谁,low[cur]=1了,而number[father]=2;此时,low[cur]<number[father]。也就说明我能去到爷爷,father不是割点。

number[]//其实number代表的是自己的辈分,

low[];//代表我能认识到最老的长辈。注意,越老,值越小。因为辈分是1

说了那么多,我们来看看完整的代码?

#include <stdio.h>
#include <stdlib.h>
int n,m;//n代表有多少个节点,m代表有多少条边
//要注意m>=n-1最小也要有n-1条边才能把图接上
int e[51][51];//用邻接表来存一个图
int num[51],low[51];
int flag[51];//用来表示哪一个是割点
int root;//根节点。
int first;//表示一开始num和low的值
int min (int x,int y)
{
	return x>y?y:x;
}
void dfs (int cur,int father)
{
        int child=0;//代表cur有多少个儿子
        int i;//用来循环的而已
	first++;//这个我们称为时间戳
	num[cur] = first;//刚开始的辈分
	low[cur] = first;//刚开始能访问到的,只能是自己
	for (i=1;i<=n ;i++ )//循环一个图
	{
        if (e[cur][i]==1)//如果cur去i是有路的
        {
			if (num[i]==0)//就是i还没被访问过。先跳去看else
			{
				child++;//儿子数+1
				dfs (i,cur);//分清楚谁是爸爸,谁是儿子,此时很
				//明显cur是i的爸爸
				
				//结束dfs后
				low [cur] = min(low[cur],low[i]);//更新我和儿子能访问到的人
				//如果我儿子能访问到我太爷,但是我爸爸又不推荐我去见我太爷
				//那么,我可以通过儿子去见啊,父之命,能不从?

				if (cur != root && low[i]>=num[cur])
				//此时儿子是i 父亲是cur
				//如果我不是第一辈的人,并且,我的儿子是渣渣,失去我的推荐后
				//自己不能去到我的爸爸或者更早的人
				//那么,我成了我儿子的割点了
				//其实究竟是我伤仲永,还是我儿子本来就不是千里马呢?哈哈
				//有些东西,一个巴掌拍不响
				{
					flag[cur] = 1;//我是割点
				}
				if (cur == root &&child==2)
			    //如果我是第一辈,并且我有两个儿子
				{
					flag[cur]=1;
				}
			}
			else if (i!=father)//如果节点i被访问过
//但是,这个i不是cur的父亲father。我们动起笔,模拟一下这个过程
//首先(先不看1-->2)1-->3,到3的dfs,for循环中,第一个值i=1,就是
//3访问他父亲了,这是没意思的,但是我们又不能book标记它,这样会造成很多
//节点访问不到的,所以,如果访问到的节点是父亲,我们不做任何事情。
//就是上面else if不成立,但是,如果不是呢?就是6能去到3了,而6号的父亲是
//4啊。。那么,恭喜你,你拜见了你的爷爷爷 爷了,快去拜师吧
			{
				low[cur] = min(low[cur],num[i]);//拜师中
				//注意此时的cur是6,i是3
				//就是,cur去到的辈分的i的辈分
				//也就是num[i]。当然low[cur]比num[i]更小就另当别论了
				//也就是cur见到了太太太爷。。。哇靠!神人啊。
				//太爷出来见他
			}
        }
	}
}
void work()
{
	scanf ("%d%d",&n,&m);
	int i,j;
	for (i=1;i<=n ; i++)
	{
		for (j=1;j<=n ;j++ )
		{
			e[i][j] = 0;//初始化一个图
		}
	}
	for (i=1;i<=m ;i++ )
	{
		int u,v;
		scanf ("%d%d",&u,&v);
		e[u][v] = 1;//表示有路可去
		e[v][u] = 1;//无向图嘛
	}
	root=1;//先把root设置成1
	dfs (1,root);//从节点1开始,把节点1当成是根节点
	for (i=1;i<=n ;i++ )
	{
		if (flag[i])
		{
			printf ("%d ",i);//输出割点
		}
	}
	return ;
}
int main()
{
	work();//调用一个函数来分担主函数的代码
	return 0;
}

  对了,我还没说过child和root的事情,root代表的是祖爷(第一代传人,就是家主)。因为家主没可能成为其儿子的割点啊,家主都没父亲(他从石头爆出来的),那么1为什么还是割点啊,这里他是妨碍了自己的儿子见面,他的儿子也是的,师兄都不见面,各立一派吗?哦,对哦,家主之位,岂能乱传?所以,一般古代那些皇帝的儿子,都是闹不和的,为什么?大家都想自己的爸爸给皇帝位给我啊。!!!

child代表的是儿子,要注意,这里的child==2不代表家主只有一个儿子,代表的是,家主闹不和的儿子达到了两个,家主就是割点了。。最后,希望我们能动笔把每次dfs都写出来,这样,理解起来,更加的好、、

本人初出茅庐,有什么不正确的地方,希望朋友们能指教下,我感激不尽。。共同努力

posted on 2015-12-11 11:20  stupid_one  阅读(432)  评论(0编辑  收藏  举报

导航