首页 | 新闻 | 新品 | 文库 | 方案 | 视频 | 下载 | 商城 | 开发板 | 数据中心 | 座谈新版 | 培训 | 工具 | 博客 | 论坛 | 百科 | GEC | 活动 | 主题月 | 电子展
返回列表 回复 发帖

图论算法 有图有代码 万字总结 向前辈致敬(5)

图论算法 有图有代码 万字总结 向前辈致敬(5)

网络流问题如下左图所示,有一个顶点s,称为源点(source);还有一个顶点t,称为汇点(sink)。对于顶点c,它最大流出2,因此它的最大流入为2,如下右图所示。而t的最大流也就是5。

要想计算最大流,同样可是使用前面的思想——分阶段进行。令开始时所有边都没有流,如下中间图所示。我们可以用残余图(residual graph)来表示对于每条边还能再添加上多少流。对于每一条边,可以从容量中减去当前的流而计算出残留的流。

第一步:假设我们选择s−b−d−t路径,此时会发出2个单位的流通过这条路径的每一边,如下中间图所示。对比左图,我们做如下约定:一旦注满(使饱和)一条边,例如a到b和b到d,就将这条边从残余图(也就是中间图)去掉,如下右图所示。

第二步:接下来选择s−a−c−t路径,此时也会发出2个单位的流通过这条路径的每一边,如下中间图所示(只看s−a−c−t即可,s−b−d−t为上一步说走过的路径)。同样将残余图更新如下右图所示。

第三步:从上图的残余图中我们已经可以看出来最后一步的唯一一种走法了,也就是从s−a−d−t。做如下图所示更新。

很显然从t无法走到s,因此算法至此便终止了。因此正好5个单位的流是最大值。前面的三步我们走的如此顺利,那么问题真的如此简单么?
如果一开始我们选择了s−a−d−t,那么算法就会失败了,因为路已经被堵死了。

为了使算法得以成功运作,那么就要让流图中具有以相反方向发送流的路径,如下所示。那么对于如下右图中的残余图而言,从d返回到a的便成了3而非4,这是因为从t流到d的流量是3个单位。现在在残余图中就有a和d之间有2个方向,或者还有1个单位的流可以从a导向d,或者是3个单位的流导向相反的反向,当然,我们也可以撤销流。

紧接着如果通过d到a导入2个单位的流,算法就会从边(a,d)取走2个单位的流,更新流图如下。

思考:找出下面网络中的一个拓扑排序以及最大流。

活动网络AOV网络除了一些不能再简单的工程外,所有的工程都可以划分为若干个成为活动(activities)的子工程。比如说大学的课程,得修完了大学英语1才能修大学英语2,也得修完了高等数学才能修线性代数、概率论、离散数学等。将这些作为顶点标记为图时,它便是一个有向图。
顶点表示活动的网(activity on vertex network)或AOV网,是用顶点表示活动或任务,边表示活动或任务之间的优先关系的有向图G。在AOV网络G中,当且仅当从顶点i到j存在一条有向路径,则顶点i称为顶点j的前驱(predecessor);当且仅当<i,j>是G中的一条边,则称顶点i为顶点j的直接前驱(immediate predecessor)。如果顶点i是顶点j的前驱,则称顶点j为顶点i的后继(successor);如果顶点i是顶点j的直接前驱,则称顶点j为顶点i的直接后继。
拓扑排列是由有向图中所有顶点形成一个线性序列,对图中任意两个顶点i和j,如果顶点i是顶点j的前驱,则顶点i在拓扑序列中排在顶点j的前面。
我们在前面已经介绍了拓扑排序,这里给出它的伪代码。
for(i=0;i<n;i++) {     if every vertex has a predecessor     {         fprintf(stderr,"Network has a cycle.n");         exit(1);     }     pick a vertex v that has no predecessors;     output v;     delete v and all edges leading out of v from the netwok; }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
对于拓扑排序问题,所需的操作主要有:
1)判断顶点是否有前驱;
2)删除顶点和关联于该顶点的所有边。
在操作1中,我们可以在每个顶点中都保存其直接前驱个数的计数。对于操作2,可以使用前面介绍过的邻接表来表示AOV网络。于是可以将其实现为以下C代码:
// 声明 typedef struct node *node_pointer; typedef struct node {     int vertex;     node_pointer link; }; typedef struct  {     int count;     node_pointer link; }hdnodes; hdnodes graph[MAX_VERTICES];  // 函数 void topsort(hdnodes graph[],int n) {     int i,j,k,top;     node_pointer ptr;     top=-1;     for(i=0;i<n;i++)     {         if(!graph.count)         {             graph.count=top;             top=i;         }     }     for(i=0;i<n;i++)     {         if(top==-1)         {             fprintf(stderr,"nNetwork has a cycle. Sort terminated.n");             exit(1);         }         else         {             j=top;             top=graph[top].count;             printf("v%d, ",j);             for(ptr=graph[j].link;ptr;ptr=ptr->link)             {                 k=ptr->vertex;                 graph[k].count--;                 if(!graph[k].count)                 {                     graph[k].count=top;                     top=k;                 }             }         }     } }   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
在topsort的声明中,count域用来保存顶点的入度,而link域则是指向邻接表首结点的指针。邻接表中的每个结点又包含了两个域:vertex和link。在输入时,可以方便地设置count域的值。当输入一条边<i,j>时,顶点j的count就会加1。用一个栈来保存count值为0的顶点序列。当然也可以使用队列,但栈更容易实现。由于在count域减至0以后,count域就没有用了,所以通过头结点的count域把栈中的各个结点链接起来。
对于topsort的分析:对于具有n个顶点和e条边的AOV网络,第一个for循环的时间开销为O(n)。而第二个for循环执行n次。if子句在常数时间内完成;else子句中的for循环时间开销为O(di),其中di是顶点i的出度。由于这个循环会在每个顶点输出时执行一次,所以总时间为:
O((∑n−1i=odi)+n)=O(e+n)
因此这个算法的渐进时间为O(e+n),与问题的规模呈线性关系。
继承事业,薪火相传
很好的算法分析,感谢楼主的分享
返回列表