Algorithm 迭代DFS中的边缘分类

Algorithm 迭代DFS中的边缘分类,algorithm,depth-first-search,Algorithm,Depth First Search,可以使用递归DFS根据三个类别(后边、树/前边、交叉边)对边进行分类,递归DFS将节点标记为未访问、已发现或已完成(或白色、灰色、黑色) 我们还可以使用迭代算法(cf)对边缘进行分类吗 此版本仅使用两个类别:未访问和已发现。我们可以在将所有相邻节点推送到堆栈后将节点标记为完成,但它不会给出预期的结果 编辑(澄清):问题是,我们是否可以修改上面给出的DFS迭代版本,以便将边分类为树/前进边、交叉边和后边,就像递归版本通常利用节点标签/颜色一样?假设您使用递归版本。然后可以对其进行如下修改: DFS

可以使用递归DFS根据三个类别(后边、树/前边、交叉边)对边进行分类,递归DFS将节点标记为未访问、已发现或已完成(或白色、灰色、黑色)

我们还可以使用迭代算法(cf)对边缘进行分类吗

此版本仅使用两个类别:未访问和已发现。我们可以在将所有相邻节点推送到堆栈后将节点标记为完成,但它不会给出预期的结果


编辑(澄清):问题是,我们是否可以修改上面给出的DFS迭代版本,以便将边分类为树/前进边、交叉边和后边,就像递归版本通常利用节点标签/颜色一样?

假设您使用递归版本。然后可以对其进行如下修改:

DFS(G,v):
    v.discovered = True
    for all edges from v to w in G.adjacentEdges(v) do
    if not w.discovered then
        recursively call DFS(G,w)
    v.finished = True
DFS-iterative(G,v):
    let S be a stack
    S.push(v)
    while S is not empty do
        v = S.pop()
        if not v.discovered do
            v.discovered = True
            for all edges from v to w in G.adjacentEdges(v) do
                if w.discovered do
                    w.parent.numActiveChildren = w.parent.numActiveChildren - 1
                v.numActiveChildren = v.numActiveChildren + 1
                w.parent = v
                S.push(w)

            while v != Nil and v.numActiveChildren = 0 do
                v.finished = True
                v = v.parent
                if v != Nil do
                    v.numActiveChildren = v.numActiveChildren - 1 
使用的想法,众所周知:

  • 如果一条边指向未发现的顶点,则该边为树边

  • 如果一条边指向一个已发现但尚未完成的顶点,则该边是一条后向边

  • 边是交叉边或向前边,否则

所以现在唯一的问题是让它迭代。唯一的区别是我们现在需要处理递归以前为我们做的事情。假设每个顶点的
numativechildren
设置为0,而
parent
设置为
Nil
。迭代版本可以如下所示:

DFS(G,v):
    v.discovered = True
    for all edges from v to w in G.adjacentEdges(v) do
    if not w.discovered then
        recursively call DFS(G,w)
    v.finished = True
DFS-iterative(G,v):
    let S be a stack
    S.push(v)
    while S is not empty do
        v = S.pop()
        if not v.discovered do
            v.discovered = True
            for all edges from v to w in G.adjacentEdges(v) do
                if w.discovered do
                    w.parent.numActiveChildren = w.parent.numActiveChildren - 1
                v.numActiveChildren = v.numActiveChildren + 1
                w.parent = v
                S.push(w)

            while v != Nil and v.numActiveChildren = 0 do
                v.finished = True
                v = v.parent
                if v != Nil do
                    v.numActiveChildren = v.numActiveChildren - 1 

我的解决方案是使用堆栈和“当前子”指针模拟递归

当我们从标准DFS堆栈中查看节点时,我们将检查当前子指针是否指向该节点邻接列表的末尾。如果是,则此节点已完成。如果没有,我们会将此子节点(如果符合条件)推送到DFS堆栈,而不更新当前子指针。这将允许我们稍后对此子项执行后处理

作为一个例子,从UVA评判中考虑10199名导游。它基本上要求我们找到关节点,这在很大程度上取决于边缘分类

发件人:

