5

我目前正在做一个家庭作业来实现贝尔曼福特算法。到目前为止,我已经设法读取提供的图表,将其放入一个向量中(使用 1d 向量来表示具有行主要顺序的 2d 向量)以用作矩阵。我正在使用一个结构来跟踪边缘的权重,一个布尔值是否为无穷大(不存在边)和一个变量来跟踪前面的节点。

我被难住的实际上是实现 dang 算法。我已经阅读了来自http://en.wikipedia.org/wiki/Bellman%E2%80%93Ford_algorithm的伪代码,但我很难掌握如何使用该算法。前 80 行正在读取文件,初始化向量,将值扔到正确位置的向量中。下面是我开始为算法实现的内容。我确实有几个具体的问题。

1)在我找到的算法的所有解释中,您使用算法# of nodes - 1 次。在我看过的一些实现中,我倾向于从 1 开始。这是为什么呢?此外,通过我的实施,这仍然有意义吗?

2)在维基百科的伪代码中,它说要检查每条边 u,v,其中 u 是起始顶点,v 是结束顶点。在我的矩阵中,据我所知,这意味着我需要检查每行、列对的权重/值,看看是否有更好的路径。我......不确定我是否正确地解释了这一点,甚至是否理解这一点。任何建议/指导/提示/示范将不胜感激。下面是源代码和教师演示输入的粘贴。

#include <fstream>
#include <iostream>
#include <iomanip>
#include <vector>

using namespace std;

struct graphNode
{
    int value; //Weight of the edge
    bool isInfinity; //Boolean to flag an edge as infinity
    int pred; //predecessor node
};

// Code for reading inputfile cribbed and modified from http://stackoverflow.com/questions/7651243/c-read-a-file-name-from-the-command-line-and-utilize-it-in-my-file
int main(int argc, char** argv) 
{
    ifstream input; // ifstream for the input
    string inFile = ""; //name of the input file
    int row; //Variable to keep track of what row we're inputting data for
    int col; //Variable to keep track of a column thingie, expand on this later
    int infinity = 99999999;
    int nodeCount; //Number of nodes from input file
    int edgeCount = 0; //Number of edges from the input file

    vector<graphNode> edgeList; //2D list of edges, order is a->b
    edgeList.push_back(graphNode());
    edgeList[0].value = 0;
    edgeList[0].isInfinity = false;
    edgeList[0].pred = -1;

    if( argc == 2 ) 
    {
        inFile = argv[1];
    }
    else 
    {
        cout << "Usage: ./a.out inputFile\n";
        return 1;
    }

    input.open(inFile.c_str()); // opening the provided file

    if(input.is_open()) // making sure the input is open
    {
        input >> nodeCount; //Grabbing the number of nodes from the first value of the file

        for(int i = 1; i < nodeCount*nodeCount; i++)
        {
            edgeList.push_back(graphNode());
            edgeList[i].value = infinity;
            edgeList[i].isInfinity = true;
            edgeList[i].pred = -1;
        }

        //Putting data from the file into the vector array
        while(!input.eof())
        {
            input >> row; //For each cycle through the list, we grab the first number on the line to get which x value (start vertex) we're working with
            while(input.peek() != '\n' && input.peek() != '\r' && !input.eof())
            {
                input >> col;
                input >> edgeList[((row-1)*nodeCount)+(col-1)].value;
                edgeList[((row-1)*nodeCount)+(col-1)].isInfinity = false;
                edgeList[((row-1)*nodeCount)+(col-1)].pred = row;
                edgeCount++;
            }

        }
        input.close(); //Closing our input file since we don't need it anymore
    }
    else
    {
        cout << "Error, something happened with the input." << endl;
        return 1;
    }

    //for(int i = 0; i < nodeCount - 1; i++)
    //{
        //for(int r = 0; r < nodeCount - 1; r++)
        //{
            //for(int c = 0; r < nodeCount - 1; c++)
            //{
                //if(r == c) continue;
                //if(edgeList[r][c].isInfinity) continue;
                //if(edgeList[i][r] + edgeList[r][c] < edgeList[c][i])
}

演示输入:

