forked from networkx/networkx
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add is_arborescence(), is_branching().
Generalize is_tree(), is_forest() to handle directed graphs.
- Loading branch information
Showing
2 changed files
with
131 additions
and
53 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,50 +1,151 @@ | ||
#-*- coding: utf-8 -*- | ||
""" | ||
We use the following definitions: | ||
A forest is an (undirected) graph with no cycles. | ||
A tree is a connected forest. | ||
A directed forest is a directed graph whose underlying graph is a forest. | ||
A directed tree is a (weakly) connected, directed forest. | ||
Equivalently: It is a directed graph whose underlying graph is a tree. | ||
Note: Some take the term directed tree to be synonymous with an | ||
arborescence. We do not follow that convention here. | ||
Note: Since the underlying graph is a tree, any orientation defines a DAG | ||
So all directed trees are DAGs. Thus, the definition we use here is, | ||
in fact, equivalent to a polytree. | ||
A DAG is a directed graph with no directed cycles. | ||
Example: A -> B, A -> C, B -> C is a DAG that is not a directed tree. | ||
A polyforest is a DAG that is also a directed forest. | ||
A polytree is a weakly connected polyforest. | ||
Equivalently, a polytree is a DAG whose underlying graph is a tree. | ||
A branching is a polyforest with each edge directed to a different node. | ||
So the maximum in-degree is, at most, one. The maximum number of edges any | ||
branching can have is n-1. In this case, the branching spans the graph, and | ||
we have an arborescence. It is in this sense that the min/max spanning tree | ||
problem is analogous to the min/max arborescence problem. | ||
An arborescence is a (weakly) connected branching. That is, if you look | ||
at the underlying graph, it is a spanning tree. Additionally, all edges | ||
are directed away from a unique root node, for if you had two nodes with | ||
in-degree zero, then weak connectivity would force some other node | ||
to have in-degree of at least 2 (which is not allowed in branchings). | ||
""" | ||
|
||
import networkx as nx | ||
from networkx.utils import not_implemented_for | ||
__author__ = """\n""".join(['Ferdinando Papale <[email protected]>']) | ||
__all__ = ['is_tree', 'is_forest'] | ||
|
||
__author__ = """\n""".join([ | ||
'Ferdinando Papale <[email protected]>', | ||
'chebee7i <[email protected]>', | ||
]) | ||
|
||
@not_implemented_for('directed') | ||
def is_tree(G): | ||
"""Return True if the input graph is a tree | ||
|
||
__all__ = ['is_arborescence', 'is_branching', 'is_forest', 'is_tree'] | ||
|
||
@nx.utils.not_implemented_for('undirected') | ||
def is_arborescence(G): | ||
""" | ||
Returns `True` if `G` is an arborescence. | ||
""" | ||
if not is_tree(G): | ||
return False | ||
|
||
if max(G.in_degree().values()) > 1: | ||
return False | ||
|
||
return True | ||
|
||
@nx.utils.not_implemented_for('undirected') | ||
def is_branching(G): | ||
""" | ||
Returns `True` if `G` is a branching. | ||
A branching is a directed forest with maximum in-degree equal to 1. | ||
Parameters | ||
---------- | ||
G : NetworkX Graph | ||
An undirected graph. | ||
G : directed graph | ||
The directed graph to test. | ||
Returns | ||
------- | ||
True if the input graph is a tree | ||
b : bool | ||
A boolean that is `True` if `G` is a branching. | ||
Notes | ||
----- | ||
For undirected graphs only. | ||
""" | ||
n = len(G) | ||
if n == 0: | ||
raise nx.NetworkXPointlessConcept | ||
return nx.number_of_edges(G) == n - 1 | ||
if not is_forest(G): | ||
return False | ||
|
||
if max(G.in_degree().values()) > 1: | ||
return False | ||
|
||
return True | ||
|
||
@not_implemented_for('directed') | ||
def is_forest(G): | ||
"""Return True if the input graph is a forest | ||
""" | ||
Returns `True` if G is a forest. | ||
For directed graphs, the direction of edges is ignored, and the graph `G` | ||
is considered to be a directed forest if the underlying graph is a forest. | ||
""" | ||
n = G.number_of_nodes() | ||
if n == 0: | ||
raise nx.exception.NetworkXPointlessConcept('G has no nodes.') | ||
|
||
if G.is_directed(): | ||
components = nx.weakly_connected_component_subgraphs | ||
else: | ||
components = nx.connected_component_subgraphs | ||
|
||
for component in components(G): | ||
# Make sure the component is a tree. | ||
if component.number_of_edges() != component.number_of_nodes() - 1: | ||
return False | ||
|
||
return True | ||
|
||
def is_tree(G): | ||
""" | ||
Returns `True` if `G` is a tree. | ||
A tree is a simple, connected graph with no cycles. | ||
For directed graphs, the direction of edges is ignored, and the graph `G` | ||
is considered to be a directed tree if the underlying graph is a tree. | ||
Parameters | ||
---------- | ||
G : NetworkX Graph | ||
An undirected graph. | ||
G : graph | ||
The graph to test. | ||
Returns | ||
------- | ||
True if the input graph is a forest | ||
b : bool | ||
A boolean that is `True` if `G` is a tree. | ||
Notes | ||
----- | ||
For undirected graphs only. | ||
Directed trees are also known as polytrees. Sometimes, "directed tree" | ||
is defined more restrictively to mean "arboresence" instead. | ||
""" | ||
for graph in nx.connected_component_subgraphs(G): | ||
if not nx.is_tree(graph): | ||
return False | ||
return True | ||
n = G.number_of_nodes() | ||
if n == 0: | ||
raise nx.exception.NetworkXPointlessConcept('G has no nodes.') | ||
|
||
if G.is_directed(): | ||
is_connected = nx.is_weakly_connected | ||
else: | ||
is_connected = nx.is_connected | ||
|
||
# A simple, connected graph with no cycles has n-1 edges. | ||
|
||
if G.number_of_edges() != n - 1: | ||
return False | ||
|
||
return is_connected(G) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters