我有一个DAG的邻接列表,我需要从所有节点中找到所有连接的节点,例如:对于下面的DAG
1 -> 3 -> 4
2 -> 4
3 -> 2
4 -> 5
5 -> NULL
我需要这个:
1 -> {2, 3, 4, 5}
2 -> {4, 5}
3 -> {2, 4, 5}
4 -> {5}
5 -> NULL
这有什么有效的算法吗?
答案 0 :(得分:3)
正如您提到的DAG,您可以使用以下算法将所有连接的组件提供给给定的顶点: -
- 对图中的所有节点进行拓扑排序
- 以排序节点的降序开始。
- 为每个节点维护一组所有连接的节点。
- 按照订单顺序排列顶点u的邻接列表
- 对于顶点u和每个边(u,v)和!Set(u).contains(v)do Set(u)= Set(u)union Set(v)union v
- 按照toposort的降序对所有节点执行此操作。
醇>
时间复杂度: -
Toposort: O(E)
计算集: O(V^2*logV)
总计: O(V^2*logV)
示例: - 强>
1 -> 3 -> 4
2 -> 4
3 -> 2
4 -> 5
5 -> NULL
TopoSort: 1,3,2,4,5
Visiting in descending order :-
1. Set(5) = {null}
2. Set(4) = Set(5) + 5 = {5}
3. Set(2) = Set(4) + 4 = {4,5}
4. Set(3) = Set(2) + 2 = {2,4,5}
5. Set(1) = Set(3) + 1 = {1,2,4,5}
答案 1 :(得分:2)
最直接的方法是递归地下降图表。伪代码:
function get_connected_nodes(node){
nodes = set();
foreach(child in node.children){
nodes.add(child);
nodes = nodes.union(get_connected_nodes(child));
}
return nodes;
}
示例Python实现:
adjacencies = {
1: [3],
2: [4],
3: [2, 4],
4: [5],
5: []
}
def get_connected_nodes(node):
nodes = set()
for child in adjacencies[node]:
nodes.add(child)
nodes = nodes | get_connected_nodes(child)
return nodes
for i in range(1, 6):
print i, ":", get_connected_nodes(i)
#output:
#1 : set([2, 3, 4, 5])
#2 : set([4, 5])
#3 : set([2, 4, 5])
#4 : set([5])
#5 : set([])
编辑:为了提高性能,存储以前计算的结果可以避免多次遍历节点。伪代码:
connected_results = dict();
function get_connected_nodes(node){
if(!connected_results.has_key(node)){
nodes = set();
foreach(child in node.children){
nodes.add(child);
nodes = nodes.union(get_connected_nodes(child));
}
connected_results[node] = nodes;
}
return connected_results[node];
}
示例Python实现:
def memoize(fn):
answers = {}
def memoized_fn(*args):
if args not in answers:
answers[args] = fn(*args)
return answers[args]
return memoized_fn
adjacencies = {
1: [3],
2: [4],
3: [2, 4],
4: [5],
5: []
}
@memoize
def get_connected_nodes(node):
nodes = set()
for child in adjacencies[node]:
nodes.add(child)
nodes = nodes | get_connected_nodes(child)
return nodes
for i in range(1, 6):
print i, ":", get_connected_nodes(i)
答案 2 :(得分:1)
您正在寻找的东西似乎是DAG传递闭包的邻接列表。
因为你需要效率:你有一个DAG,所以它没有周期,这意味着你可以通过DFS决定来自O(m + n)中给定起始顶点的所有顶点的可达性/ BFS。对n个顶点中的每一个执行此操作一次,最后得到O(n *(m + n)),对于具有Omega(n)边的图,它为O(n * m)。
如果你有巨大的密集DAG,矩阵乘法方法(取邻接矩阵,对它进行平方,只要adjacencyMatrix [i] [j]> 0),你就得到了一条边(i,j)快速矩阵多重化可能更快;确保相关的基准测试,因为尽管矩阵乘法方法具有渐近的优越性,但在大多数应用中,它确实往往不会优于上述方法。
如果你真的处理密集的图形(即Omega(n ^ 2)边缘)你可能也想考虑使用Ford-Warshall,甚至认为它不会超越BFS方法,纯粹是因为你' ll可能会找到一个位于某处的实现(如果没有,谷歌帮助)。
答案 3 :(得分:0)
您将获得有向非循环图(DAG),并且您打算查找从给定节点可到达的所有节点。请考虑以下重复解决方案,其中f(u)
表示可从u访问的节点集。
这只是意味着您可以从u
到达的节点是您可以从其所有邻居(相邻节点)到达的节点的并集。这可以通过DFS(深度优先搜索)以及先前计算的f(u)
集的记忆容易地实现。此方法的时间复杂度为O(|E| + |V| + |E||V|log(|V|))
,空间复杂度为O(|V|^2)
。请注意,由于|V|^2
内存要求,此算法无法很好地扩展到非常大的图形。
以下C ++代码演示了这种方法:
#include <iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<map>
#include<vector>
#include<set>
using namespace std;
#define FOR(i,n) for(int i=0;i<n;++i)
#define FORS(i,s,n) for(int i=s;i<n;++i)
#define MAX 1000000
#define WHITE 0
#define GRAY 1
#define BLACK 2
void dfs_visit(vector<int> adj[], int u, int color[], set<int> cache[])
{
color[u]=GRAY;
int len = adj[u].size();
FOR(i,len)
{
int v = adj[u][i];
if(color[v]==WHITE)
{
dfs_visit(adj,v,color,cache);
}
}
// Before coloring a node black, set its f(u) to be the union of the f(v) of its neighbours for all v
FOR(i,len)
{
int v = adj[u][i];
cache[u].insert(v);
// This will take time logarithmic in the size of f(u) since each insertion requires comparison for uniqueness
for(set<int>::iterator it = cache[v].begin(); it!=cache[v].end(); ++it)
{
cache[u].insert(*it);
}
}
color[u]=BLACK;
}
void dfs(vector<int> adj[], set<int> cache[], int n)
{
int color[n];
memset(color, WHITE, sizeof(color));
FOR(i,n)
{
if(color[i]==WHITE)
{
dfs_visit(adj, i, color, cache);
}
}
}
int main()
{
int n, m;
scanf("%d%d", &n, &m);
// To handle 0-based index, initialize as n+1
set<int> cache[n+1];
vector<int> adj[n+1];
FOR(i,m)
{
int u,v;
scanf("%d%d", &u, &v);
adj[u].push_back(v);
}
dfs(adj, cache, n);
FORS(i,1,n)
{
set<int> fu = cache[i];
printf("%d->{", i);
for(set<int>::iterator it=fu.begin(); it!=fu.end(); ++it)
{
printf("%d,", *it);
}
printf("}\n");
}
return 0;
}
输入(N =节点数,M =边数,M行跟边缘列表):
5 6
1 3
2 4
3 4
3 2
4 5
输出:
1->{2,3,4,5,}
2->{4,5,}
3->{2,4,5,}
4->{5,}