算法-图论_关键节点的判断

无向图的关节点

概述:

在网络中关节点的判断将成为影响网络连通性的主要因素。节点之间通过关键点传递信息,如在我们以太网中的网关。当网关节点失效,那么两个网络之间的节点就不能够进行通信。在无线传感器网络中,会造成不能及时监控部分区域的信息。这次程序主要实现一个简单的判断关键节点方法,当然关键节点不能单靠两个节点之间的连通与否简单判断。

算法思想:

符合关键节点的条件为:后继结点不能访问该节点的父节点。也就是说只能通过该节点才能实现网络节点间的连通性。

如何的判断后继节点不能访问父节点?父节点如何的定义?我们很熟悉的就是利用DFS能够判断网络的连通性。因此我们依旧采用这种方式。假设一个图为如下所示:


那么它的深度优先遍历将会如下图所示:

图中的实线为深度优先遍历产生的树,虚线为节点的后向边。标注的后向边为节点能访问到最前的后向边。

由此可以利用一个计数来表示访问当前节点顺序。由此在访问子节点时其访问顺序必定会大于树中的父节点。如此保证了每个节点的唯一的一个编号,就如ID一样。再加一个表示后向边指向的在树中最高的节点Pred。一旦这个pred的值大于它的父节点,那么就说明它可以绕过父节点访问树中上面的节点。在图中,如f节点,尽管是从c节点遍历访问的,但是因为有指向a的后向边,a的计数小于c的计数(num),因此我们把f的pred指向a。

如何在代码中判断c是否为关节点。因为其子节点的pred<c的计数,所以我们认为c不是关键节点。那么节点对应的数据结构如下:

//判断连通性的节点定义
struct ConnectNode
{
	int key;//键值
	int num;//
	int pred;//前向边的索引
	bool bKeyNode;
};
//解释:在识别连通性时,将连接节点的边分为前向边与后向边

其中bKeyNode是为了判断该节点是否为关节点,防止在第一副图中b判断一次d节点,e又判断一个d节点,结果输出两次的情况。

但是还要注意的一个小细节是:当从a开始访问,c节点的后向边为a,肯定的是c.pred值将为a.num。因此我们需要判断节点是否为根节点且其孩子的个数。由此下面为联通图判断关键节点的类。

//连通性的图
class ConnectGraph
{
public:
	int nodeNum;//节点个数

	int **Matrix;//利用邻接矩阵存储数据
	ConnectNode* nodes;//节点
	int *keynodes;//存放关键节点
	int resultPos;
	int count;
	bool **visitedMatrix;
	int headChild;
}

其中count用以为每个节点的num赋值,保证唯一性。而visitedMatrix为了防止c访问a,a访问c这种不断的循环。将对应的边访问置为true进行避免。

程序的代码如下:

void blockSearch()//将网络按照关键节点分为若干块
	{
		//主要思想:对每个节点存放前向边
		connectGraph(&nodes[0],&nodes[0]);//循环是避免
		cout<<endl<<"关节点为:";
		for (int i=0;i<resultPos;i++)
		{
			cout<<"  "<<keynodes[i];
		}
		cout<<endl<<totalCount;
	}
	void connectGraph(ConnectNode* node,ConnectNode* headNode)//判断是否连通
	{
		node->num=++count;
		node->pred=node->num;
		for (int i=0;i<nodeNum;i++)
		{
			int nodeKey=node->key;
			if(visitedMatrix[nodeKey][i]==false&&Matrix[nodeKey][i]==1)
			{
				if (nodes[i].num==0)
				{
					visitedMatrix[nodeKey][i]=visitedMatrix[i][nodeKey]=true;
					if (node==headNode)
					{
						headChild++;
					}
					connectGraph(&nodes[i],headNode);
					if (nodes[i].pred>=node->num&&!node->bKeyNode)
					{
						if (node==headNode&&headChild<2)
						{
							continue;//防止出现从a开始但是子节点的pred不能大于头节点判断头结点为关节点
						}
						else
						{
							node->bKeyNode=true;//设置节点的关键节点为true
						    keynodes[resultPos++]=node->key;
						}
					}
					else
					{
						if (node->pred>nodes[i].pred)//将子节点更高的前驱赋予此节点
						{
							node->pred=nodes[i].pred;
						}
					}

				}//有一种情况是当节点不能前向到父节点之前的边,但是其它的由父节点引出的边可以到达
				else
				{   //防止节点的前向边指向父节点,进而产生多次的赋值
					if (node->pred>nodes[i].num)//将较小的值赋予
					{ 
						node->pred=nodes[i].num;//为什么这后面又是num了呢?
					}
				}
			}
		}
	}

相信上面的代码很简单,首先要判断当前节点是否已经被访问过了,没有则必须进行DFS算法。当该子节点都访问了之后判断pred值。

if (node->pred>nodes[i].pred)//将子节点更高的前驱赋予此节点
{
	node->pred=nodes[i].pred;
}

当其pred的值更小,那么将子节点的pred赋予该节点,表示父节点能够通过子节点访问到更高的节点,这里不用担心会不会跨过关键点。因为所遍历的是子节点的。

如果子节点已经被访问过了,我们看到这条语句是。

if (node->pred>nodes[i].num)//将较小的值赋予
{ 
	node->pred=nodes[i].num;//为什么这后面又是num了呢?
}

仔细想想注释中的疑问。因为如果当前节点为关键节点的子节点,那么它的一个邻居节点很可能就是关键节点,而关键节点的前驱很可能就是另一个连通块中的点的num。如果进行像上一条一样的复制,就等于说关键节点的子节点能够绕过关键节点,访问另一个图。

程序的运行结果为:



小结:

1) 通过前面的图的连通性或者是树的遍历,我发现以一个全局变量进行描述,比如该程序中的num,以及在检测环时的int* num数组都是这样的一个辅助效果。

2) 算法要深入的分析,就如何的判断根节点是否为关键点,我想了很多钟方案,包括1、是不是已经被访问过,其上一次被访问是否为根节点,等等。其实一个技术headChild就可以解决。减少计算的复杂度。

3) 本算法的时间复杂度还是比较高的。


  • 4
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值