Algorithm 如何确定给定的有向图是否是树
程序的输入是图形中的一组边。例如,考虑下面的简单有向图:Algorithm 如何确定给定的有向图是否是树,algorithm,tree,directed-graph,Algorithm,Tree,Directed Graph,程序的输入是图形中的一组边。例如,考虑下面的简单有向图: a -> b -> c # given a graph and a starting vertex, check if the graph is a tree def checkTree(G, v): # |E| = |V| - 1 if edges.size != vertices.size - 1: return false; for v in vertices:
a -> b -> c
# given a graph and a starting vertex, check if the graph is a tree
def checkTree(G, v):
# |E| = |V| - 1
if edges.size != vertices.size - 1:
return false;
for v in vertices:
visited[v] = false;
hasCycle = explore_and_check_cycles(G, v);
# a tree is acyclic
if hasCycle:
return false;
for v in vertices:
if not visited[v]: # the graph isn't connected
return false;
# otherwise passes all properties for a tree
return true;
# given a Graph and a vertex, explore all reachable vertices from the vertex
# and returns true if there are any cycles
def explore_and_check_cycles(G, v):
visited[v] = true;
for (v, u) in edges:
if not visited[u]:
return explore_and_check_cyles(G, u)
else: # a backedge between two vertices indicates a cycle
return true
return false
此图的边集为
{ (b, c), (a, b) }
给定一个有向图作为一组边,如何确定有向图是否是树?如果是树,那么树的根节点是什么
首先,我看的是如何表示这个图,邻接列表/邻接矩阵/其他任何东西?您将如何利用您选择的表述有效地回答上述问题
编辑1:
有些人正在考虑使用DFS进行周期检测,但问题是从哪个节点启动DFS。因为它是一个有向图,所以我们不能从随机节点启动DFS,例如,如果我从顶点“c”启动DFS,它将不会继续,因为没有后缘可以转到任何其他节点。接下来的问题应该是如何确定此树的根。从根开始,“标记”它,然后转到所有子级并递归重复。如果你接触到一个已经被标记的孩子,这意味着它不是一棵树。注意:这绝不是最有效的方法,但在概念上是有用的。有时你需要效率,有时你需要一个替代的观点,因为教育学的原因。这当然是后者 算法:从大小为
n
的邻接矩阵A
开始。取矩阵幂A**n
。如果矩阵对于每个条目都为零,那么您知道它至少是树的集合(森林)。如果可以显示它已连接,则它必须是一棵树。见a。了解更多信息
为了找到根节点,我们假设您已经显示了图是一个连通树。设k
为矩阵变为零之前,必须提高幂次A**k
的次数。将转置带到(k-1)
powerA.T**(k-1)
。唯一的非零项必须是根
分析:粗略的最坏情况分析表明,它的上限为
O(n^4)
,矩阵乘法最多为n
次。通过对矩阵进行对角化,您可以做得更好,这将使矩阵降到O(n^3)
。考虑到时间/空间中的这个问题,这只是逻辑和理解问题的有用练习。这里有一个相当直接的方法。它可以通过邻接矩阵或边列表来完成
如果没有关于节点和边数的信息,很难知道什么是有效的。有3个属性可以检查图形是否是树:
- (1) 图中的边数正好比顶点数| E |=| V |-1少一条
- (2) 没有周期
- (3) 图是连通的
a -> b -> c
# given a graph and a starting vertex, check if the graph is a tree
def checkTree(G, v):
# |E| = |V| - 1
if edges.size != vertices.size - 1:
return false;
for v in vertices:
visited[v] = false;
hasCycle = explore_and_check_cycles(G, v);
# a tree is acyclic
if hasCycle:
return false;
for v in vertices:
if not visited[v]: # the graph isn't connected
return false;
# otherwise passes all properties for a tree
return true;
# given a Graph and a vertex, explore all reachable vertices from the vertex
# and returns true if there are any cycles
def explore_and_check_cycles(G, v):
visited[v] = true;
for (v, u) in edges:
if not visited[u]:
return explore_and_check_cyles(G, u)
else: # a backedge between two vertices indicates a cycle
return true
return false
资料来源:
S.Dasgupta、C.H.Papadimitriou和U.V.Vazirani的算法
对于有向图,如果无向图是非循环且完全连通的,则底层无向图将是一棵树。如果对于有向图,每个顶点的阶数均为1,但其中一个顶点的阶数为0,则相同的属性适用
如果邻接列表表示也支持每个顶点的度属性,那么我们可以很容易地应用上述规则。否则,我们应该应用一个经过调整的DFS来查找底层无向图以及| E |=| V |-1的循环 以下是我为此编写的代码。请随意提出优化建议
import java.util.*;
import java.lang.*;
import java.io.*;
class Graph
{
private static int V;
private static int adj[][];
static void initializeGraph(int n)
{
V=n+1;
adj = new int[V][V];
for(int i=0;i<V;i++)
{
for(int j=0;j<V ;j++)
adj[i][j]= 0;
}
}
static int isTree(int edges[][],int n)
{
initializeGraph(n);
for(int i=0;i< edges.length;i++)
{
addDirectedEdge(edges[i][0],edges[i][1]);
}
int root = findRoot();
if(root == -1)
return -1;
boolean visited[] = new boolean[V];
boolean isTree = isTree(root, visited, -1);
boolean isConnected = isConnected(visited);
System.out.println("isTree= "+ isTree + " isConnected= "+ isConnected);
if(isTree && isConnected)
return root;
else
return -1;
}
static boolean isTree(int node, boolean visited[], int parent)
{
// System.out.println("node =" +node +" parent" +parent);
visited[node] = true;int j;
for(j =1;j<V;j++)
{
// System.out.println("node =" +node + " j=" +j+ "parent" + parent);
if(adj[node][j]==1)
{
if(visited[j])
{
// System.out.println("returning false for j="+ j);
return false;
}
else
{ //visit all adjacent vertices
boolean child = isTree(j, visited, node);
if(!child)
{
// System.out.println("returning false for j="+ j + " node=" +node);
return false;
}
}
}
}
if(j==V)
return true;
else
return false;
}
static int findRoot()
{
int root =-1, j=0,i;
int count =0;
for(j=1;j<V ;j++)
{
count=0;
for(i=1 ;i<V;i++)
{
if(adj[i][j]==1)
count++;
}
// System.out.println("j"+j +" count="+count);
if(count==0)
{
// System.out.println(j);
return j;
}
}
return -1;
}
static void addDirectedEdge(int s, int d)
{
// System.out.println("s="+ s+"d="+d);
adj[s][d]=1;
}
static boolean isConnected(boolean visited[])
{
for(int i=1; i<V;i++)
{
if(!visited[i])
return false;
}
return true;
}
public static void main (String[] args) throws java.lang.Exception
{
int edges[][]= {{2,3},{2,4},{3,1},{3,5},{3,7},{4,6}, {2,8}, {8,9}};
int n=9;
int root = isTree(edges,n);
System.out.println("root is:" + root);
int edges2[][]= {{2,3},{2,4},{3,1},{3,5},{3,7},{4,6}, {2,8}, {6,3}};
int n2=8;
root = isTree(edges2,n2);
System.out.println("root is:" + root);
}
}
import java.util.*;
导入java.lang.*;
导入java.io.*;
类图
{
专用静态INTV;
私有静态int adj[][];
静态无效初始化图(int n)
{
V=n+1;
adj=新整数[V][V];
对于(int i=0;i家庭作业?面试问题?可能只是一个注释的重复:由于原始图形是定向的,要使用人们在下面描述的算法,您需要将边视为无向/双向。如何“从根开始”?您没有获得根。嗯……您需要找到一个没有父节点的节点,并将其称为“根”…此外,您还需要确保标记所有节点,因为它可能不是树而是林…实际上,如果您考虑它,您可以从任何节点开始,只要您确保标记所有节点…此外,如果终止时仍有未访问的顶点,则图形具有多个连接组件,因此不是树(虽然它可能是一片森林…)幂零并不意味着“全零”。如果A**k=0
,那么A
本身就是。@TedHopp我同意,但矩阵幂零的概念与图中循环的存在有关。为了清晰起见,我将对其进行编辑。我在考虑类似的问题。我使用邻接列表来存储图,因为我可以一次处理一条边并构建列表。我想知道……是吗足够验证属性1和3了吗?我没有给出一个具有属性1和3并且仍然是循环的图的例子。。。