首先要明确割点与割边的概念,
割点:在一个连通图中,如果去掉了某个点和所有与这个点相连的边后,是图分成了两个部分,变成了一个不连通的图。那么这个点就是割点。
割边:在一个连通图中,如果去掉了某个点,把图分成了两个部分,变成了一个不连通的图,那么这个边就是割边。
那么我们先来看看如何求割点,最简单的方法呢,其实就是,挨个点都试验一遍,去掉这个点以及与这个点相连的所有边之后,用dfs或bfs来判断这个图是否还是连通的,但这个算法的时间复杂度是N^3的,算法效率很低。所以我们再来想想其他办法的使用。
根据割点的定义,割点将图分成了两个部分,这个两个部分是靠割点来连通的,如果没有割点存在,那么图将不连通。所以,换句话说,我们要从第一部分的点进入第二部分的话,我们必须经过这个割点。然后接着,我们再考虑图的dfs的时候。由于割点是通往第二部分的门户,那么当我们从第一部分开始对图进行dfs的时候,如果访问了割点,那么再一次回退到割点的时候,必须是等第二部分的所有的点都访问完了之后才可以,否则就相当于“大门”被关闭了。第二部分的点就无法再次访问到了。因为dfs遇到访问过的点就立刻返回。
对dfs来说,她遍历的最后是生成一棵树,其中树有一些叶子节点,形成叶子节点的原因是,他们能接触到的点都已经在他们之前访问过了。所以到了他们之后dfs不再继续递归了。联系到上面,我们可以看出dfs遍历所形成的树中的图的第二部分的点,与他们相连的只能是图中第二部分的点,或者是割点。所以我们如果对dfs访问的点按顺序来进行标号的话,那么第二部分的点的被访问序号一定要比割点的访问序号要大。
所以我们就想到了利用这个特性,用dfs求割点的方法:
首先从任意点开始,进行dfs,
每递归到一个点,如果这点是没有被访问的,那么用dfs的序号给这个点的n1赋值,然后还要接着去访问与这个点相邻的其他的点,把其中最小的返回值返回,作为这个点的n2值。如果相连的点是被访问过的,那么直接把该店的n1作为返回值。
然后,在访问的过程中,还要记录下序边,下序边是指,在生成的树中,由父节点指子节点的边。是有方向的。
当dfs结束后。对于每一个点,从它的下序点中找到最小的n2的值,若果n1<=n2,那么就说明,他的下序点中,没有比它先被访问的点,那就说明这个点是一个割点。若果n1>n2,那么这个点不是割点。
(但要注意,这个算法有一个默认的前提假设,就是说,dfs的起始点是被割点分成的两个部分中的点,但是如果这个起始点开始就是割点。则最后的比较是找不到割点的,所以最后我们还需要一个单独对这个起始点的判断,用最简单的dfs或者bfs即可)。
而对于求割边得算法呢,做法是一样的,只是不用记录下序边,在最后形成的树中,如果AB是一条边,如果A的n1要比B的n1小的话,并且B的n2>A的n1,那么这个边就是一个割边。
以上就是求割边与求割点的做法,下面我们来看看一个例题:
一个电话公司,通过一个电话网络提供服务。这个网络有电话线和交换站组成,线路是双向的,每个居住点都会设置一个交换站点,并且这个网络是全部连通的,任意两个居住点都是连通的。但是常常会有个别站点出问题,而且,有些站点出问题后,除了本居住地无法与外界交换信号以外,其他的站点也要受影响。
这个例题的意思实际上就是求割点的意思。下面只是简要的写一段最主要的核心部分的代码:
下面就看一下这个简要的源代码:
int dfs(int ind)
{
父节点。算法中不往父节点进行回退。
}
下面是在dfs过程进行结束之后,判断割点的过程。
void checkALL()
{
}