写在前面:仅为个人代码/总结,未必标准,仅供参考!如有错误,还望指出交流,共同进步!
通信网络
【问题描述】(CSP原题)
某国的军队由N个部门组成,为了提高安全性,部门之间建立了M条通路,每条通路只能单向传递信息,即一条从部门a到部门b的通路只能由a向b传递信息。信息可以通过中转的方式进行传递,即如果a能将信息传递到b,b又能将信息传递到c,则a能将信息传递到c。一条信息可能通过多次中转最终到达目的地。
由于保密工作做得很好,并不是所有部门之间都互相知道彼此的存在。只有当两个部门之间可以直接或间接传递信息时,他们才彼此知道对方的存在。部门之间不会把自己知道哪些部门告诉其他部门。
上图中给了一个4个部门的例子,图中的单向边表示通路。部门1可以将消息发送给所有部门,部门4可以接收所有部门的消息,所以部门1和部门4知道所有其他部门的存在。部门2和部门3之间没有任何方式可以发送消息,所以部门2和部门3互相不知道彼此的存在。
现在请问,有多少个部门知道所有N个部门的存在。或者说,有多少个部门所知道的部门数量(包括自己)正好是N。
【输入形式】
输入的第一行包含两个整数N, M,分别表示部门的数量和单向通路的数量。所有部门从1到N标号。
接下来M行,每行两个整数a, b,表示部门a到部门b有一条单向通路。
【输出形式】
输出一行,包含一个整数,表示答案。
【问题分析】
① 处理对象:N个整数和M对表示边的整数。
② 实现的功能:基于图的ADT计算知道所有N个部门的存在的部门数量。
③ 结果的显示:输出知道所有N个部门的存在的部门数量。
【抽象数据类型设计】&&【物理数据对象设计】
结构特征:图
图的ADT:
【算法思想设计】
(说明:此解法只适用于测试数据规模较小的情况,仅作为联系应用图的ADT的载体,提交到CSP系统可能只会判30分,当然也可用Warshall算法解题)
通过验证图中每个顶点到其他点的连通性来判断知道所有N个部门的存在的部门数量:
首先定义图的ADT和继承类Graphm的设计,根据输入所给顶点和各边,基于图的邻接矩阵建立图各顶点的对应关系从而构建图。然后在Graphm类中增加声明一个数组num,用于后继作为表示某顶点到图中其他各点是否连通的标记,然后对于每个顶点,根据邻接矩阵通过DFS1深度优先检索依照边的方向正向(可以向其他点发出讯息)搜索与该点连通的其他各点并在num数组中做好标记,通过DFS2深度优先检索依照边的方向反向(可以接收到其他点发出讯息)搜索与该点连通的其他各点并在num数组中做好标记,最后遍历num数组中的元素,若发现有该点与其他某点不连通的标记,在表示该部门不知道所有N个部门的存在,否则表示该部门知道所有N个部门的存在。
【题目样例基于设计算法的求解过程】
根据输入构建以下图:
其邻接矩阵为:
正向搜索:从1可达 2、3、4,从2可达4,从3可达4。
反向搜索:从4可达1、2、3,从2可达1,从3可达1。
则可以的到3与2不连通,故知道有4个部门存在的只有1、4两个部门。
【关键实现部分的算法步骤】
① 构建图的各边:setEdge(int v1, int v2, int wt)函数:
由于实验使用的是基于邻接矩阵实现图的ADT,因此在建立边时直接将邻接矩阵中对应位置置为边的权值,这里都赋值为1。
②用dfs1(int i)函数实现从某一点出发正向搜索可达到的其他的点,伪代码如下:
void dfs1(int i)
{
setMark(i,VISITED);//做顶点访问标记
for(int j=0;j<numVertex;j++)
{
if(matrix[i][j]==1)
{
num[j]=1;//做可到达顶点的标记
if(getMark(j)==UNVISITED)
//根据下一顶点是否已访问过作为递归条件,避免由于图中有环出现死循环
{
dfs1(j);//递归搜索
}
}
}
}
③用dfs2(int i)函数实现从某一点出发反向搜索可达到的其他的点,伪代码如下:
void dfs2(int i)
{
setMark(i,VISITED); //做顶点访问标记
for(int j=0;j<numVertex;j++)
{
if(matrix[j][i]==1)//相当于转置邻接矩阵
{
num[j]=1; //做可到达顶点的标记
if(getMark(j)==UNVISITED)
//根据下一顶点是否已访问过作为递归条件,避免由于图中有环出现死循环
{
dfs2(j); //递归搜索
}
}
}
}
④判断某一点是否与其他各点连通,伪代码如下:
bool countnum(int j)
{
bool flag=true;
for(int i=0;i<numVertex;i++)
{
if(num[i]==0&&i!=j)//排除自身与自身连通的情况同时判断是否有不连通的点
{
flag=false;break;
}
}
return flag;
}
⑤main函数中关键部分的实现:
int res=0;//知道所有N个部门的存在的部门数量
for(int i=0;i<N;i++)
{
G.resetmark();
G.resetnum();//对每一个点开始搜索前清除连通标记
G.dfs1(i);//正向寻找从该点沿边能到达的其他点
G.resetmark();//清除各点访问标记
G.dfs2(i);//反向寻找从该点沿边能到达的其他点
bool flag=G.countnum(i);//表示该点是否与图中其他所有点连通
if(flag==true)
{
res++;
}
}
cout<<res<<endl;