【问题标题】:Dynamic Programming - Counting paths in a subway system动态规划 - 计算地铁系统中的路径
【发布时间】:2015-10-24 14:28:56
【问题描述】:

我在地铁系统中有一个车站网络。车站的数量,我可以在车站之间旅行的车票数量,以及哪些车站相互连接,都在一个文本文件中作为程序的输入给出。哪些站点相互连接,保存在一个二维布尔矩阵中。我必须找到从 0 站到 0 站的使用所有车票的路径数。

这里是其中一个例子:

在该示例中,有 7 个车站和 5 张车票。 开始和返回0,有6条路径:

0-1-2-3-4-0
0-1-5-3-4-0
0-1-6-3-4-0
0-4-3-6-1-0
0-4-3-5-1-0
0-4-3-2-1-0

我目前有一个在 O(N^k) 中运行的递归解决方案(N 表示车站的数量,而 k 是车票的数量),但我必须将其转换为迭代的动态规划解决方案O(k*N^2) 适用于任何输入。

#include <algorithm>
#include <fstream>
#include <iostream>
#include <map>
#include <vector>

using namespace std;


// We will represent our subway as a graph using
// an adjacency matrix to indicate which stations are
// adjacent to which other stations.
struct Subway {
  bool** connected;
  int nStations;

  Subway (int N);

private:
  // No copying allowed
  Subway (const Subway&) {}
  void operator= (const Subway&) {}
};


Subway::Subway(int N)
{
  nStations = N;
  connected = new bool*[N];
  for (int i = 0; i < N; ++i)
    {
      connected[i] = new bool[N];
      fill_n (connected[i], N, false);
    }
}

unsigned long long int callCounter = 0;
void report (int dest, int k)
{
  ++callCounter;
  // Uncomment the following statement if you want to get a feel 
  // for how many times the same subproblems get revisited
  // during the recursive solution.
  cerr << callCounter << ": (" << dest << "," << k << ")" << endl;
}


/**
 * Count the number of ways we can go from station 0 to station destination
 * traversing exactly nSteps edges.
 */
unsigned long long int tripCounter (const Subway& subway, int destination, int nSteps)
{
    report (destination, nSteps);
    if (nSteps == 1)
    {
        // Base case: We can do this in 1 step if destination is
        // directly connected to 0.
        if (subway.connected[0][destination]){
            return 1;
        }
        else{
            return 0;
        }
    }
    else
    {
        // General case: We can get to destinaiton in nSteps steps if
        // we can get to station S in (nSteps-1) steps and if S connects
        // to destination.
        unsigned long long int totalTrips = 0;
        for (int S = 0; S < subway.nStations; ++S)
        {
            if (subway.connected[S][destination])
            {
                // Recursive call
                totalTrips += tripCounter (subway, S, nSteps-1);
            }
        }
        return totalTrips;
    }
}

// Read the subway description and
// print the number of possible trips.
void solve (istream& input)
{
  int N, k;
  input >> N >> k;
  Subway subway(N);
  int station1, station2;
  while (input >> station1)
    {
      input >> station2;
      subway.connected[station1][station2] = true;
      subway.connected[station2][station1] = true;
    }
  cout << tripCounter(subway, 0, k) << endl;
  // For illustrative/debugging purposes
  cerr << "Recursive calls: " << callCounter << endl;
}




int main (int argc, char** argv)
{
  if (argc > 1) 
    {
      ifstream in (argv[1]);
      solve (in);
    }
  else
    {
      solve (cin);
    }
  return 0;
}

我不是在寻找解决方案。我目前没有想法,希望有人能指出我正确的方向。由于我需要为此实现自下而上的方法,我将如何开始使用最小的子问题开发动态规划表?

【问题讨论】:

  • 你需要使用DP吗?你要找到所有这样的路径吗?

标签: c++ algorithm c++11 dynamic-programming


【解决方案1】:

您应该构造一个数组T,每个步骤T[i] 告诉“0 和i 之间有多少条路径”。

