边缘可以根据三个类别(后边缘,树/前边缘,交叉边缘)使用递归DFS进行分类,该DFS将节点标记为未访问,发现或完成(或白色,灰色,黑色)。
我们是否可以使用算法的迭代版本对边缘进行分类(参见Depth-First Search)?
procedure DFS-iterative(G,v):
2 let S be a stack
3 S.push(v)
4 while S is not empty
5 v = S.pop()
6 if v is not labeled as discovered:
7 label v as discovered
8 for all edges from v to w in G.adjacentEdges(v) do
9 S.push(w)
此版本仅使用两个类别,未访问和已发现。在将所有相邻节点推送到堆栈之后,我们可以将节点标记为已完成,但它不会给出预期结果。
编辑(澄清):问题是,我们可以修改上面给出的DFS的迭代版本,以便将边缘分类为树/前沿,交叉边缘和后边缘,就像通常通过采用递归版本一样节点标签/颜色的优点?
答案 0 :(得分:1)
假设您使用递归版本。然后可以修改如下:
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
使用bracketing的概念,众所周知:
如果边缘是未经发现的顶点,则边缘是树边缘。
如果边缘是通向已发现且未完成的顶点,则边缘是向后边缘
否则边缘是交叉或前沿。
所以现在唯一的问题是让它迭代。唯一的区别是我们现在需要操纵递归之前为我们做过的事情。假设每个顶点的numActiveChildren
设置为0,parent
设置为Nil
。迭代版本可能如下所示:
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
答案 1 :(得分:0)
我的解决方案是使用堆栈和当前的孩子模拟递归#34;指针。
当我们查看标准DFS堆栈中的节点时,我们将检查当前子指针是否指向此节点的邻接列表的末尾。如果是,我们就完成了这个节点。如果没有,我们将把这个子节点(如果符合条件)推送到DFS堆栈而不更新当前子指针。这将允许我们稍后对这个孩子进行后期处理。
举个例子,考虑10199 - UVa Judge的旅游指南。它基本上要求我们找到关键点,这关键点取决于边缘分类。
void CutVertexFinder::DFS(int root) {
for (auto&& child : graph[root]) {
if (child == parent[root]) continue;
if (parent[child] == -1) {
// Tree edge
parent[child] = root;
entry[child] = time++;
farthest_ancestor[child] = child;
num_children[root]++;
DFS(child);
if (entry[farthest_ancestor[child]] < entry[farthest_ancestor[root]]) {
farthest_ancestor[root] = farthest_ancestor[child];
}
} else if (entry[child] < entry[root]) {
// Back edge
if (entry[child] < entry[farthest_ancestor[root]]) {
farthest_ancestor[root] = child;
}
}
}
}
void CutVertexFinder::DFS(int root) {
std::vector<int> current_child_index(N, 0);
stack<int> S;
S.emplace(root);
while (!S.empty()) {
int node = S.top();
const int child_index = current_child_index[node];
if (child_index >= graph[node].size()) {
S.pop();
continue;
}
int child = graph[node][child_index];
if (child == parent[node]) {
current_child_index[node]++;
continue;
}
if (parent[child] == -1) {
parent[child] = node;
entry[child] = time++;
farthest_ancestor[child] = child;
num_children[node]++;
S.emplace(child);
continue;
}
if (parent[child] == node) {
if (entry[farthest_ancestor[child]] < entry[farthest_ancestor[node]]) {
farthest_ancestor[node] = farthest_ancestor[child];
}
} else if (entry[child] < entry[node]) {
if (entry[child] < entry[farthest_ancestor[node]]) {
farthest_ancestor[node] = child;
}
}
current_child_index[node]++;
}
}
正如您所看到的,迭代解决方案可能是一种过度杀伤。
答案 2 :(得分:0)
这是我在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();
}
}
这将获得DFS的entry
和exit
次。可以使用这些时间根据发布的here规则对边进行分类。你也可以动态地做到这一点,虽然规则有点不同。例如,在搜索期间,如果我们遇到edge(u,v),并且entry[v]
已设置但exit[v]
尚未设置,则(u,v)是后沿。