【问题标题】:C++- How to increase stack size to allow more recursion for Kosaraju's Algorithm to compute Strongly Connected ComponentsC++-如何增加堆栈大小以允许 Kosaraju 算法的更多递归来计算强连通分量
【发布时间】:2017-02-25 10:15:10
【问题描述】:

我使用的是 Mac、4GB 的 RAM 和 CLion IDE。编译器是 Clang。我需要在深度优先搜索的递归实现中允许更多递归(当前在具有 80k 节点的图上失败)。

typedef unordered_map <int, vector<int>> graph;
void DFS (graph &G, int i, vector <bool> &visited) {

    visited[i] = true;
    for (int j = 0; i < G[i].size(); j++) {
        if (!visited[G[i][j]]) {
            DFS(G, G[i][j], visited);
        }
    }
    t++;
    finishingTime[t] = i; //important step
}

这是为了实现 Kosaraju 算法来计算图中的强连通分量。 https://en.wikipedia.org/wiki/Kosaraju%27s_algorithm 我知道可以将 DFS 实现为迭代,但最后一步很重要,我找不到使用迭代包含它的方法。这是因为该步骤是在 DFS 失败并发生回溯时完成的,而递归提供了一种非常自然的方式来执行此操作。

所以目前我只有两个选择:

  • 增加堆栈大小以允许更多递归
  • 或者找一个迭代解决方案

有什么办法吗?

【问题讨论】:

  • “或者找到一个迭代的解决方案”你总是可以使用std::stack而不是递归。
  • 增加堆栈大小只会推迟问题:)
  • 你有没有看我的问题? :P 我知道有一个迭代的 DFS 实现,但它不适用于 Kosaraju 的算法。回溯发生时需要做一些修改来记录。
  • (不应该是finishingTime[i] = t;吗?//important step 是一个骇人听闻的评论 - 描述 what 一些代码要完成 - 如果 你不能让它太明显。)
  • @Rad Lexus 这在很大程度上是部分事实。堆栈大小非常有限,即使在相当普通的桌面上,堆大小也会达到数十亿字节。最终这种蛮力算法会失败,而爆栈的事实表明它可能是错误的方法,但在没有看到整个问题的情况下,我们无法对此发表评论。

标签: c++ algorithm recursion


【解决方案1】:

正如评论所建议的,您可以将每个对 DFS 的调用放在从 DFS 的参数列表生成的堆上分配的堆栈上,然后遍历堆栈。堆栈中的每个条目本质上都是一个任务。

伪代码:

Start and run "recursion":
nr_of_recursions = 0;
dfs_task_stack.push(first_task_params)
while dfs_task_stack not empty
  DFS(dfs_task_stack.pop)
  nr_of_recursions += 1
end while;
true_finishingtime[] = nr_of_recursions - finishingtime[];

DFS:
for each recursion found
  dfs_task_stack.push(task_params)
end for;
t++; finishingtime...

不确定您的算法,但您将任务推送到堆栈的顺序可能很重要,即“for each ...”的顺序。

我冒昧地将“完成时间”的含义重新定义为它的倒数。为了得到原始定义,用递归的总数减去新的完成时间。

【讨论】:

  • 什么是“task_stack”?
  • @SinByCos 根据其参数构建的 DFS 函数调用堆栈。我认为完整的 c++ 声明将是 std::stack> task_stack;与您在 DFS 中的参数列表匹配。然而,由于图形和访问对于任何递归都是“全局的”,因此 std::stack 就足够了,并在 while-loop 范围内传递全局变量。
  • 哦,好吧,我明白了。我应该如何实现“找到的每个递归”?
  • @SinByCos 与原始代码类似,但将 DFS(...) 与 dfs_task_stack.push(G[i][j]) 交换。您需要将循环顺序从自下而上翻转到自上而下以获得与以前相同的结果,但老实说,我不确定这对算法是否重要。
  • @SinByCos 随时使用您的解决方案编辑我的答案,以便我进行审查。
【解决方案2】:

我不知道这是否是最好的解决方案,但您可以通过拥有多个已访问状态,仅使用堆栈和已访问状态数组来构建完成时间列表。

下面的代码只是为了说明算法。我实际上并没有对它进行很多测试(只是一点点{{0, {1}}, {1, &lt;&gt;}, {2, &lt;&gt;}} 小测试),但我已经在过去以同样的方式使用了这种技术来制作更大的图表,我知道它是有效的。

这个想法是在被访问的节点被访问后将其保留在堆栈中,直到所有访问过的节点都弹出之前,从而模拟递归调用,但推送的数据更少,并且在堆栈对象中。

#include <iostream>
#include <vector>
#include <stack>
#include <cassert>
#include <unordered_map>

using namespace std;

typedef enum {
    vssClean,
    vssPushed,
    vssVisited
} VerticeStackState;


typedef unordered_map <int, vector<int>> graph;
void kosarajuBuildFinishOrder(const int inital, graph &G, vector<int> &finish, vector<VerticeStackState> &state, int &lastFinished) {

    assert(vssClean == state[inital]);



    std::stack<int> stack;

    stack.push(inital);
    state[inital] = vssPushed;

    int current;
    while (!stack.empty())
    {
        current = stack.top();
        if (vssPushed == state[current])
        {
            state[current] = vssVisited;
            for (const auto to: G[current])
            {
                if (state[to]==vssClean)
                {
                    state[to] = vssPushed;
                    stack.push(to);
                }
            }
        }
        else {
            assert(vssVisited == state[current]);
            stack.pop();
            finish[--lastFinished] = current;
        }
    }
}


int main() {
    graph G;
    G.insert({0, vector<int>(1, 1)});
    G.insert({1, vector<int>()});
    G.insert({2, vector<int>()});

    vector<int> finish(G.size(), 0);
    vector <VerticeStackState> state(G.size(), vssClean);


    int  lastFinished = G.size();

    for (int i=0; i < G.size(); ++i) {
        if (vssClean == state[i]){
            kosarajuBuildFinishOrder(i, G, finish, state, lastFinished);
        }
    }

    for (auto i: finish) {
        cout << i << " ";
    }

    return 0;
}

【讨论】:

    【解决方案3】:

    对于您提到的有关增加堆栈大小的选项之一,您可以这样做:

    g++ -Wl,--stack,16777216 -o kosaraju.exe kosaraju_stl.cpp
    

    这会将堆栈大小增加到 16MiB。虽然正如前面的答案中提到的,它只是推迟了问题。

    【讨论】:

      【解决方案4】:
      typedef unordered_map <int, vector<int>> graph;
      void DFS (graph &G, vector <bool> &visited) {
      
      std::stack<int> stack;
      stack.push(0);  // root 
      int i, j;
      
      while(!stack.empty())
      {
          i = stack.pop_back();
          visited[i] = true;
          for (j= (int) G[i].size() -1; j >= 0; j--) 
          {
              if (!visited[G[i][j]]) 
              {
                  stack.push_back(G[i][j]);
      
              }
          }
          t++;
          finishingTime[t] = i; //important step
        } // end while.
       }
      

      任何人都可能犯编程错误,由于我没有你的测试数据,我无法测试这个,但输出是否相同?

      【讨论】:

      • 不,这行不通。它将完成时间分配给根节点为1,而完成时间为1的节点应该是第一个遇到的没有未访问的相邻节点的节点。
      猜你喜欢
      • 1970-01-01
      • 2019-04-06
      • 1970-01-01
      • 2010-10-19
      • 2014-04-12
      • 2015-03-23
      • 1970-01-01
      • 2011-11-24
      • 2011-01-30
      相关资源
      最近更新 更多