对于 0 步,这个数组是:

[1, 0, 0, ... 0]

然后,对于每个步骤,执行:

T_new[i] = sum{0&lt;=j&lt;n}(T[j] if there is an edge (i, j))

在这些步骤中的k 之后,T[0] 将是答案。

这里有一个简单的 Python 实现来说明:

def solve(G, k):
    n = len(G)

    T = [0]*n
    T[0] = 1

    for i in xrange(k):
        T_new = [
            sum(T[j] for j in xrange(n) if G[i][j]) 
            for i in xrange(n)
        ]
        T = T_new

    return T[0]

G = [
     [0, 1, 0, 0, 1, 0, 0],
     [1, 0, 1, 0, 0, 1, 1],
     [0, 1, 0, 1, 0, 0, 0],
     [0, 0, 1, 0, 1, 1, 1],
     [1, 0, 0, 1, 0, 0, 0],
     [0, 1, 0, 1, 0, 0, 0],
     [0, 1, 0, 1, 0, 0, 0]
]

print solve(G, 5) #6

【讨论】:

    【解决方案2】:

    Dynamic programming 通过递归存储先前的子问题结果来工作。在您的情况下,子问题包括查找给定票数 k 可以到达车站的所有路径的数量。

    在基本情况下,您有 0 张车票,因此您可以到达的唯一站是没有路径的 0 站。为了启动算法,我们假设空路径也是有效路径。

    在这一点上,我建议您先拿一张纸自己尝试一下。您需要的递归类似于

    set base case (i.e. station 0 == 1 null path)    
    
    for each ticket in [1;k]
      stations = get the stations which were reached at the previous step
      for each station in stations
        spread the number of paths they were reached with to the neighbors
    
    return the number of paths for station 0 with k tickets
    

    完整的 DP 算法,最大限度地减少将其集成到代码中所需的更改次数,如下

    /**
    * Count the number of ways we can go from station 0 to station destination
    * traversing exactly nSteps edges with dynamic programming. The algorithm
    * runs in O(k*N^2) where k is the number of tickets and N the number of
    * stations.
    */
    unsigned int tripCounter(const Subway& subway, int destination, int nSteps)
    {
      map<int, vector<int>> m;
      for (int i = 0; i < nSteps + 1; ++i)
        m[i].resize(subway.nStations, 0);
    
      m[0][0] = 1; // Base case
    
      for (int t = 1; t < m.size(); ++t) { // For each ticket
    
        vector<int> reachedStations;
        for (int s = 0; s < subway.nStations; ++s) { // For each station
          if (m[t-1][s] > 0)
            reachedStations.push_back(s); // Store if it was reached in the previous state
        }
    
        for (auto s : reachedStations) {
          // Find adjacent stations
          for (int adj = 0; adj < subway.nStations; ++adj) {
            if (s == adj)
              continue;
            if (subway.connected[s][adj])
              m[t][adj] += m[t-1][s];
          }
        }
      }
      return m[nSteps][0];
    }
    

    复杂度是。 确保在使用之前了解代码。

    正如您将了解到的,迭代子问题是动态编程算法中的 common pattern

    【讨论】:

      【解决方案3】:

      我建议你考虑子问题:

      DP[i][a] = 使用 i 票从 0 到 a 的路径数

      这是用 DP[0][0] = 1 和 DP[0][a!=0] = 0 初始化的。

      您可以通过考虑节点的所有路径来获得更新公式:

      DP[i][a] = a 的所有邻居 b 的总和 DP[i-1][b]

      有kN个子问题,每个子问题需要O(N)来计算,所以总复杂度是O(kN^2)。

      最终答案由DP[k][0]给出。

      【讨论】:

      • 你会如何实现呢?对于子问题,for 块会是什么样子?
      猜你喜欢
      • 1970-01-01
      • 2019-04-29
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-07-22
      • 1970-01-01
      • 2018-05-11
      相关资源
      最近更新 更多