【算法】网络最大流问题,三次尝试以失败告终

简介: 开始已多次看到“网络最大流问题”的字眼,一直不知道是什么,后来终于有一次打算仔细了解一下,期间我发现了一篇不错的博客:全面理解网络流中的最大流问题。在这篇博客的帮助下,我成功弄清楚了什么是网络流中的最大流问题,同时也明白了解决这个问题的基本思路。

开始

已多次看到“网络最大流问题”的字眼,一直不知道是什么,后来终于有一次打算仔细了解一下,期间我发现了一篇不错的博客:全面理解网络流中的最大流问题。在这篇博客的帮助下,我成功弄清楚了什么是网络流中的最大流问题,同时也明白了解决这个问题的基本思路。

基本思路:“反悔”机制

这里不仔细介绍思路了,大家可以先看看前面那篇博客

在前面提到的博客中,博主认为“借用”这个描述比“反悔”更为合适。然而在阅读的过程中,我反而理解了“反悔”这个概念,同时觉得还挺合适的。下面简单说说自己关于“反悔”的理解。

首先说说为什么会需要“反悔”。举个有点抽象的例子,在某个网络中,A只有一条路可走,B却率先占用了A唯一的路径,然而其实B还有其它空闲的路径可走,以至于网络的运载能力没有被充分利用,不是“最大流”。于是A告诉B:你挡着道了,B听到后就换了一条路走,A便也得以通过。


我们看看下面这个网络(来自前面提到的博客):

e7933a45f0304b35ac717bfc8a97fee3.png

如果选择路径的顺序为:

