首页 > 编程语言 >算法设计与分析:实验六 图论——最大流应用问题

算法设计与分析:实验六 图论——最大流应用问题

时间:2024-09-01 09:22:11浏览次数:12  
标签:图论 源点 增广 路径 流量 算法 实验 球队

实验内容:

1996 年 9 月 10 日,《旧金山纪事报》的体育版上登载了《巨人队正式告别 NL 西区比赛》一文,宣布了旧金山巨人队输掉比赛的消息。当时,圣地亚哥教士队凭借 80 场胜利暂列西区比赛第一,旧金山巨人队只赢得了 59 场比赛,要想追上圣地亚哥教士队,至少还得再赢 21 场比赛才行。然而,根据赛程安排,巨人队只剩下 20 场比赛没打了,因而彻底与冠军无缘(摘自http://www.matrix67.com/blog/archives/5190)。

      有趣的是,报社可能没有发现,其实在两天以前,也就是 1996 年 9 月 8 日,巨人队就已经没有夺冠的可能了。那一天,圣地亚哥教士队还只有 78 场胜利,与洛杉矶道奇队暂时并列第一。此时的巨人队仍然是 59 场胜利,但还有 22 场比赛没打。因而,表面上看起来,巨人队似乎仍有夺冠的可能。然而,根据赛程安排,圣地亚哥教士队和洛杉矶道奇队互相之间还有 7 场比赛要打,其中必有一方会获得至少 4 场胜利,从而拿到 82 胜的总分;即使巨人队剩下的 22 场比赛全胜,也只能得到 81 胜。由此可见,巨人队再怎么努力,也不能获得冠军了。

      在美国职业棒球的例行赛中,每个球队都要打 162 场比赛(对手包括但不限于同一分区里的其他队伍,和同一队伍也往往会有多次交手),所胜场数最多者为该分区的冠军;如果有并列第一的情况,则用加赛决出冠军。在比赛过程中,如果我们发现,某支球队无论如何都已经不可能以第一名或者并列第一名的成绩结束比赛,那么这支球队就提前被淘汰了(虽然它还要继续打下去)。从上面的例子中可以看出,发现并且证明一个球队已经告败,有时并不是一件容易的事。

关于这个事情有一个有趣的故事,下面是一段对话:

“看到上周报纸上关于爱因斯坦的那篇文章了吗?……有记者请他算出三角比赛的数学公式。你知道,一支球队赢得了很多剩余的比赛,其他球队则赢这个赢了那个。这个比赛到底有多少种可能性?哪个球队更有优势?”

“他到底知道吗?”

“显然他知道的也不多。上周五他选择道奇队没有选巨人队。”

上面的表是四个球队的比赛情况,现在的问题是哪些球队有机会以最多的胜利结束这个赛季?可以看到蒙特利尔队因最多只能取得 80 场胜利而被淘汰,但亚特兰大队已经取得 83 场胜利,蒙特利尔队因为wi + ri < wj 而被淘汰。费城队可以赢83场,但仍然会被淘汰。 。 。如果亚特兰大输掉一场比赛,那么其他球队就会赢一场。所以答案不仅取决于已经赢了多少场比赛,还取决于他们的对手是谁。请利用最大流算法给出上面这个棒球问题的求解方法。

问题分析:

要想知道队伍x是否已经被淘汰,可以考虑以下两种情况:

(1)平凡淘汰:如果队伍x的已获胜次数w[x]加上剩余比赛的次数r[x](即最大可能获胜次数)小于其他队伍j的已获胜次数w[j],则队伍x一定会被淘汰。

因此,对于队伍x和其余队伍j,若w[x]+r[x]<w[j],则x一定会被淘汰。

(2)非平凡淘汰:如果无法使用平凡淘汰的直接判断方式确定队伍x是否一定会被淘汰,则采用最大流进行判断。

最大流模型解决问题前置知识:

最大流问题:

  • 输入:
  1. 有向图G=<V,E,C>,其中c(e)∈C表示边e的容量。
  2. 源点s,汇点t。
  • 输出:
  1. 总流量|f|。
  2. 约束目标:
  3. 约束条件:容量限制:0<=f(e)<=c(e) (边上的流量不能超过容量);
  4. 流量守恒:

(每个顶点进入和流出的流量和相等)

算法引入:

直观想法:

  1. 对∀e∈E,初始化流量为f(e)=0。
  2. 寻找一条源点s到汇点t的路径P,此路径上的每条边e均满足f(e)<c(e)。
  3. 按路径P上最小剩余容量增加路径流量。
  4. 迭代寻找路径P直至无法增加路径流量。

以下图为例,在所给流网络G中,选择红线路径作为路径P,并增加了11的流量:

如此迭代寻找P,便可以得到解。但是,显然该方法并不可以得到最优解,例如,当该流网络为如下图状态时:

我们发现,按照直观策略,该网络不能再增加流量。但是如果我们稍作调整,调整如下图所示,我们便成功将网络的流量增加1。

我们将流量进行了重新分配,缩减了某些边( v 2 , v 3 )的流量,并将其重新分配到别的边( v 2 , v 4 ) 上,实际上,上述变动可以看作我们沿着下图中红色路径走了1个流量:

也就相当于,我们引入了一条反向边( v 3 , v 2 ) ,并发现,如果我们寻找路径时允许反向搜索,则可以增大总流量。一旦引入了反向边,就需要考虑反向边的权重问题,为保证流量调整合理,定义正向边及反向边的权重按照以下规定:

  1. 反向边权重:描述可缩减流量的上限,即原始边上的流量f ( e )
  2. 正向边权重:描述可扩充流量的上限,即原始边上的剩余容量c ( e ) − f ( e )

如此,由一条原始边可以生成两条正向、反向边,实现反向搜索,以此来帮助我们更好地寻找到使得总流量增加的路径P。

相关定义和概念:

  • 流网络:包含一个源点s,一个汇点t和一组带容量的有向边的图。设给定有向图G=<V,E,C>,其被称为流网络。
  • 容量:∀e∈E,c(e)>=0
  • 流量:∀e∈E,0<=f(e)<=c(e)
  • 剩余容量:∀e∈E,c(e)−f(e)
  • 总流量:(总流量=源点流出量=汇点流入量)
  • 流:对于每一条边(u,v),定义流量f(u,v),它表示从节点u流向v的流量。流量需要满足两个条件:
  1. 容量限制:0<=f(u,v)<=c(u,v),其中c(u,v)是边(u,v)的容量。
  2. 流量守恒:对于每个节点v(除了源点和汇点),流入等于流出即∑f(u,v)=∑f(v,w)。
  • 割:将节点集V分为两个不相交的集合S和T,其中s∈S,t∈T。割的容量是从S到T的边的容量之和。
  • 残存网络:对于给定流网络G=<V,E,C>和流量f,可以得到残存网络Gf=<V,Ef>,其中∀cf(e)∈Ef,其残存容量为:

 

残存网络中存储的是容量信息(可扩充及可缩减上限),可以帮助我们快速地发现增广路径(包含返向边的路径)。

例如下图:

  • 增广路径:流网络中源点s到汇点t的某一条路径称为增广路(要求路径上的边的剩余容量不为负数)。给定流网络G=<V,E,C>和流量f,增广路径p是残存网络Gf中一条从源点s到汇点t的简单路径(路径上顶点不重复)。如果残存网络中存在增广路径,则说明存在流量的进一步调整方式。

如下图所示:

增广路径的残存容量指的是一条增广路径p上各边残存容量的最小值,如图3中,此条增广路径的残存容量为2。增广路径的残存容量实际上就是沿着该路径流量扩充的最大值

Ford-Fullkerson算法:

  • 算法思路:
  1. 对∀e∈E,初始化流量为f(e)=0。
  2. 构造残存网络Gf,寻找源点s到汇点t的增广路径p
  3. 增广路径p的残存容量增加路径流量。
  4. 迭代寻找路径P直至无法增加路径流量。

 最大流模型构建:

对于每一个队伍x建立带权有向图:

  1. 该图有4个部分:源点S、比赛点、队伍点、汇点T
  2. 其中源点S汇点T都是虚拟点。
  3. 比赛点:由剩余n-1条队伍两两比赛,共有C(n-1,2)个比赛点。(其中C(n-1,2)表示组合数学中从n-1个队伍选出两个队伍的方案数)
  4. 源点S到比赛点(i,j)的权值:g[i][j](第i支和第j支队伍的比赛次数),也是源点S到比赛点(i,j)的容量。边的容量表示这场比赛的剩余次数。这意味着源点可以向比赛节点提供的流量总和就是所有未进行比赛的总数。
  5. 比赛点到队伍点的权值:无穷大。(比赛点所指队伍点代表此队伍在此比赛胜出)。表示比赛的结果必须被传递到相关的队伍。这确保了每场比赛都必须有结果,即两个队伍中的一个获胜。
  6. 队伍点到汇点T的权值:w[x]+r[x]-w[j](其中x为当前队伍编号,j为剩下队伍编号)。边的容量表示该队伍最多还能赢得的场次。这确保了当流量达到该边的容量时,队伍j已经赢得了足够多的比赛,超过了队伍x的可能胜场数,从而淘汰队伍x。

以费城(队伍编号为1)为例,图示如下:

最大流模型评判:

  1. 计算总流量需求:通过源点到比赛节点的所有边的容量总和,我们得到了从源点流出的总流量需求,记为FlowFromS(也是所有从源点S流出的最大流量)。在图1中,此时的FlowFromS=6+1+0=7。
  2. 计算最大流:通过最大流算法,计算从源点到汇点的最大流量,记为MaxFlow。这个最大流量表示在所有可能的比赛结果分配下,能实际流动的最大流量。
  3. 比较判断是否淘汰:若FlowFromS<=MaxFlow,说明所有比赛的流量需求都能被满足,没有出现任何流量瓶颈,即所有比赛的结果能够分配得当,使得队伍x没有被淘汰。若FlowFromS>MaxFlow,说明存在流量瓶颈,某些比赛结果无法合理分配,从而导致队伍x被淘汰。

求解最大流MaxFlow:

Ford-Fulkerson算法(简称FF)

Ford-Fulkerson 算法是一个经典的最大流算法,通过寻找增广路径来不断增加流量。它使用深度优先搜索(DFS)或广度优先搜索(BFS)来寻找增广路径,直到没有增广路径为止。在本次实验中FF算法使用的是DFS。

  • 算法思路:
  1. 初始化:将所有边的初始流量设置为 0。
  2. 寻找增广路径:使用DFS在残余网络中寻找从源点 s 到汇点 t 的增广路径。
  3. 增加流量:找到路径上的瓶颈容量(即最小的剩余容量),然后沿着增广路径增加该瓶颈容量的流量。
  4. 更新残余网络:根据增广路径和瓶颈容量,更新残余网络中的流量和容量。
  5. 重复步骤 2-4:直到没有增广路径为止。
  6. 返回最大流量:最后,源点的出流总和即为最大流量。
  • 伪代码:

Ford-Fulkerson算法

Algorithm Ford-Fulkerson( G, s, t ) :

  初始化所有边的流量为0

  While 存在从s到t的增广路径p in 残余图中 found by DFS( G, s, t, parent )

    c = p中最小的残余容量

    对于增广路径p中的每条边( u, v )

      增加沿边( u, v )的流量c

      减少沿边( v, u )的流量c

  Return 从s到t的总流量

Bool DFS( G, u, t, parent )

  如果 u == t : Return true 即存在

  对于每条边( u, v ) in G 的邻接边 :

    如果v未访问且残余容量( u, v ) > 0 :

      parent[v] = u

        如果DFS( G, v, t, parent ) : Return true

  Return false

算法复杂度:

Ford-Fulkerson算法的时间复杂度取决于找到的增广路径的数量以及每次找到增广路径的时间。假设最大流量是 F,每条增广路径的容量至少为1,那么最多有 F 条增广路径。如果使用DFS来寻找增广路径,则每次寻找增广路径的时间复杂度为 O(E),其中 E 是图中的边数。

总时间复杂度:O(F*E)

由于FF算法使用DFS每次只找一条路、存在绕远路(存在经过n个点才到达汇点的可能)的可能,而且增加的流量是路上最小的权值,最大流量F也可以很大。因此FF算法的效率是十分低下的。

Edmonds-Karp算法(简称EK)

Edmonds-Karp 算法是 Ford-Fulkerson 算法的一个特例,通过广度优先搜索(BFS)来寻找增广路径。DFS中存在“绕远路”的情况,而BFS则保证找到的就是最短的增广路,尽可能的避免了“绕远路”的情况出现。保证了每次增广路径的最短长度,从而限制了算法的执行次数。

  • 算法思路:
  1. 初始化:将所有边的初始流量设置为 0。
  2. BFS 寻找增广路径:在残余网络中使用 BFS 寻找从源点 s 到汇点 t 的增广路径。BFS 保证找到的路径是最短的(边数最少)。
  3. 增加流量:找到路径上的瓶颈容量(即最小的剩余容量),然后沿着增广路径增加该瓶颈容量的流量。
  4. 更新残余网络:根据增广路径和瓶颈容量,更新残余网络中的流量和容量。
  5. 重复步骤 2-4:直到没有增广路径为止。
  6. 返回最大流量:最后,源点的出流总和即为最大流量。
  • 伪代码:

Edmonds-Karp算法

  1. Algorithm Edmonds-Karp( G, s, t ) :
  2.   初始化所有边的流量为0
  3.   While 存在从s到t的增广路径p in 残余图中 found by BFS( G, s, t, parent )
  4.     c = p中最小的残余容量
  5.     对于增广路径p中的每条边( u, v )
  6.       增加沿边( u, v )的流量c
  7.       减少沿边( v, u )的流量c
  8.   Return 从s到t的总流量
  9. Bool BFS( G, s, t, parent )
  10.   创建一个队列queue并将s入队
  11.   初始化parent数组,大小为顶点数,用于记录路径上的父节点
  12.   初始化visited数组为false
  13.   标记s为已访问
  14.   While queue 不为空 :
  15.     u = queue.dequeue() 出队
  16.   对于每条边( u, v) in G 的邻接边 :
  17.     If v 未访问且残余容量( u, v ) > 0 :
  18.       parent[v] = u
  19.       If v == t 即找到了
  20.         Return true
  21.       queue.enqueue(v) 入队
  22.       标记v为已访问
  23.   Return false
  • 算法复杂度:

Edmonds-Karp算法使用BFS来寻找增广路径,对于每次增广路径查找,时间复杂度为 O(E)。在最坏情况下,每次增广路径至少增加1的流量,每次增广都会导致至少一条路径满载,因此想要再经过这条边只能是通过走它的反向边的方式,而从反向边经过会导致长度至少增加2,而最短增广路径的总路程不会超过n,换句话说,经过一条边的路径最多有V/2种。一共E条边,因此最多增广次数为(VE)/2。所以最多需要 O(V*E) 次增广路径查找。

总时间复杂度:O(V*(E^2))

Dinic算法

Dinic是一种更高效的最大流算法,它通过分层网络来寻找增广路径。适合处理大规模网络。 算法思想类似于FF/EK算法,也是一种找增广路的算法(其实Dinic算法就是这两个算法的优化版本)。Dinic算法使用BFS分层,DFS寻找增广路。所谓分层就是通过BFS预处理出每个点到源点的距离,寻找时,我们只往高处寻找增广路即算法只会在层次单调递增的路径上前进,也就是说,当前节点只能访问其层次值更高的邻接节点。这就避免了回到层次更低的节点,从而减少了走回头路和绕圈子的情况。

  1. 分层网络的构建 (BFS):首先,从源点出发使用广度优先搜索(BFS)来为每个节点赋予一个层次(level)。源点的层次为0,直接与源点相连的节点层次为1,依此类推。BFS的结果是一个层次图,其中每个节点都有一个层次值,表示该节点距离源点的最短路径上的边数。
  2. 寻找增广路径 (DFS):在寻找增广路径时,算法只会在层次单调递增的路径上前进,也就是说,当前节点只能访问其层次值更高的邻接节点。这就避免了回到层次更低的节点,从而减少了走回头路和绕圈子的情况。具体来说,假设我们在一个节点 u ,其层次为 level[u] 。我们只会尝试访问那些层次为 level[u] + 1的邻接节点 v 。这样,路径总是朝向汇点方向前进,避免了无效的循环。
  • 算法思路:
  1. 初始化:将所有边的初始流量设置为 0。
  2. 构建层次网络:使用 BFS 从源点 s 开始构建层次网络,标记每个节点的层次(距离源点的最短路径)。如果无法到达汇点 t,则算法结束,返回最大流量。
  3. 分层网络中寻找增广路径:使用 DFS 在分层网络中寻找从源点 s 到汇点 t 的增广路径,找到瓶颈容量,并沿路径增加流量。每次找到一条增广路径后,更新分层网络。
  4. 重复步骤 2-3:不断重建层次网络并寻找增广路径,直到无法在分层网络中找到增广路径为止。
  5. 返回最大流量:最后,源点的出流总和即为最大流量。
  • 伪代码:

Dinic算法

  1. Algorithm Dicnic( G, s, t ) :
  2.   初始化所有边的流量为0
  3.   While BFS( G, s, t )构建层次图:
  4.     初始化层次图中的阻塞流
  5.     While 层次图中存在增广路径p:
  6.     c = p中最小的容量
  7.     对于增广路径p中的每条边( u, v )
  8.       增加沿边( u, v )的流量c
  9.       减少沿边( v, u )的流量c
  10.   Return 从s到t的总流量
  11. Bool BFS( G, s, t )
  12.   初始化所有顶点的层次为-1
  13.   level[s] = 0
  14.   Queue = [s]
  15.   While queue 不为空 :
  16.     u = queue.dequeue() 出队
  17.     对于每条具有正残余容量的边( u, v)  :
  18.     If  level[v] < 0 :
  19.       level[v] = level[u] + 1
  20.       queue.enqueue(v) 入队
  21.   Return level[t] >= 0
  • 算法复杂度:

Dinic算法包括两部分:构建分层网络和在分层网络中找到阻塞流。构建分层网络(BFS)的时间复杂度是 O(E),在分层网络中找到阻塞流的时间复杂度是 O(E*V)。

因此每次构建分层网络和找到阻塞流的总时间复杂度是O(E)+O(E*V)=O(E*V),在最坏情况下,需要进行 O(V) 次构建分层网络的操作。

总时间复杂度:O((V^2)*E)

  • 算法优化:

Dinic算法还可以通过一些方法来优化,如:

  1. 多路增广:在每次迭代中通过层次图找到尽可能多的增广路径,并将它们一次性地增加到流量中,而不是每次只处理一条增广路径。即若我们在某点找到一条增广路后,如果还剩下多余的流量未使用,那么我们可以尝试继续在该点找到更多的增广路。
  2. 当前弧优化:当前弧优化是一种减少DFS中回溯次数的技术,它通过记录每个节点当前正在处理的邻边,使得DFS在回溯时可以从上次停止的地方继续,而不是重新开始。这是因为在Dinic算法中,一条边增广一次后,不会再次增广,所以下次增广时不用在考虑这些边。

优化Dinic算法伪代码如下:

Dinic优化算法

  1. Algorithm Dinic( G, s, t ) :
  2.   初始化所有边的流量为0
  3.   While BFS( G, s, t )构建层次图:
  4.     初始化当前弧数组currentArc为0  //当前弧数组currentArc记录当前DFS过程中每个节点的当前弧,从而避免重复检查边。
  5.     While True: //多路增广。反复调用DFS,直到不再有增广路径为止。
  6.       调用DFS( G, s, t, Infinity )
  7.       如果返回流量为0,跳出循环
  8.   Return 从s到t的总流量
  9. Bool BFS( G, s, t ) :
  10.   初始化所有顶点的层次为-1
  11.   level[s] = 0
  12.   Queue = [s]
  13.   While queue 不为空 :
  14.     u = queue.dequeue() 出队
  15.     对于每条具有正残余容量的边( u, v)  :
  16.       If level[v] < 0 :
  17.         level[v] = level[u] + 1
  18.         queue.enqueue(v) 入队
  19.   Return level[t] >= 0
  20. Function DFS( G, u, t, flow ) :
  21.   如果 u == t:  Return flow
  22.   While currentArc[u] < G.adj(u).size() :
  23.     e = G.adj(u)[currentArc[u]]
  24.     v = e.other(u)
  25.     如果 e.residualCapacityTo(v) > 0 且 level[v] == level[u] + 1 :
  26.       f = DFS( G, v, t, min(flow, e.residualCapacityTo(v)) )
  27.       如果 f > 0 :
  28.         e.addResidualFlowTo(v, f)
  29.         Return f
  30.     currentArc[u] += 1  //没有找到增广路径,则更新当前弧数组currentArc,避免重复检查该边。
  31.   Return 0

优化Dinic算法效率

多路增广和当前弧优化后的Dinic算法时间复杂度的最坏情况仍然是 O((V^2)*E)。虽然这两种优化并不会改变算法的最坏情况时间复杂度,但它们可以降低实际运行时间。这是因为它们通过减少不必要的遍历次数和更新操作来提高算法的效率,尤其是在处理具有特定结构的网络时。

多路增广:在寻找增广路径时,允许同时选择多条路径进行增广,而不是仅限于一条路径。这样可以在一次搜索中增加更多的流量,减少遍历次数。因此,相比于只能选择一条路径增广的基本Dinic算法,多路增广可以更快地达到最大流。

当前弧优化:传统的Dinic算法中,每次寻找增广路径时都会从头开始遍历与当前节点相连的边。但在实际应用中,我们可能不需要从头开始,而是从上一次搜索中停下的位置继续搜索,因为这些边在上一次搜索中可能已经被标记为无法使用了。当前弧优化就是记录每个节点当前正在处理的边的位置,从而避免不必要的遍历,提高了搜索的效率。

这两种优化方法结合起来可以显著降低算法的实际运行时间,尤其是在处理大型网络时。虽然它们不会改变最坏情况的时间复杂度,但在实践中可以大大提高算法的效率。

棒球比赛问题解决思路:

平凡淘汰条件判断:

如果队伍x的已获胜次数w[x]加上剩余比赛的次数r[x](即最大可能获胜次数)小于其他队伍j的已获胜次数w[j],则队伍x一定会被淘汰。

因此,对于队伍x和其余队伍j,若w[x]+r[x]<w[j],则x一定会被淘汰。

非平凡淘汰条件判断:

如果无法使用平凡淘汰的直接判断方式确定队伍x是否一定会被淘汰,则采用最大流进行判断。在最大流模型中:

  1. 计算总流量需求FlowFromS:通过源点到比赛节点的所有边的容量总和,得到从源点流出的总流量需求。
  2. 计算最大流MaxFlow:通过最大流算法计算从源点到汇点的最大流量。

最大流算法:

  • Ford-Fulkerson算法:O(F*E)
  • Edmonds-Karp算法:O(V*(E^2))
  • Dinic算法:O((V^2)*E)

 

比较判断是否淘汰:若FlowFromS<=MaxFlow,说明所有比赛的流量需求都能被满足,队伍x没有被淘汰。若FlowFromS>MaxFlow,说明存在流量瓶颈,某些比赛结果无法合理分配,从而导致队伍x被淘汰。

淘汰子集的引入

  1. 淘汰子集:主要思路是基于最大流算法和最小割的概念。在棒球比赛中,如果一个球队已经没有机会赢得该赛季的联赛冠军,那么它就被称为被淘汰了。淘汰子集指的是在某个球队被判定为被淘汰之后,可能导致其淘汰的其他球队的集合。这个集合中的球队是能够在某种情况下(例如在剩余比赛中取得足够的胜利)使得被淘汰的球队无法获得联赛冠军的球队。因此,淘汰子集给出了对于被淘汰球队为何被淘汰的一种解释。
  2. 淘汰子集判断思路
  • 首先,获取指定球队的最大流。使用最大流算法,计算指定球队在剩余比赛中可能获得的最大胜利数。
  • 然后,遍历所有其他球队,并检查它们是否满足淘汰条件:
  • 如果最大流为空,表示指定球队在剩余比赛中不可能获得足够的胜利来排名第一。因此,只需比较指定球队与其他球队的胜利数和剩余场次,确定是否满足淘汰条件,满足(此队可以淘汰掉指定球队)则加入淘汰子集。
  • 如果最大流不为空,可以使用最小割算法来找到最小割集合。判断其他球队队是否在最小割s侧,如果是则加入淘汰子集。

 为什么要判断其他球队队是否在最小割s侧:

  • 最大流和最小割定理:根据最大流最小割定理,在一个流网络中,最大流等于最小割的容量。最小割将网络分成两部分:源点一侧(s-side)和汇点一侧(t-side)。在最大流问题中,源点一侧中的节点无法通过剩余容量大于0的边与汇点一侧直接相连。
  • 找最小割对应边:我们找到了最大流,根据最大流最小割定理,它的值也就是最小割,因此当我们找到了最大流,也就是没有s->t的增广路径时,剩下的残留网络刚好将S集合与T集合分割开来,这就是最小割。寻找最小割对应边也就十分简单了:
  1. 找到最大流max_flow;
  2. 对最后的残留网络进行DFS/BFS遍历,可达的点集合为S,不可达的为T;
  3. 从S集合到T集合的边,即∀e(u,v),u∈S,v∈T就是最小割对应边
  • 淘汰的定义:在棒球淘汰问题中,目标是确定是否有一种情况使得某个特定的球队(称为 x)不可能赢得更多比赛,从而被其他球队淘汰。
  • 最小割中的s-side和t-side:如果某个球队位于最小割的s-side,这意味着在最大流的残余网络中,从源点s到这个球队的路径已经被切断,表示这个球队无法再增加胜利。这些s-side中的球队形成了一个集合R。集合R中的球队总的胜利场次已经足以淘汰被考虑的球队x。也就是说,只要R中的球队按照目前的趋势赢下足够的比赛,球队x就没有任何机会超越R中的球队。例如假设球队x求完最大流的残存网络如下图所示,此时队伍1和2在s-side中,表示队伍1和2按照目前的趋势赢下比赛,球队x没有机会超过他们,所以队伍1和2可以淘汰掉队伍x,即加入淘汰子集。

标签:图论,源点,增广,路径,流量,算法,实验,球队
From: https://blog.csdn.net/gyeolhada/article/details/141720700

相关文章

  • 算法设计与分析:实验四 动态规划—鸡蛋掉落问题
    实验内容:动态规划将问题划分为更小的子问题,通过子问题的最优解来重构原问题的最优解。动态规划中的子问题的最优解存储在一些数据结构中,这样我们就不必在再次需要时重新处理它们。任何重复调用相同输入的递归解决方案,我们都可以使用动态规划对其进行优化。鸡蛋掉落问题是理解......
  • 算法学习每日一题数位不同的组合
    Problem:3153.所有数对中数位不同之和思路本题关键在于如何处理数位不同的个数,其实就是组合问题,两个不同数字的不同数位只能算一对,所以我们不妨把后方元素与前方元素数位不同算作一对,保持单调性避免重复计数。那么后方元素不同的数位应该如何统计呢,我们不妨使用哈希......
  • 智慧校园实训管理:打造高效实验项目管理系统
    在智慧校园的实训管理框架中,实验项目管理功能是培养学生实践能力和创新精神的关键一环。这一功能通过数字化手段,实现了实验项目的全流程管理,从项目设计、资源调配、过程监控到成果评估,确保了实训活动的高效开展和教学质量的稳步提升。实验项目管理功能始于项目的策划与设计阶段。教......
  • 【数模资料包】最新数模国赛word+latex模版|数模常用的算法python+matlab代码
     【2024最全国赛研赛数模资料包】C君珍贵国一数模资料|最新数模国赛word+latex模版|数模常用的算法python+matlab代码国赛指:高教社杯全国大学生数学建模竞赛,研赛指:华为杯研究生数学建模竞赛。资料内容具体看文末卡片以下是三个相关的资料内容:1C君珍贵国一数模资料2最......
  • 双指针算法详解
      我的主页:2的n次方_       1.双指针算法双指针算法是一种在数组或字符串中常用且高效的算法技术,它通过维护两个指针(或索引)来遍历数据结构,从而解决某些问题。这种算法能够减少不必要的重复遍历,降低时间复杂度,并且往往能够使得代码更加简洁易懂。根据指针......
  • 代码随想录算法day5 - 哈希表1
    题目1242.有效的字母异位词给定两个字符串*s*和*t*,编写一个函数来判断*t*是否是*s*的字母异位词。字母异位词是通过重新排列不同单词或短语的字母而形成的单词或短语,通常只使用所有原始字母一次。示例1:输入:s="anagram",t="nagaram"输出:true示例2:......
  • 手把手教你使用C语言实现堆栈数据结构算法-两种方式(链表+数组)
    堆栈定义栈(stack)是一种遵循先入后出逻辑的线性数据结构,常见操作入栈,出栈,访问栈图片来源:https://www.hello-algo.com/栈的实现栈遵循先入后出的原则,因此我们只能在栈顶添加或删除元素。然而,数组和链表都可以在任意位置添加和删除元素,因此栈可以视为一种受限制的数组或链表......
  • python实现椭圆曲线加密算法(ECC)
    目录椭圆曲线加密算法(ECC)简介ECC的数学基础椭圆曲线的定义ECC的基本操作ECC加密和解密流程Python面向对象实现ECC加密和解密代码解释场景应用:安全通信总结椭圆曲线加密算法(ECC)简介椭圆曲线加密算法(EllipticCurveCryptography,ECC)是一种基于椭圆曲线数学结构的......
  • python实现数字签名算法 (DSA)
    目录数字签名算法(DSA)介绍DSA的数学基础DSA签名生成和验证流程Python面向对象实现DSA签名和验证代码解释场景应用:电子合同签署总结数字签名算法(DSA)介绍数字签名算法(DigitalSignatureAlgorithm,DSA)是一种基于公钥加密的数字签名标准。它被广泛用......
  • 「代码随想录算法训练营」第五十天 | 图论 part8
    目录拓扑排序题目:117.软件构建dijkstra(朴素版)题目:47.参加科学大会dijkstra算法和prim算法的区别dijkstra(堆优化版)题目:47.参加科学大会拓扑排序拓扑排序概括来说就是给出一个有向无环图,把这个有向无环图转成线性的排序,就叫拓扑排序。使用广度优先搜索(BFS)即可。如上图,当我们......