C++-如何增加堆栈大小以允许Kosaraju算法进行更多递归以计算强连接组件

C++- How to increase stack size to allow more recursion for Kosaraju's Algorithm to compute Strongly Connected Components

本文关键字:算法 递归 组件 连接 计算 Kosaraju 何增加 增加 C++- 堆栈      更新时间:2023-10-16

我使用的是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失败并发生回溯时,就会执行该步骤,而递归提供了一种非常自然的方法。

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

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

有什么办法吗?

根据注释的建议,您可以将对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...

不确定你的算法,但你将任务推到堆栈的顺序可能很重要,即"针对每个…"的顺序。

我冒昧地将"结束时间"的含义重新定义为相反的含义。要获得原始定义,请将新的finishingtime减去递归的总数。

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

下面的代码只是为了说明算法。我实际上并没有对它进行太多测试(只是一个{{0, {1}}, {1, <>}, {2, <>}}小测试(,但我过去已经以同样的方式对更大的图使用了这种技术,我知道它是有效的。

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

#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;
}

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

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

这将使堆栈大小增加到16MiB。正如前面的回答所提到的,这只是推迟了问题的解决。

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.
 }

任何人都可能犯编程错误,因为我没有你的测试数据,所以我不能测试这个,但输出是一样的吗?