1、a -> e -> d = 8(有流量8通过这条路径

2、a -> c = 2

那么从路径b过来的流量就被阻塞了。

而如果我们在通过路径e时,建立一条与通过流量相同的反向边 e’ ,表示走这条路径的选择是可以反悔的,最大可反悔流量为8。那么当流量A通过路径b后,发现了可反悔的路径e,就告诉以占用e的流量B:你挡着道了。于是B的两个流量就从 e’ 返回,然后通过c边到达了终点t。而路径d也腾出了2个流量,A就从原先流量B占用的路径通过。于是有:

3、b -> e’ -> c = 2


得到最大流 8 + 2 + 2 = 12

f8951220df304ded9808a9cda5010ae2.png

干活

我对于自己测试代码的能力不太自信,于是从洛谷上找了个题:P3376 【模板】网络最大流

尝试一:深度优先搜索

算法过程采用深度优先搜索,从 源点 开始,到每个点时记录流入该点的流量,然后将这些流量分向其它的点,通过边时就更新边的剩余流量,到达 汇点 时就累加更新最大流。对于有分叉出路的点,通过边的同时建立反向边。

算法过程采用深度优先搜索,从 源点 开始,到每个点时记录流入该点的流量,然后将这些流量分向其它的点,通过边时就更新边的剩余流量,到达 汇点 时就累加更新最大流。对于有分叉出路的点,通过边的同时建立反向边。

于是我遇到了一些问题:

  • 不应当在搜索过程中刚经过一条边(一个点)时就更新它的剩余流量,因为实际通过流量会受后面的影响。(从源点到汇点一条路径的流量,由路径上流量最小的边决定
  • 解决方案:在遍历时记录走过的点,在达到汇点时再更新整条路径所有边(点)的剩余流量

结果在部分测试集上超时了。

尝试二:少走弯路

我起初并没有去查找其它的算法,而想自己在原来算法过程的基础上优化试试。于是我作出了一个假设:很多时间浪费在已经不可能的方向上

于是我尝试引入一种点死亡机制,在搜索过程中,如果从一个点某次没能成功到达汇点,就判定这个点已经寄了,以后再也不走这里了。

5a18b8874509490080b4baffe0fbd3e3.png

但我后面发现了问题:


在搜索过程中判定点的死亡并不合适。因为在搜索时,我并不会再走已经走过的点,而一个被判定为寄了的点,可能经过那些已走过的点是可以达到汇点的。

解决方案(没想出来):如果不在搜索路径的中途判定,而是单独判断从某个点到汇点是否联通的话,这个判断本身又会消耗太多的时间。

尝试三:最短增广路径,广度优先

我还是决定看看别人的算法,于是我找到了这篇博客:网络最大流算法,并计划使用Edmonds-Karp算法,即每次寻找一条从源点到汇点的最短路径,以此更新边的剩余流量、最大流。

使用广度优先搜索策略可用来寻找最短路径,从源点开始,在搜索的同时,构建出一个层次网络,到达汇点时停止搜索。然后从汇点逆向一直走,就可以得到一条从源点到汇点的路径。(如果想得到最短路径,可以给层次网络中的每个点标上层号

       527c06b0be2449138c48385150eaefe6.png                    99%              


图中点2没有指向t是因为有一个点到达t时就可以停止搜索了。此时我们从t往回走,总能得到一条s与t之间的路径,它可能是:

t -> 1 -> s

t -> 1 -> 2 -> s

我们发现,在层次网络中往回走的过程中,每一步要么使所在层数降低,要么层数不变,但层次不会增大。(而如果你给每一点标记了层号,就可以限制每次都是向上一层走)残量网络:原来的网络,每次找到路径后会更新网络中每条边的剩余流量,故称为残量网络。

相比深度优先搜索:即使你不要求最短路径,也总能以不太大的代价找到一条路径,而不会陷入局部的泥沼。

还是没ac

在自己对代码测试一番后,就觉得可以了,然而还是有三个测试点没过。测试数据下载下来发现是200个点,5000条边(数据中有重复的边)的那种,调试感觉无从下手,我至今没有找到问题出在哪

8b3de2e600244c9289d7067b8cb674a6.png

记两个小bug

1. 数组越界

忘记使用数组下标是从1开始的了,因此数组大小应定义为nNum+1

dbe145da60094adda4cefdffbc0c2c56.png

2. 写错变量名

不是第一次,也不是第n次,是第n+1次了。初始化列表中应为t(_t)

176f762f63d24c8fb0692be8986ba4be.png

小结

在这个问题上花了好多时间,还是没得到一个好的结果。有时会有些后悔感,感觉那些时间花得不值得。我总喜欢了解一下算法大体思路后就自己去琢磨具体的细节和实现,而不愿意去阅读别人的源代码,好几次一个题的bug找很久。有时bug是思路上的,有时是写代码太粗心导致的错误。但是这样太花时间了。最近从其它方面得到一些感受,多观察是有益的。

最后一个版本的代码(C++)

定义类与函数

#include<iostream>
#include<cstdio>
#include<climits>
#include<cstdlib>
#include<queue>
using namespace std;
const int Len = 201;
class Net
{
  public:
  int net[Len][Len];
  int *road;
  int iR;
  long long sum;
  int nNum;
  int s;
  int t;
  Net(int _nNum, int _s, int _t);
  void findRoad();
  void upNet();
  void forward();
};
//---------------------------
Net::Net(int _nNum, int _s, int _t)
  :net{}
  ,nNum(_nNum)
  ,s(_s)
  ,t(_t)
{
  iR = 0;
  sum = 0;
  road = new int[nNum]{};
}
//---------------------------
void Net::findRoad()
{
  iR = 0;
  int net2[Len][Len]{};//层次网络 
  bool pass[nNum + 1]{};//走过为true 
  queue<int> q;//辅助层次网络构建 
  q.push(s);
  //pass[s] = 1;
  //构建层次网络 
  while(1){
    int front = q.front();
    if(pass[front] == 1){
      q.pop();
      // 可能越界 
      if(q.empty()){
        return;
      } 
      front = q.front();
    }
    pass[front] = 1;
    q.pop();
    bool flag = 0;
    for(int i = 1; i <= nNum; i++){
      if(net[front][i] > 0 && pass[i] == 0){ 
        net2[front][i] = net[front][i];
        q.push(i);
        if(i == t){
          goto ROAD;
        }
        flag = 1;
      }
    }
    if(flag == 0 && q.empty() == true){
      break;
    }
  }
  //得到从源点到汇点的路径 
  ROAD:
    for(int i = 1, now = t; i <= nNum; i++){
      if(net2[i][now] > 0){
        road[iR++] = now;
        if(i == s){
          road[iR++] = i;
          break;
        }
        now = i;
        i = 0;
      }
    }
}
//---------------------------
void Net::upNet()
{
  //1.找road最小值,更新最大流 
  int min(INT_MAX);
  for(int i = 0; i <= iR - 2; i++){
    if(net[road[i + 1]][road[i]] < min){
      min = net[road[i + 1]][road[i]];
    }
  }
  sum += min;
  //2.更新net残量,建立反向边 
  for(int i = 0; i <= iR - 2; i++){
    net[road[i + 1]][road[i]] -= min;
    net[road[i]][road[i + 1]] += min;
  } 
}
//---------------------------
void Net::forward()
{
  findRoad();
  while(iR > 0){
    upNet();
    findRoad();
  }
}

主函数

int main(){
  int n = 2;
  int m = 1;
  int s = 1;
  int t = 2;
  cin >> n >> m >> s >> t;
  Net netWork(n, s, t);
  for(int i = 1; i <= m; i++){
    int a = 0, b = 0, w = 0;
    cin >> a >> b >> w;
    netWork.net[a][b] = w;
  }
  netWork.forward();
  printf("%lld", netWork.sum);
  return 0;
}

类与函数的调试版本

IDLE的调试工具我用得不多,比较习惯于通过输出中间结果进行debug,这里记录了含输出点的代码。

#include<iostream>
#include<cstdio>
#include<climits>
#include<cstdlib>
#include<queue>
using namespace std;
const int Len = 201;
class Net
{
  public:
  int net[Len][Len];
  int *road;
  int iR;
  long long sum;
  int nNum;
  int s;
  int t;
  Net(int _nNum, int _s, int _t);
  void findRoad();
  void upNet();
  void forward();
};
//---------------------------
Net::Net(int _nNum, int _s, int _t)
  :net{}
  ,nNum(_nNum)
  ,s(_s)
  ,t(_t)
{
  iR = 0;
  sum = 0;
  road = new int[nNum]{};
}
//---------------------------
void Net::findRoad()
{
  iR = 0;
  int net2[Len][Len]{};//层次网络 
  bool pass[nNum + 1]{};//走过为true 
  queue<int> q;//辅助层次网络构建 
  q.push(s);
  //pass[s] = 1;
  //构建层次网络 
  while(1){
    int front = q.front();
    if(pass[front] == 1){
      // test pop 已过点
//      printf("pop  %d\n", front);
      //---------------------------end
      q.pop();
      // 可能越界 
      if(q.empty()){
        return;
      } 
      front = q.front();
    }
    pass[front] = 1;
    //test pop
//    printf("pop  %4d\n", front);
    //--------------------end
    q.pop();
    bool flag = 0;
    for(int i = 1; i <= nNum; i++){
      if(net[front][i] > 0 && pass[i] == 0){ 
        net2[front][i] = net[front][i];
        //pass[i] = 1; //改在出队时进行 
        //test push
//        printf("push %4d\n", i);
        //--------------------end
        q.push(i);
        if(i == t){
          goto ROAD;
        }
        flag = 1;
      }
    }
    if(flag == 0 && q.empty() == true){
      break;
    }
  }
  ROAD:
    for(int i = 1, now = t; i <= nNum; i++){
      //printf("ROAD i:%4d, now:%4d, e:%4d\n", i, now, net[i][now]);
      if(net2[i][now] > 0){
        road[iR++] = now;
        //test ROAD
//        printf("road:");
//        for(int i = iR - 1; i >= 0; i--){
//          printf("%4d", road[i]);
//        }
//        printf("\n");
        //--------------------end
        if(i == s){
          //test t to road
          //printf("to ROAD\n");
          //------------------end
          road[iR++] = i;
          break;
        }
        now = i;
        i = 0;
      }
    }
  //test 层次网络
//  printf("层次网络:\n");
//  for(int i = 1; i <= nNum; i++){
//    for(int j = 1; j <= nNum; j++){
//      printf("%4d", net2[i][j]);
//    }
//    printf("\n");
//  } 
  //--------------------end
  //test ROAD
//  printf("road:");
//  for(int i = iR - 1; i >= 0; i--){
//    printf("%4d", road[i]);
//  }
//  printf("\n");
  //------------------end
}
//---------------------------
void Net::upNet()
{
  //1.找road最小值,更新最大流 
  int min(INT_MAX);
  for(int i = 0; i <= iR - 2; i++){
    if(net[road[i + 1]][road[i]] < min){
      min = net[road[i + 1]][road[i]];
    }
  }
  sum += min;
  //test min and sum
//  printf("min:%d\n", min);
//  printf("sum:%lld\n", sum);
  //---------------------------end
  //2.更新net残量,建立反向边 
  for(int i = 0; i <= iR - 2; i++){
    net[road[i + 1]][road[i]] -= min;
    net[road[i]][road[i + 1]] += min;
  } 
  //test 残量 
//  printf("残量网络:\n");
//  for(int i = 1; i <= nNum; i++){
//    for(int j = 1; j <= nNum; j++){
//      printf("%4d", net[i][j]);
//    }
//    printf("\n");
//  } 
  //---------------------------end
}
//---------------------------
void Net::forward()
{
  findRoad();
  //test iR
//  printf("iR:%4d\n", iR);
  //---------------------------end
  while(iR > 0){
    upNet();
    findRoad();
    //test iR
//    printf("iR:%4d\n", iR);
    //---------------------------end
  }
}
相关文章
|
7天前
|
机器学习/深度学习 人工智能 算法
深度学习入门:理解神经网络与反向传播算法
【9月更文挑战第20天】本文将深入浅出地介绍深度学习中的基石—神经网络,以及背后的魔法—反向传播算法。我们将通过直观的例子和简单的数学公式,带你领略这一技术的魅力。无论你是编程新手,还是有一定基础的开发者,这篇文章都将为你打开深度学习的大门,让你对神经网络的工作原理有一个清晰的认识。
|
8天前
|
机器学习/深度学习 人工智能 算法
鸟类识别系统Python+卷积神经网络算法+深度学习+人工智能+TensorFlow+ResNet50算法模型+图像识别
鸟类识别系统。本系统采用Python作为主要开发语言,通过使用加利福利亚大学开源的200种鸟类图像作为数据集。使用TensorFlow搭建ResNet50卷积神经网络算法模型,然后进行模型的迭代训练,得到一个识别精度较高的模型,然后在保存为本地的H5格式文件。在使用Django开发Web网页端操作界面,实现用户上传一张鸟类图像,识别其名称。
51 12
鸟类识别系统Python+卷积神经网络算法+深度学习+人工智能+TensorFlow+ResNet50算法模型+图像识别
|
10天前
|
机器学习/深度学习 算法 数据安全/隐私保护
基于PSO粒子群优化的GroupCNN分组卷积网络时间序列预测算法matlab仿真
本项目展示了一种结合粒子群优化(PSO)与分组卷积神经网络(GroupCNN)的时间序列预测算法。该算法通过PSO寻找最优网络结构和超参数,提高预测准确性与效率。软件基于MATLAB 2022a,提供完整代码及详细中文注释,并附带操作步骤视频。分组卷积有效降低了计算成本,而PSO则智能调整网络参数。此方法特别适用于金融市场预测和天气预报等场景。
WK
|
16天前
|
机器学习/深度学习 自然语言处理 算法
PSO算法和人工神经网络有什么不同
PSO算法(粒子群优化)与人工神经网络(ANN)在原理、应用及优化方式上差异显著。PSO模拟鸟群行为,通过粒子协作在解空间中搜索最优解;而ANN模仿大脑神经元结构,通过训练学习输入输出映射,适用于模式识别、图像处理等领域。PSO主要用于优化问题,实时性高,结果直观;ANN则在处理复杂非线性关系方面更强大,但结构复杂,训练耗时长,结果解释性较差。实际应用中需根据需求选择合适技术。
WK
16 0
|
16天前
|
机器学习/深度学习 算法
基于小波神经网络的数据分类算法matlab仿真
该程序基于小波神经网络实现数据分类,输入为5个特征值,输出为“是”或“否”。使用MATLAB 2022a版本,50组数据训练,30组数据验证。通过小波函数捕捉数据局部特征,提高分类性能。训练误差和识别结果通过图表展示。
|
1月前
|
机器学习/深度学习 算法 文件存储
【博士每天一篇文献-算法】 PNN网络启发的神经网络结构搜索算法Progressive neural architecture search
本文提出了一种名为渐进式神经架构搜索(Progressive Neural Architecture Search, PNAS)的方法,它使用顺序模型优化策略和替代模型来逐步搜索并优化卷积神经网络结构,从而提高了搜索效率并减少了训练成本。
36 9
|
1月前
|
算法
基于多路径路由的全局感知网络流量分配优化算法matlab仿真
本文提出一种全局感知网络流量分配优化算法,针对现代网络中多路径路由的需求,旨在均衡分配流量、减轻拥塞并提升吞吐量。算法基于网络模型G(N, M),包含N节点与M连接,并考虑K种不同优先级的流量。通过迭代调整每种流量在各路径上的分配比例,依据带宽利用率um=Σ(xm,k * dk) / cm来优化网络性能,确保高优先级流量的有效传输同时最大化利用网络资源。算法设定收敛条件以避免陷入局部最优解。
|
2月前
|
传感器 算法
基于无线传感器网络的MCKP-MMF算法matlab仿真
MCKP-MMF算法是一种启发式流量估计方法,用于寻找无线传感器网络的局部最优解。它从最小配置开始,逐步优化部分解,调整访问点的状态。算法处理访问点的动态影响半径,根据带宽需求调整,以避免拥塞。在MATLAB 2022a中进行了仿真,显示了访问点半径请求变化和代价函数随时间的演变。算法分两阶段:慢启动阶段识别瓶颈并重设半径,随后进入周期性调整阶段,追求最大最小公平性。
基于无线传感器网络的MCKP-MMF算法matlab仿真
|
1月前
|
数据采集 算法 数据可视化
【优秀python算法设计】基于Python网络爬虫的今日头条新闻数据分析与热度预测模型构建的设计与实现
本文设计并实现了一个基于Python网络爬虫和机器学习模型的今日头条新闻数据分析与热度预测系统,通过数据采集、特征工程、模型构建和可视化展示,挖掘用户行为信息和内容特征,预测新闻热度,为内容推荐和舆情监控提供决策支持。
【优秀python算法设计】基于Python网络爬虫的今日头条新闻数据分析与热度预测模型构建的设计与实现
|
2月前
|
算法 网络性能优化 调度
基于De-Jitter Buffer算法的无线网络业务调度matlab仿真,对比RR调度算法
1. **功能描述**: 提出了一个去抖动缓冲区感知调度器,结合用户终端的缓冲状态减少服务中断。该算法通过动态调整数据包发送速率以优化网络延迟和吞吐量。 2. **测试结果**: 使用MATLAB 2022a进行了仿真测试,结果显示De-Jitter Buffer算法在网络拥塞时比RR调度算法更能有效利用资源,减少延迟,并能根据网络状态动态调整发送速率。 3. **核心程序**: MATLAB代码实现了调度逻辑,包括排序、流量更新、超时和中断处理等功能。 仿真结果和算法原理验证了De-Jitter Buffer算法在无线网络调度中的优势。