《数据结构与算法分析》深度优先搜索--割点寻找详解

前言:

      经过一番折腾,最后还是留在了雷达所,不知道是对是错,证明自己的代价就是:别人觉得你行,然后你就得担起责任来了。虽然这些事情你一点也不想干。

不说这些伤心事了,说了项目也得干,做完了再说。还是来看看今天我要做的算法吧。

我的github:

我实现的代码全部贴在我的github中,欢迎大家去参观。

https://github.com/YinWenAtBIT

介绍:

深度优先搜索:

一、定义:

深度优先搜索可以看成是先序遍历的一种推广,递归遍历所有与顶点相邻的点。

编码实现:

深度优先搜索这一部分非常的简单,可以直接介绍编码实现了。

Table deepFirstSearch(Graph G, VertexType start)
{
	Index P1;
	P1 = FindKey(start, G);
	if(G->TheCells[P1].Info != Legitimate)
		return NULL;

	Table T = (Table)malloc(G->vertex * sizeof(struct TableNode));
	initTable(P1, G->vertex, T);

	Dfs(P1, T, G);
	return T;
}

void Dfs(Index S, Table T, Graph G)
{
	Index adj;
	Edge edge = G->TheCells[S].next;
	T[S].known = true;
	while(edge != NULL)
	{
		adj = edge->vertexIndex;
		if(!T[adj].known)
		{
			T[adj].path = S;
			T[adj].dist = edge->weight;
			Dfs(adj, T, G);
		}
		edge = edge->next;
	}
}
深度优先搜索很容易就实现了,问题是拿它来干什么。

双连通性:

1.定义:

如果一个连通的无向图中的任意一个顶点被删除之后,剩下的图仍然是连通的,那么这样的无向连通图就称为双连通的。如果一个图不是双连通的,那么,将其删除后图不再连通的那么顶点叫做割点。

运用实例:

一个计算机网络之中,节点是计算机,边是链路。如果有台计算机出故障,那么整个网络并不受影响。

《数据结构与算法分析》深度优先搜索--割点寻找详解_第1张图片

如上图,这样的无向图是双连通的,删除其中任意一个顶点,其他顶点还是连通的。

《数据结构与算法分析》深度优先搜索--割点寻找详解_第2张图片

这样的图就是具有割点的图,删除了顶点C,D,图将会变得不连通。

那么现在的问题就是,如何在一个非双连通的无向图中找出割点。

寻找割点:

使用深度优先算法可以用线性时间找出割点。做法如下:

准备工作:

1.执行深度优先搜素,并且在每个顶点被访问的时候给其编号,称其编号为Num(v);

2.计算编号最低的顶点,成为Low(v)(该点从v开始,通过深度优先生成树的零条或多条边,最多可以由一条反向边)

具体Low(v)计算如下图所示:

《数据结构与算法分析》深度优先搜索--割点寻找详解_第3张图片

上图从点A开始进行深度优先搜索,所示为深度优先搜索生成树。前一个为Num(v),后一个数字为Low(v)。

根据Low(v)的定义可知,Low(v)是:

1.Num(v);

2.所有背向边(v,w)中的最低Num(v);

3.树所有边(v,w)中的最低Low(v)。

前两种方法很明显,第三种方法需要使用递归的方式来说明,因为需要知道顶点v所有儿子的Low值之后才可以算v点的Low值,那么这是一个后序遍历。

判断割点:

1.深度优先遍历树的根,当根多与一个儿子的时候,他才是割点;

2.对于其他顶点v,只有在它的儿子w有Low(w)>=Num(v)时,才是割点(注意,这个条件在根节点处肯定满足,所以需要把根节点单独提取出来)。

编码实现:

有两种方式来实现这个过程,直观的方法:

1.先进行一次深度优先搜索,给每个顶点标上Num值。

2.再进行一次深度优先搜索,计算Low值,同时比较Low值与Num值寻找割点。

这种方法遍历了两次,有更简单的办法:

不存在一个遍历一定是先序遍历或者是后续遍历。在递归调用前处理的部分可以归为先序部分,递归调用后的部分可以归为后续部分,这样,就可以将上面两个过程融合起来。

伪代码:

void FindArt(Vertex V)
{
	Vertex W;
	Visited[V] = true;
	Low[V] = Num[V] = Counter++;
	for each W adjacent to V
	{
		if(!Visited[W])
		{
			Parent[W] = V;
			FindArt(W);
			if(Low[W]>= Num[V])
				printf("%v is an articulation point\n", v);
			Lov[V] = Min(Low[V], Low[W]);
		}
		else if(Parent[V] != W)
			Low[V] = Min(Low[V], Num[W]);
	}
}

实现这一部分需要先定义一个结构体,用来保存访问、父亲、Num、Low值,使用数组传递参数就太多了。结构体定义如下:

struct ArtNode
{
	bool known;
	int parent;
	int Low;
	int Num;
};
启动函数:

void FindArticulation(VertexType S, Graph G)
{
	Index P1;
	P1 = FindKey(S, G);
	if(G->TheCells[P1].Info != Legitimate)
		return;

	int count =1;
	struct ArtNode * Art= (struct ArtNode *)calloc(G->vertex, sizeof(struct ArtNode));
	FindArt(P1, G, Art, &count);

	free(Art);
}
运算函数:

void FindArt(Index S, Graph G, struct ArtNode * Art, int * countPos)
{
	Index W;
	int rootchild =0;

	Art[S].known = true;
	Art[S].Low = Art[S].Num = (*countPos)++;

	Edge edge = G->TheCells[S].next;
	while(edge != NULL)
	{
		W = edge->vertexIndex;
		/*正向边*/
		if(!Art[W].known)
		{
			if(Art[S].Num ==1)
			{
				rootchild++;
				if(rootchild >1)
					printf("%s is a articulation point\n", G->TheCells[S].vertexName);
			}

			Art[W].parent = S;
			FindArt(W, G, Art, countPos);
			/*正向边*/

			/*根处总是满足这个条件,需要排除*/
			if(Art[W].Low >= Art[S].Num && Art[S].Num !=1)
				printf("%s is a articulation point\n", G->TheCells[S].vertexName);

			Art[S].Low = Min(Art[W].Low, Art[S].Low);
		}
		else if(Art[S].parent != W)
			Art[S].Low = Min(Art[W].Num, Art[S].Low);
		edge = edge->next;
	}

}
测试结果:

第一段输出显示的是深度优先搜索的路径,这是一张路径表,第二部分输出的就是对割点的判断。判断正确。

《数据结构与算法分析》深度优先搜索--割点寻找详解_第4张图片

总结:

这一部分实现起来还是挺容易的,感觉在做完了360的笔试题目之后,已经在创业公司编写了几天代码之后,整个编码速度再一次上升了。编写完毕,测试也很快就通过了。后一部分的欧拉排序也挺简单就看懂了,只不过一直没想通怎样才能做到O(E+V)的复杂度。这个可能还需要再考虑考虑。





你可能感兴趣的:(《数据结构与算法分析》深度优先搜索--割点寻找详解)