void CutVertexFinder::DFS(int root){
用于(自动和子:图形[根]){
如果(child==parent[root])继续;
if(父[子]==-1){
//树缘
父[子]=根;
条目[子项]=时间++;
最远的祖先[子女]=子女;
num_children[根]+;
DFS(儿童);
if(条目[最远的祖先[子]]<条目[最远的祖先[根]){
最远的祖先[根]=最远的祖先[子];
}
}else if(条目[child]<条目[root]){
//后缘
if(条目[子项]<条目[最远的祖先[根]){
最远的祖先[根]=子;
}
}
}
}
发件人:

void CutVertexFinder::DFS(int root){
向量当前子索引(N,0);
堆栈S;
S.emplace(根);
而(!S.empty()){
int node=S.top();
const int child_index=当前子索引[节点];
if(子索引>=图[node].size()){
S.pop();
继续;
}
int child=graph[node][child_index];
if(子节点==父节点[节点]){
当前子索引[节点]+;
继续;
}
if(父[子]==-1){
父[子]=节点;
条目[子项]=时间++;
最远的祖先[子女]=子女;
num_子节点[节点]+;
美国就业(儿童);
继续;
}
if(父[子]==节点){
if(条目[最远的祖先[子]]<条目[最远的祖先[节点]){
最远的祖先[节点]=最远的祖先[子节点];
}
}else if(条目[子项]<条目[节点]){
if(条目[子项]<条目[最远的祖先[节点]){
最远的祖先[节点]=子节点;
}
}
当前子索引[节点]+;
}
}

正如您所看到的,迭代解决方案可能是一种过激的解决方案。

这是我的c++解决方案:

std::vector<bool> visited(number_of_nodes, false);
std::vector<int> entry(number_of_nodes, 0);
std::vector<int> exit(number_of_nodes, 0);

// trace stack of ancestors
std::vector<int> trace;

int time = 1;

// u is current node that moves around
int u = nodes.front();
trace.push_back(u);
visited[u] = true;

// iterative DFS with entry and exit times
while (!trace.empty()) {
    bool found = false;
    for (auto& v : neighbours_of(u)) {
        if ((!visited[v]) && (!found)) {
            found = true; // no blockage
            u = v;
            entry[u] = time++;
            visited[u] = true;
            trace.push_back(v);
            break;
        }
    }

    if (!found) {
        trace.pop_back();
        exit[u] = time++;
        u = trace.back();
    }
}
std::访问的向量(节点数,false);
std::向量条目(节点的数量,0);
std::向量出口(节点的数量,0);
//祖先的踪迹堆栈
std::矢量跟踪;
整数时间=1;
//u是移动的当前节点
int u=nodes.front();
跟踪。向后推_(u);
访问[u]=真;
//具有进入和退出时间的迭代DFS
而(!trace.empty()){
bool-found=false;
适用于(汽车和车辆:第(u)部分的第(u)部分){
如果((!已访问[v])&&(!已找到)){
found=true;//无阻塞
u=v;
条目[u]=时间++;
访问[u]=真;
痕迹。推回(v);
打破
}
}
如果(!找到){
trace.pop_back();
退出[u]=时间++;
u=trace.back();
}
}

这将获得DFS的
输入
退出
时间。可以根据使用这些时间发布的规则对边缘进行分类。你也可以在飞行中这样做,尽管规则有点不同。例如,在搜索过程中,如果我们遇到了边(u,v),并且设置了
entry[v]
,但尚未设置
exit[v]
,则(u,v)是一个后向边

我不确定你的问题是什么。请你重写一下好吗?请你也包括类别的定义。据我所知,类别的定义如下,取自:前边缘点从树的一个节点指向它的一个后代,后边缘点从一个节点指向它的一个祖先,我不知道我是否明白。如何在顶点弹出之前对其进行标记?但是,即使在弹出后将其标记为“已完成”,也不正确,因为您需要先从该顶点开始处理子树,然后再将其标记为“已完成”。你可以在递归版本中这样做,但我不知道如何在迭代版本中这样做。@nemo这是一个很好的观点。修复它并不难,但我现在不在电脑前。稍后将进行修订。
std::vector<bool> visited(number_of_nodes, false);
std::vector<int> entry(number_of_nodes, 0);
std::vector<int> exit(number_of_nodes, 0);

// trace stack of ancestors
std::vector<int> trace;

int time = 1;

// u is current node that moves around
int u = nodes.front();
trace.push_back(u);
visited[u] = true;

// iterative DFS with entry and exit times
while (!trace.empty()) {
    bool found = false;
    for (auto& v : neighbours_of(u)) {
        if ((!visited[v]) && (!found)) {
            found = true; // no blockage
            u = v;
            entry[u] = time++;
            visited[u] = true;
            trace.push_back(v);
            break;
        }
    }

    if (!found) {
        trace.pop_back();
        exit[u] = time++;
        u = trace.back();
    }
}