10
3 6 4 9 0 7 8
8 5 3 7 3 4 -2
5 10 2 8 1 4 1
2 6 -3 1 3 7 1
1 10 -1 2 2 4 -2
10 9 -3 1 3 7 2 5 1
7 3 0 10 1 2 1 8 2
9 6 6 3 4 10 7
4 8 5 1 9 5 6
6 2 4 3 0 9 0
4

2 回答 2

2

对于 #1,您正在检查节点之间的边,以便最长路径不能超过 nodeCount-1 边。例如,如果 nodeCount==1,则不需要检查任何边。

对于#2,你有有趣的数据结构。看来你需要不同的。您的“graphNode”应该被称为“graphEdge”,但没有“pred”变量。声明两个新结构:

vector<int> predecessor;
vector<int> distance;

每个都是 nodeCount 大小。您在 Wikipedia 中看到的“w”是您的 edgeList。

在您注释掉的最后一部分中,外部循环正在迭代 nodeCount 次。它应该是 nodeCount-1,但没有害处。但是,您稍后的索引将关闭,因为您有一个将其视为二维的一维 edgeList。您可以通过 edgeList[(r-1)*nodeCount + c] 访问正确的边。

不确定这是否算作一个答案,但这是一个开始。

于 2013-04-17T04:41:19.047 回答
2

在此处查看有关 Bellman-Ford 算法的短视频。我认为它可以帮助您更好地理解算法: https ://class.coursera.org/algo2-2012-001/lecture/preview

贝尔曼福特背后的主要思想是:

要找到 2 个节点(例如 s 和 t)之间的最短路径,您需要迭代地找到从 s 到 t 的最短路径,并且在随后的每次迭代中,允许算法在路径中使用比前一次迭代多 1 条的边。

在任何特定的迭代 k 中,您现在允许算法在路径中使用最多k 个边,s 和 t 之间的最短路径可以

  1. 通过使用恰好k 个边或
  2. 取与从前一次迭代中找到的值相同的值,即最多使用(k - 1) 条边。

因此,在特定的迭代 k 中:

令 d[ k ][ t ] 是从 s 到节点 t 的距离,最多使用 k 条边。然后:

d[ k ][ t ] = min{ 
d[k - 1][ t ], # Case 2
for all edges (u, t) in graph min d[k - 1][ u ] + c[ u ][ t ] # Case 1
}

其中 min{ ., .} 方程的第二部分简单地找到 s 到最终目的地 t 的任何邻居 u 之间的最短路径,并添加边缘 c[ u ][ t ] 的成本以从 u 到 t ,因此需要恰好k 个边。

因此,伪代码如下所示:

d[s][0] = 0 # The cost from s to itself requiring zero edges is 0.
d[u][0] = INFINITY for all other nodes other than s (i.e. you cannot reach them with no edges).

Let n = number of nodes in graph
for k = 1 to n - 1: # Allow one more edge in path in each iteration
  for every edge (u, v) in edges of graph: # Check whether can get a better cost to v using k edges by going through node u or k - 1 edges is enough.
    d[ v ][ k ] = min{ d[k - 1][ v ], d[k - 1][ u ] + c[ u ][ v ] }

要回答您问题的第 1 部分,请考虑方程的外循环在边数上循环。它从 1 增加到 n - 1,其中 n 是图中的节点数。它最多 n - 1 的原因是路径中可以拥有的最大边数是 (n - 1) 条边(即,您最终连接所有 n 个节点以从 s 到 t,具有 n - 1 条边它们之间)。

要回答问题的第 2 部分,您只需要考虑到每个目标节点的传入节点,并检查使用 k - 1 条边到其中一个节点的路径是否加上从该节点到目标节点的成本比以前少,正如我上面解释的那样。

最后,为了检查负循环(wiki 代码中的最后一部分),您运行算法进行 1 次迭代。我们知道任何路径最多可以使用 n - 1 条边。再多就多余了。因此,如果当您允许多使用 1 个边时,任何路径的成本降低,则它一定包含一个负循环,因为这是其成本可能降低的唯一方式。任何非负循环都会导致相同或更大的成本,因为它使用了更多的边。

我强烈建议在上面的链接中观看 Tim Roughgarden 的视频。请注意,他的处理方式与维基百科中的伪代码略有不同,但想法基本相同。

于 2013-04-17T05:14:22.360 回答