"""
算法用于寻找 k-边增广
k-边增广是一组边,一旦添加到图中,就能确保图是 k-边连通的;即除非移除 k 条或更多边,否则图不会断开连接。通常,目标是找到具有最小权重的增广。一般来说,不能保证存在 k-边增广。
See Also
--------
:mod:`edge_kcomponents` : 用于寻找 k-边连通分量的算法
:mod:`connectivity` : 用于确定边连通性的算法
"""
import itertools as it
import math
from collections import defaultdict, namedtuple
import networkx as nx
from networkx.utils import not_implemented_for, py_random_state
__all__ = ["k_edge_augmentation", "is_k_edge_connected", "is_locally_k_edge_connected"]
[docs]
@not_implemented_for("directed")
@not_implemented_for("multigraph")
@nx._dispatchable
def is_k_edge_connected(G, k):
"""测试一个图是否为k边连通图。
是否不可能通过移除少于k条边来断开图?
如果是,那么G是k边连通图。
Parameters
----------
G : NetworkX图
一个无向图。
k : 整数
要测试的边连通性
Returns
-------
布尔值
如果G是k边连通图,则返回True。
See Also
--------
:func:`is_locally_k_edge_connected`
Examples
--------
>>> G = nx.barbell_graph(10, 0)
>>> nx.is_k_edge_connected(G, k=1)
True
>>> nx.is_k_edge_connected(G, k=2)
False
"""
if k < 1:
raise ValueError(f"k must be positive, not {k}")
# First try to quickly determine if G is not k-edge-connected
if G.number_of_nodes() < k + 1:
return False
elif any(d < k for n, d in G.degree()):
return False
else:
# Otherwise perform the full check
if k == 1:
return nx.is_connected(G)
elif k == 2:
return nx.is_connected(G) and not nx.has_bridges(G)
else:
return nx.edge_connectivity(G, cutoff=k) >= k
[docs]
@not_implemented_for("directed")
@not_implemented_for("multigraph")
@nx._dispatchable
def is_locally_k_edge_connected(G, s, t, k):
"""测试图中的一个边是否是局部k边连通的。
是否不可能通过移除少于k条边来断开s和t?
如果是,那么s和t在G中是局部k边连通的。
Parameters
----------
G : NetworkX图
一个无向图。
s : 节点
源节点
t : 节点
目标节点
k : 整数
s和t节点的局部边连通性
Returns
-------
布尔值
如果s和t在G中是局部k边连通的,则返回True。
See Also
--------
:func:`is_k_edge_connected`
Examples
--------
>>> from networkx.algorithms.connectivity import is_locally_k_edge_connected
>>> G = nx.barbell_graph(10, 0)
>>> is_locally_k_edge_connected(G, 5, 15, k=1)
True
>>> is_locally_k_edge_connected(G, 5, 15, k=2)
False
>>> is_locally_k_edge_connected(G, 1, 5, k=2)
True
"""
if k < 1:
raise ValueError(f"k must be positive, not {k}")
# First try to quickly determine s, t is not k-locally-edge-connected in G
if G.degree(s) < k or G.degree(t) < k:
return False
else:
# Otherwise perform the full check
if k == 1:
return nx.has_path(G, s, t)
else:
localk = nx.connectivity.local_edge_connectivity(G, s, t, cutoff=k)
return localk >= k
[docs]
@not_implemented_for("directed")
@not_implemented_for("multigraph")
@nx._dispatchable
def k_edge_augmentation(G, k, avail=None, weight=None, partial=False):
"""查找使G成为k边连通图的一组边。
向G中添加这些增广边后,除非移除k条或更多边,否则无法断开G。此函数使用最有效的可用函数(取决于k值以及问题是否加权)来搜索可用边的最小权重子集,以使G成为k边连通图。一般来说,寻找k边增广问题是NP难的,因此不能保证解是最小的。此外,可能不存在k边增广。
Parameters
----------
G : NetworkX图
一个无向图。
k : 整数
期望的边连通度
avail : 字典或2或3元组集合
可用于增广的边。
如果未指定,则G的补图中的所有边都可用。否则,每个项是一条可用边(可选带权重)。
在无权重情况下,每个项是一条边 ``(u, v)`` 。
在加权情况下,每个项是一个3元组 ``(u, v, d)`` 或一个字典,键值对为 ``(u, v): d`` 。第三个项 ``d`` 可以是一个字典或一个实数。如果 ``d`` 是一个字典, ``d[weight]`` 对应于权重。
weight : 字符串
如果 ``avail`` 是一个包含字典的3元组集合,用于查找权重的键。
partial : 布尔值
如果partial为True且不存在可行的k边增广,则生成一个部分k边增广。将部分增广中的边添加到G中,最小化k边连通分量的数量,并最大化这些分量之间的边连通度。详细信息请参阅:func:`partial_k_edge_augmentation` 。
Yields
------
edge : 元组
一旦添加到G中,将使G成为k边连通的边。如果partial为False且无法实现,则会引发错误。否则,生成的边形成一个部分增广,尽可能地k边连通G的任何部分,并最大限度地连接剩余部分。
Raises
------
NetworkXUnfeasible
如果partial为False且不存在k边增广。
NetworkXNotImplemented
如果输入图是有向图或多重图。
ValueError:
如果k小于1
Notes
-----
当k=1时,返回一个最优解。
当k=2且 ``avail`` 为None时,返回一个最优解。否则当k=2时,返回一个最优解的2近似。
对于k>3,此问题是NP难的,使用随机算法生成一个可行解,但不保证解的权重。
Examples
--------
>>> # 无权重情况
>>> G = nx.path_graph((1, 2, 3, 4))
>>> G.add_node(5)
>>> sorted(nx.k_edge_augmentation(G, k=1))
[(1, 5)]
>>> sorted(nx.k_edge_augmentation(G, k=2))
[(1, 5), (5, 4)]
>>> sorted(nx.k_edge_augmentation(G, k=3))
[(1, 4), (1, 5), (2, 5), (3, 5), (4, 5)]
>>> complement = list(nx.k_edge_augmentation(G, k=5, partial=True))
>>> G.add_edges_from(complement)
>>> nx.edge_connectivity(G)
4
>>> # 加权情况
>>> G = nx.path_graph((1, 2, 3, 4))
>>> G.add_node(5)
>>> # avail可以是一个包含字典的元组
>>> avail = [(1, 5, {"weight": 11}), (2, 5, {"weight": 10})]
>>> sorted(nx.k_edge_augmentation(G, k=1, avail=avail, weight="weight"))
[(2, 5)]
>>> # 或者avail可以是一个包含实数的3元组
>>> avail = [(1, 5, 11), (2, 5, 10), (4, 3, 1), (4, 5, 51)]
>>> sorted(nx.k_edge_augmentation(G, k=2, avail=avail))
[(1, 5), (2, 5), (4, 5)]
>>> # 或者avail可以是一个字典
>>> avail = {(1, 5): 11, (2, 5): 10, (4, 3): 1, (4, 5): 51}
>>> sorted(nx.k_edge_augmentation(G, k=2, avail=avail))
[(1, 5), (2, 5), (4, 5)]
>>> # 如果增广不可行,则可以找到一个部分解
>>> avail = {(1, 5): 11}
>>> sorted(nx.k_edge_augmentation(G, k=2, avail=avail, partial=True))
[(1, 5)]
"""
try:
if k <= 0:
raise ValueError(f"k must be a positive integer, not {k}")
elif G.number_of_nodes() < k + 1:
msg = f"impossible to {k} connect in graph with less than {k + 1} nodes"
raise nx.NetworkXUnfeasible(msg)
elif avail is not None and len(avail) == 0:
if not nx.is_k_edge_connected(G, k):
raise nx.NetworkXUnfeasible("no available edges")
aug_edges = []
elif k == 1:
aug_edges = one_edge_augmentation(
G, avail=avail, weight=weight, partial=partial
)
elif k == 2:
aug_edges = bridge_augmentation(G, avail=avail, weight=weight)
else:
# raise NotImplementedError(f'not implemented for k>2. k={k}')
aug_edges = greedy_k_edge_augmentation(
G, k=k, avail=avail, weight=weight, seed=0
)
# Do eager evaluation so we can catch any exceptions
# Before executing partial code.
yield from list(aug_edges)
except nx.NetworkXUnfeasible:
if partial:
# Return all available edges
if avail is None:
aug_edges = complement_edges(G)
else:
# If we can't k-edge-connect the entire graph, try to
# k-edge-connect as much as possible
aug_edges = partial_k_edge_augmentation(
G, k=k, avail=avail, weight=weight
)
yield from aug_edges
else:
raise
@nx._dispatchable
def partial_k_edge_augmentation(G, k, avail, weight=None):
"""找到能够尽可能多地k边连接图的增强。
当无法实现k边增强时,我们仍然可以尝试找到一组小的边,尽可能多地部分k边连接图。在剩余部分之间生成所有可能的边。这最小化了结果图中k边连通子图的数量,并最大化这些子图之间的边连通性。
Parameters
----------
G : NetworkX图
一个无向图。
k : 整数
期望的边连通性
avail : 字典或2或3元组的集合
更多详情,请参阅 :func:`k_edge_augmentation` 。
weight : 字符串
如果 ``avail`` 是3元组的集合,用于查找权重的键。
更多详情,请参阅 :func:`k_edge_augmentation` 。
Yields
------
edge : 元组
部分增强G中的边。这些边k边连接G中任何可能的部分,并最大限度地连接剩余部分。换句话说,生成avail中的所有边,除了那些已经在k边连通子图中的边。
Notes
-----
构建H,用avail中的所有边增强G。
找到H的k边子图。
对于每个k边子图,如果节点数大于k,则找到该图的k边增强并将其添加到解决方案中。然后在k边子图之间添加avail中的所有边到解决方案中。
See Also
--------
:func:`k_edge_augmentation`
Examples
--------
>>> G = nx.path_graph((1, 2, 3, 4, 5, 6, 7))
>>> G.add_node(8)
>>> avail = [(1, 3), (1, 4), (1, 5), (2, 4), (2, 5), (3, 5), (1, 8)]
>>> sorted(partial_k_edge_augmentation(G, k=2, avail=avail))
[(1, 5), (1, 8)]
"""
def _edges_between_disjoint(H, only1, only2):
"""找到不相交节点之间的边缘"""
only1_adj = {u: set(H.adj[u]) for u in only1}
for u, neighbs in only1_adj.items():
# Find the neighbors of u in only1 that are also in only2
neighbs12 = neighbs.intersection(only2)
for v in neighbs12:
yield (u, v)
avail_uv, avail_w = _unpack_available_edges(avail, weight=weight, G=G)
# Find which parts of the graph can be k-edge-connected
H = G.copy()
H.add_edges_from(
(
(u, v, {"weight": w, "generator": (u, v)})
for (u, v), w in zip(avail, avail_w)
)
)
k_edge_subgraphs = list(nx.k_edge_subgraphs(H, k=k))
# Generate edges to k-edge-connect internal subgraphs
for nodes in k_edge_subgraphs:
if len(nodes) > 1:
# Get the k-edge-connected subgraph
C = H.subgraph(nodes).copy()
# Find the internal edges that were available
sub_avail = {
d["generator"]: d["weight"]
for (u, v, d) in C.edges(data=True)
if "generator" in d
}
# Remove potential augmenting edges
C.remove_edges_from(sub_avail.keys())
# Find a subset of these edges that makes the component
# k-edge-connected and ignore the rest
yield from nx.k_edge_augmentation(C, k=k, avail=sub_avail)
# Generate all edges between CCs that could not be k-edge-connected
for cc1, cc2 in it.combinations(k_edge_subgraphs, 2):
for u, v in _edges_between_disjoint(H, cc1, cc2):
d = H.get_edge_data(u, v)
edge = d.get("generator", None)
if edge is not None:
yield edge
@not_implemented_for("multigraph")
@not_implemented_for("directed")
@nx._dispatchable
def one_edge_augmentation(G, avail=None, weight=None, partial=False):
"""找到连接图G的最小权重边集。
等价于当k=1时的 :func:`k_edge_augmentation` 。将结果边添加到G中将使其成为1-边连通的。该解决方案对加权和非加权变体都是最优的。
Parameters
----------
G : NetworkX图
一个无向图。
avail : 字典或2或3元组集合
更多详情,参见 :func:`k_edge_augmentation` 。
weight : 字符串
如果 ``avail`` 是3元组集合,用于查找权重的键。
更多详情,参见 :func:`k_edge_augmentation` 。
partial : 布尔值
如果partial为True且不存在可行的k-边增广,则增广边最小化连通分量的数量。
Yields
------
edge : 元组
G的1-增广中的边
Raises
------
NetworkXUnfeasible
如果partial为False且不存在1-边增广。
Notes
-----
根据是否指定 ``avail`` ,使用 :func:`unconstrained_one_edge_augmentation` 或 :func:`weighted_one_edge_augmentation` 。两种算法都基于寻找最小生成树。因此,两种算法都找到最优解并在线性时间内运行。
See Also
--------
:func:`k_edge_augmentation`
"""
if avail is None:
return unconstrained_one_edge_augmentation(G)
else:
return weighted_one_edge_augmentation(
G, avail=avail, weight=weight, partial=partial
)
@not_implemented_for("multigraph")
@not_implemented_for("directed")
@nx._dispatchable
def bridge_augmentation(G, avail=None, weight=None):
"""找到一组连接G的桥接边。
等价于当k=2且partial=False时的:func:`k_edge_augmentation` 。将结果边添加到G中将使其成为2边连通的。如果没有指定约束条件,返回的边集是最小且最优的,否则解决方案是近似的。
Parameters
----------
G : NetworkX图
一个无向图。
avail : 字典或一组2或3元组
更多详情,参见:func:`k_edge_augmentation` 。
weight : 字符串
如果 ``avail`` 是一组3元组,用于查找权重的键。
更多详情,参见:func:`k_edge_augmentation` 。
Yields
------
edge : 元组
G的桥接增强中的边
Raises
------
NetworkXUnfeasible
如果不存在桥接增强。
Notes
-----
如果没有约束条件,可以使用:func:`unconstrained_bridge_augmentation` 在
线性时间内计算解决方案。否则,问题变为NP-hard,解决方案由
:func:`weighted_bridge_augmentation` 近似计算。
See Also
--------
:func:`k_edge_augmentation`
"""
if G.number_of_nodes() < 3:
raise nx.NetworkXUnfeasible("impossible to bridge connect less than 3 nodes")
if avail is None:
return unconstrained_bridge_augmentation(G)
else:
return weighted_bridge_augmentation(G, avail, weight=weight)
# --- Algorithms and Helpers ---
def _ordered(u, v):
"""返回无向边中的节点,按下三角顺序排列"""
return (u, v) if u < v else (v, u)
def _unpack_available_edges(avail, weight=None, G=None):
"""辅助函数,用于将可用边及其对应权重分离"""
if weight is None:
weight = "weight"
if isinstance(avail, dict):
avail_uv = list(avail.keys())
avail_w = list(avail.values())
else:
def _try_getitem(d):
try:
return d[weight]
except TypeError:
return d
avail_uv = [tup[0:2] for tup in avail]
avail_w = [1 if len(tup) == 2 else _try_getitem(tup[-1]) for tup in avail]
if G is not None:
# Edges already in the graph are filtered
flags = [not G.has_edge(u, v) for u, v in avail_uv]
avail_uv = list(it.compress(avail_uv, flags))
avail_w = list(it.compress(avail_w, flags))
return avail_uv, avail_w
MetaEdge = namedtuple("MetaEdge", ("meta_uv", "uv", "w"))
def _lightest_meta_edges(mapping, avail_uv, avail_w):
"""将原始图中的可用边映射到元图中的边。
Parameters
----------
mapping : dict
由 :func:`collapse` 生成的映射,将原始图中的每个节点映射到元图中的一个节点
avail_uv : list
边列表
avail_w : list
边权重列表
Notes
-----
元图中的每个节点是原始图中的一个 k-边连通分量。我们不关心同一 k-边连通分量内的任何边,因此忽略自环边。我们还只对连接每个 k-边连通分量的最小权重边感兴趣,因此我们按元边分组并取每组中最轻的边。
Examples
--------
>>> # 每个组代表一个元节点
>>> groups = ([1, 2, 3], [4, 5], [6])
>>> mapping = {n: meta_n for meta_n, ns in enumerate(groups) for n in ns}
>>> avail_uv = [(1, 2), (3, 6), (1, 4), (5, 2), (6, 1), (2, 6), (3, 1)]
>>> avail_w = [20, 99, 20, 15, 50, 99, 20]
>>> sorted(_lightest_meta_edges(mapping, avail_uv, avail_w))
[MetaEdge(meta_uv=(0, 1), uv=(5, 2), w=15), MetaEdge(meta_uv=(0, 2), uv=(6, 1), w=50)]
"""
grouped_wuv = defaultdict(list)
for w, (u, v) in zip(avail_w, avail_uv):
# Order the meta-edge so it can be used as a dict key
meta_uv = _ordered(mapping[u], mapping[v])
# Group each available edge using the meta-edge as a key
grouped_wuv[meta_uv].append((w, u, v))
# Now that all available edges are grouped, choose one per group
for (mu, mv), choices_wuv in grouped_wuv.items():
# Ignore available edges within the same meta-node
if mu != mv:
# Choose the lightest available edge belonging to each meta-edge
w, u, v = min(choices_wuv)
yield MetaEdge((mu, mv), (u, v), w)
@nx._dispatchable
def unconstrained_one_edge_augmentation(G):
"""找到连接G的最小边集。
这是无权最小生成树问题的一个变种。
如果G不为空,总是存在一个可行解。
Parameters
----------
G : NetworkX图
一个无向图。
Yields
------
edge : 元组
G的一边增广中的边
See Also
--------
:func:`one_edge_augmentation`
:func:`k_edge_augmentation`
Examples
--------
>>> G = nx.Graph([(1, 2), (2, 3), (4, 5)])
>>> G.add_nodes_from([6, 7, 8])
>>> sorted(unconstrained_one_edge_augmentation(G))
[(1, 4), (4, 6), (6, 7), (7, 8)]
"""
ccs1 = list(nx.connected_components(G))
C = collapse(G, ccs1)
# When we are not constrained, we can just make a meta graph tree.
meta_nodes = list(C.nodes())
# build a path in the metagraph
meta_aug = list(zip(meta_nodes, meta_nodes[1:]))
# map that path to the original graph
inverse = defaultdict(list)
for k, v in C.graph["mapping"].items():
inverse[v].append(k)
for mu, mv in meta_aug:
yield (inverse[mu][0], inverse[mv][0])
@nx._dispatchable
def weighted_one_edge_augmentation(G, avail, weight=None, partial=False):
"""找到连接图G的最小权重边集(如果存在)。
这是加权最小生成树问题的一个变种。
Parameters
----------
G : NetworkX图
一个无向图。
avail : 字典或2或3元组集合
更多细节请参见 :func:`k_edge_augmentation` 。
weight : 字符串
如果 ``avail`` 是3元组集合,用于查找权重的键。
更多细节请参见 :func:`k_edge_augmentation` 。
partial : 布尔值
如果partial为True且不存在可行的k边增强,则增强边最小化连通分量的数量。
Yields
------
edge : 元组
选自avail的用于连接G的边子集中的边。
See Also
--------
:func:`one_edge_augmentation`
:func:`k_edge_augmentation`
Examples
--------
>>> G = nx.Graph([(1, 2), (2, 3), (4, 5)])
>>> G.add_nodes_from([6, 7, 8])
>>> # 不在avail中的边隐含权重为无穷大
>>> avail = [(1, 3), (1, 5), (4, 7), (4, 8), (6, 1), (8, 1), (8, 2)]
>>> sorted(weighted_one_edge_augmentation(G, avail))
[(1, 5), (4, 7), (6, 1), (8, 1)]
>>> # 通过给前一个解决方案中的边赋予大权重来找到另一个解决方案(注意必须使用一些旧边)
>>> avail = [(1, 3), (1, 5, 99), (4, 7, 9), (6, 1, 99), (8, 1, 99), (8, 2)]
>>> sorted(weighted_one_edge_augmentation(G, avail))
[(1, 5), (4, 7), (6, 1), (8, 2)]
"""
avail_uv, avail_w = _unpack_available_edges(avail, weight=weight, G=G)
# Collapse CCs in the original graph into nodes in a metagraph
# Then find an MST of the metagraph instead of the original graph
C = collapse(G, nx.connected_components(G))
mapping = C.graph["mapping"]
# Assign each available edge to an edge in the metagraph
candidate_mapping = _lightest_meta_edges(mapping, avail_uv, avail_w)
# nx.set_edge_attributes(C, name='weight', values=0)
C.add_edges_from(
(mu, mv, {"weight": w, "generator": uv})
for (mu, mv), uv, w in candidate_mapping
)
# Find MST of the meta graph
meta_mst = nx.minimum_spanning_tree(C)
if not partial and not nx.is_connected(meta_mst):
raise nx.NetworkXUnfeasible("Not possible to connect G with available edges")
# Yield the edge that generated the meta-edge
for mu, mv, d in meta_mst.edges(data=True):
if "generator" in d:
edge = d["generator"]
yield edge
@nx._dispatchable
def unconstrained_bridge_augmentation(G):
"""找到使用最少边的G的最优2边增广。
这是[1]中详细算法的实现。基本思想是构建桥连通分量的元图,连接树的叶节点以连接整个图,最后按深度优先搜索前序连接树的叶节点以桥接整个图。
Parameters
----------
G : NetworkX图
一个无向图。
Yields
------
edge : 元组
G的桥增广中的边
Notes
-----
输入:一个图G。
首先找到G的桥连通分量并将每个桥连通分量折叠成元图C的一个节点,C保证是一片树的森林。
C包含p个“叶节点”——恰好有一条入边的节点。
C包含q个“孤立节点”——没有入边的节点。
定理:如果p + q > 1,则至少需要 :math:`ceil(p / 2) + q` 条边来桥接C。该算法实现了这个最小数量。
该方法首先添加足够的边使G变成一棵树,然后简单地配对叶节点。
设n为C中树的数量。设v(i)为第i棵树中的一个孤立顶点(如果存在),否则为第i棵树中的一对不同叶节点。从这些集合中交替添加边(例如,添加边A1 = [(v(i)[0], v(i + 1)[1]), v(i + 1)[0], v(i + 2)[1])...])将C连接成一棵树T。这棵树有p' = p + 2q - 2(n -1)个叶节点且没有孤立顶点。A1有n - 1条边。下一步找到ceil(p' / 2)条边来双连通任何有p'个叶节点的树。
通过选择一个度数 >= 2 的任意根节点并将所有边指向远离根的方向,将T转换为树状图T'。注意实现隐式构造了T'。
T的叶节点是T'中没有现有边的节点。
按深度优先搜索前序对T'的叶节点进行排序。然后将这个列表分成两半并添加拉链对到A2。
集合A = A1 + A2是元图中的最小增广。
将其转换为原始图中的边
References
----------
.. [1] Eswaran, Kapali P., and R. Endre Tarjan. (1975) Augmentation problems.
http://epubs.siam.org/doi/abs/10.1137/0205044
See Also
--------
:func:`bridge_augmentation`
:func:`k_edge_augmentation`
Examples
--------
>>> G = nx.path_graph((1, 2, 3, 4, 5, 6, 7))
>>> sorted(unconstrained_bridge_augmentation(G))
[(1, 7)]
>>> G = nx.path_graph((1, 2, 3, 2, 4, 5, 6, 7))
>>> sorted(unconstrained_bridge_augmentation(G))
[(1, 3), (3, 7)]
>>> G = nx.Graph([(0, 1), (0, 2), (1, 2)])
>>> G.add_node(4)
>>> sorted(unconstrained_bridge_augmentation(G))
[(1, 4), (4, 0)]
"""
# -----
# Mapping of terms from (Eswaran and Tarjan):
# G = G_0 - the input graph
# C = G_0' - the bridge condensation of G. (This is a forest of trees)
# A1 = A_1 - the edges to connect the forest into a tree
# leaf = pendant - a node with degree of 1
# alpha(v) = maps the node v in G to its meta-node in C
# beta(x) = maps the meta-node x in C to any node in the bridge
# component of G corresponding to x.
# find the 2-edge-connected components of G
bridge_ccs = list(nx.connectivity.bridge_components(G))
# condense G into an forest C
C = collapse(G, bridge_ccs)
# Choose pairs of distinct leaf nodes in each tree. If this is not
# possible then make a pair using the single isolated node in the tree.
vset1 = [
tuple(cc) * 2 # case1: an isolated node
if len(cc) == 1
else sorted(cc, key=C.degree)[0:2] # case2: pair of leaf nodes
for cc in nx.connected_components(C)
]
if len(vset1) > 1:
# Use this set to construct edges that connect C into a tree.
nodes1 = [vs[0] for vs in vset1]
nodes2 = [vs[1] for vs in vset1]
A1 = list(zip(nodes1[1:], nodes2))
else:
A1 = []
# Connect each tree in the forest to construct an arborescence
T = C.copy()
T.add_edges_from(A1)
# If there are only two leaf nodes, we simply connect them.
leafs = [n for n, d in T.degree() if d == 1]
if len(leafs) == 1:
A2 = []
if len(leafs) == 2:
A2 = [tuple(leafs)]
else:
# Choose an arbitrary non-leaf root
try:
root = next(n for n, d in T.degree() if d > 1)
except StopIteration: # no nodes found with degree > 1
return
# order the leaves of C by (induced directed) preorder
v2 = [n for n in nx.dfs_preorder_nodes(T, root) if T.degree(n) == 1]
# connecting first half of the leafs in pre-order to the second
# half will bridge connect the tree with the fewest edges.
half = math.ceil(len(v2) / 2)
A2 = list(zip(v2[:half], v2[-half:]))
# collect the edges used to augment the original forest
aug_tree_edges = A1 + A2
# Construct the mapping (beta) from meta-nodes to regular nodes
inverse = defaultdict(list)
for k, v in C.graph["mapping"].items():
inverse[v].append(k)
# sort so we choose minimum degree nodes first
inverse = {
mu: sorted(mapped, key=lambda u: (G.degree(u), u))
for mu, mapped in inverse.items()
}
# For each meta-edge, map back to an arbitrary pair in the original graph
G2 = G.copy()
for mu, mv in aug_tree_edges:
# Find the first available edge that doesn't exist and return it
for u, v in it.product(inverse[mu], inverse[mv]):
if not G2.has_edge(u, v):
G2.add_edge(u, v)
yield u, v
break
@nx._dispatchable
def weighted_bridge_augmentation(G, avail, weight=None):
"""找到G的一个近似最小权重2边增广。
这是[1]中详细介绍的近似算法的实现。它从avail中选择一组边添加到G中,使其成为2边连通的(如果存在这样的子集)。这是通过找到一个特殊构造的元图的最小生成树来完成的。
Parameters
----------
G : NetworkX图
一个无向图。
avail : 2或3元组的集合
候选边(可选带权重)以供选择
weight : 字符串
如果avail是3元组的集合,其中每个元组的第三个项是字典,则使用此键来查找权重。
Yields
------
edge : 元组
avail中选择的子集中的边,用于桥接增广G。
Notes
-----
找到加权2边增广是NP难问题。
任何不在 ``avail`` 中的边都被认为具有无穷大的权重。
如果 ``G`` 是连通的,近似因子为2;如果不是,则为3。
运行时间为 :math:`O(m + n log(n))`
References
----------
.. [1] Khuller, Samir, and Ramakrishna Thurimella. (1993) Approximation
algorithms for graph augmentation.
http://www.sciencedirect.com/science/article/pii/S0196677483710102
See Also
--------
:func:`bridge_augmentation`
:func:`k_edge_augmentation`
Examples
--------
>>> G = nx.path_graph((1, 2, 3, 4))
>>> # 当权重相等时,(1, 4) 是最好的
>>> avail = [(1, 4, 1), (1, 3, 1), (2, 4, 1)]
>>> sorted(weighted_bridge_augmentation(G, avail))
[(1, 4)]
>>> # 给 (1, 4) 一个高权重使得两条边的解决方案成为最佳。
>>> avail = [(1, 4, 1000), (1, 3, 1), (2, 4, 1)]
>>> sorted(weighted_bridge_augmentation(G, avail))
[(1, 3), (2, 4)]
>>> # ------
>>> G = nx.path_graph((1, 2, 3, 4))
>>> G.add_node(5)
>>> avail = [(1, 5, 11), (2, 5, 10), (4, 3, 1), (4, 5, 1)]
>>> sorted(weighted_bridge_augmentation(G, avail=avail))
[(1, 5), (4, 5)]
>>> avail = [(1, 5, 11), (2, 5, 10), (4, 3, 1), (4, 5, 51)]
>>> sorted(weighted_bridge_augmentation(G, avail=avail))
[(1, 5), (2, 5), (4, 5)]
"""
if weight is None:
weight = "weight"
# If input G is not connected the approximation factor increases to 3
if not nx.is_connected(G):
H = G.copy()
connectors = list(one_edge_augmentation(H, avail=avail, weight=weight))
H.add_edges_from(connectors)
yield from connectors
else:
connectors = []
H = G
if len(avail) == 0:
if nx.has_bridges(H):
raise nx.NetworkXUnfeasible("no augmentation possible")
avail_uv, avail_w = _unpack_available_edges(avail, weight=weight, G=H)
# Collapse input into a metagraph. Meta nodes are bridge-ccs
bridge_ccs = nx.connectivity.bridge_components(H)
C = collapse(H, bridge_ccs)
# Use the meta graph to shrink avail to a small feasible subset
mapping = C.graph["mapping"]
# Choose the minimum weight feasible edge in each group
meta_to_wuv = {
(mu, mv): (w, uv)
for (mu, mv), uv, w in _lightest_meta_edges(mapping, avail_uv, avail_w)
}
# Mapping of terms from (Khuller and Thurimella):
# C : G_0 = (V, E^0)
# This is the metagraph where each node is a 2-edge-cc in G.
# The edges in C represent bridges in the original graph.
# (mu, mv) : E - E^0 # they group both avail and given edges in E
# T : \Gamma
# D : G^D = (V, E_D)
# The paper uses ancestor because children point to parents, which is
# contrary to networkx standards. So, we actually need to run
# nx.least_common_ancestor on the reversed Tree.
# Pick an arbitrary leaf from C as the root
try:
root = next(n for n, d in C.degree() if d == 1)
except StopIteration: # no nodes found with degree == 1
return
# Root C into a tree TR by directing all edges away from the root
# Note in their paper T directs edges towards the root
TR = nx.dfs_tree(C, root)
# Add to D the directed edges of T and set their weight to zero
# This indicates that it costs nothing to use edges that were given.
D = nx.reverse(TR).copy()
nx.set_edge_attributes(D, name="weight", values=0)
# The LCA of mu and mv in T is the shared ancestor of mu and mv that is
# located farthest from the root.
lca_gen = nx.tree_all_pairs_lowest_common_ancestor(
TR, root=root, pairs=meta_to_wuv.keys()
)
for (mu, mv), lca in lca_gen:
w, uv = meta_to_wuv[(mu, mv)]
if lca == mu:
# If u is an ancestor of v in TR, then add edge u->v to D
D.add_edge(lca, mv, weight=w, generator=uv)
elif lca == mv:
# If v is an ancestor of u in TR, then add edge v->u to D
D.add_edge(lca, mu, weight=w, generator=uv)
else:
# If neither u nor v is a ancestor of the other in TR
# let t = lca(TR, u, v) and add edges t->u and t->v
# Track the original edge that GENERATED these edges.
D.add_edge(lca, mu, weight=w, generator=uv)
D.add_edge(lca, mv, weight=w, generator=uv)
# Then compute a minimum rooted branching
try:
# Note the original edges must be directed towards to root for the
# branching to give us a bridge-augmentation.
A = _minimum_rooted_branching(D, root)
except nx.NetworkXException as err:
# If there is no branching then augmentation is not possible
raise nx.NetworkXUnfeasible("no 2-edge-augmentation possible") from err
# For each edge e, in the branching that did not belong to the directed
# tree T, add the corresponding edge that **GENERATED** it (this is not
# necessarily e itself!)
# ensure the third case does not generate edges twice
bridge_connectors = set()
for mu, mv in A.edges():
data = D.get_edge_data(mu, mv)
if "generator" in data:
# Add the avail edge that generated the branching edge.
edge = data["generator"]
bridge_connectors.add(edge)
yield from bridge_connectors
def _minimum_rooted_branching(D, root):
"""辅助函数,用于计算最小根分支(又称根树形图)
在计算分支之前,必须通过移除根的前驱节点使有向图成为根图。
根图G的分支/树形图是一个子图,包含从根到每个其他顶点的有向路径。它是最小生成树问题的有向图对应问题。
References
----------
[1] Khuller, Samir (2002) 高级算法讲义24笔记。
https://web.archive.org/web/20121030033722/https://www.cs.umd.edu/class/spring2011/cmsc651/lec07.pdf
"""
rooted = D.copy()
# root the graph by removing all predecessors to `root`.
rooted.remove_edges_from([(u, root) for u in D.predecessors(root)])
# Then compute the branching / arborescence.
A = nx.minimum_spanning_arborescence(rooted)
return A
@nx._dispatchable(returns_graph=True)
def collapse(G, grouped_nodes):
"""将每组节点折叠成一个单一节点。
这类似于凝聚,但适用于无向图。
Parameters
----------
G : NetworkX 图
grouped_nodes: 列表或生成器
要折叠的节点分组。分组必须是互斥的。
如果 grouped_nodes 是强连通分量,则这等效于 :func:`condensation` 。
Returns
-------
C : NetworkX 图
根据节点分组折叠的图 C。节点标签是分组列表中组件索引的整数。
C 具有名为 'mapping' 的图属性,该属性是一个字典,将原始节点映射到它们所属的 C 中的节点。
C 中的每个节点还具有一个节点属性 'members',其中包含形成该节点所代表的组的原始节点集合。
Examples
--------
>>> # 使用互斥组折叠图,但不一定是连通的
>>> G = nx.Graph([(1, 0), (2, 3), (3, 1), (3, 4), (4, 5), (5, 6), (5, 7)])
>>> G.add_node("A")
>>> grouped_nodes = [{0, 1, 2, 3}, {5, 6, 7}]
>>> C = collapse(G, grouped_nodes)
>>> members = nx.get_node_attributes(C, "members")
>>> sorted(members.keys())
[0, 1, 2, 3]
>>> member_values = set(map(frozenset, members.values()))
>>> assert {0, 1, 2, 3} in member_values
>>> assert {4} in member_values
>>> assert {5, 6, 7} in member_values
>>> assert {"A"} in member_values
"""
mapping = {}
members = {}
C = G.__class__()
i = 0 # required if G is empty
remaining = set(G.nodes())
for i, group in enumerate(grouped_nodes):
group = set(group)
assert remaining.issuperset(
group
), "grouped nodes must exist in G and be disjoint"
remaining.difference_update(group)
members[i] = group
mapping.update((n, i) for n in group)
# remaining nodes are in their own group
for i, node in enumerate(remaining, start=i + 1):
group = {node}
members[i] = group
mapping.update((n, i) for n in group)
number_of_groups = i + 1
C.add_nodes_from(range(number_of_groups))
C.add_edges_from(
(mapping[u], mapping[v]) for u, v in G.edges() if mapping[u] != mapping[v]
)
# Add a list of members (ie original nodes) to each node (ie scc) in C.
nx.set_node_attributes(C, name="members", values=members)
# Add mapping dict as graph attribute
C.graph["mapping"] = mapping
return C
@nx._dispatchable
def complement_edges(G):
"""返回仅在图G的补集中的边
Parameters
----------
G : NetworkX图
Yields
------
edge : 元组
在G的补集中的边
Examples
--------
>>> G = nx.path_graph((1, 2, 3, 4))
>>> sorted(complement_edges(G))
[(1, 3), (1, 4), (2, 4)]
>>> G = nx.path_graph((1, 2, 3, 4), nx.DiGraph())
>>> sorted(complement_edges(G))
[(1, 3), (1, 4), (2, 1), (2, 4), (3, 1), (3, 2), (4, 1), (4, 2), (4, 3)]
>>> G = nx.complete_graph(1000)
>>> sorted(complement_edges(G))
[]
"""
G_adj = G._adj # Store as a variable to eliminate attribute lookup
if G.is_directed():
for u, v in it.combinations(G.nodes(), 2):
if v not in G_adj[u]:
yield (u, v)
if u not in G_adj[v]:
yield (v, u)
else:
for u, v in it.combinations(G.nodes(), 2):
if v not in G_adj[u]:
yield (u, v)
def _compat_shuffle(rng, input):
"""用于Python 2兼容性的rng.shuffle包装器"""
rng.shuffle(input)
@not_implemented_for("multigraph")
@not_implemented_for("directed")
@py_random_state(4)
@nx._dispatchable
def greedy_k_edge_augmentation(G, k, avail=None, weight=None, seed=None):
"""贪婪算法用于寻找 k-边增广
Parameters
----------
G : NetworkX 图
一个无向图。
k : 整数
期望的边连通性
avail : 字典或一组 2 或 3 元组
更多详情,参见 :func:`k_edge_augmentation` 。
weight : 字符串
如果 ``avail`` 是一组 3 元组,用于查找权重的键。
更多详情,参见 :func:`k_edge_augmentation` 。
seed : 整数,random_state,或 None(默认)
随机数生成状态的指示器。
参见 :ref:`Randomness<randomness>` 。
Yields
------
edge : 元组
G 的贪婪增广中的边
Notes
-----
该算法很简单。边逐渐添加在图中尚未局部 k-边连通的部分之间。然后从增广集中修剪边,只要不破坏局部边连通性。
该算法是贪婪的,不提供最优性保证。它仅用于为 :func:`k_edge_augmentation` 提供生成任意 k 的可行解的能力。
See Also
--------
:func:`k_edge_augmentation`
Examples
--------
>>> G = nx.path_graph((1, 2, 3, 4, 5, 6, 7))
>>> sorted(greedy_k_edge_augmentation(G, k=2))
[(1, 7)]
>>> sorted(greedy_k_edge_augmentation(G, k=1, avail=[]))
[]
>>> G = nx.path_graph((1, 2, 3, 4, 5, 6, 7))
>>> avail = {(u, v): 1 for (u, v) in complement_edges(G)}
>>> # 随机修剪过程可能产生不同的解决方案
>>> sorted(greedy_k_edge_augmentation(G, k=4, avail=avail, seed=2))
[(1, 3), (1, 4), (1, 5), (1, 6), (1, 7), (2, 4), (2, 6), (3, 7), (5, 7)]
>>> sorted(greedy_k_edge_augmentation(G, k=4, avail=avail, seed=3))
[(1, 3), (1, 5), (1, 6), (2, 4), (2, 6), (3, 7), (4, 7), (5, 7)]
"""
# Result set
aug_edges = []
done = is_k_edge_connected(G, k)
if done:
return
if avail is None:
# all edges are available
avail_uv = list(complement_edges(G))
avail_w = [1] * len(avail_uv)
else:
# Get the unique set of unweighted edges
avail_uv, avail_w = _unpack_available_edges(avail, weight=weight, G=G)
# Greedy: order lightest edges. Use degree sum to tie-break
tiebreaker = [sum(map(G.degree, uv)) for uv in avail_uv]
avail_wduv = sorted(zip(avail_w, tiebreaker, avail_uv))
avail_uv = [uv for w, d, uv in avail_wduv]
# Incrementally add edges in until we are k-connected
H = G.copy()
for u, v in avail_uv:
done = False
if not is_locally_k_edge_connected(H, u, v, k=k):
# Only add edges in parts that are not yet locally k-edge-connected
aug_edges.append((u, v))
H.add_edge(u, v)
# Did adding this edge help?
if H.degree(u) >= k and H.degree(v) >= k:
done = is_k_edge_connected(H, k)
if done:
break
# Check for feasibility
if not done:
raise nx.NetworkXUnfeasible("not able to k-edge-connect with available edges")
# Randomized attempt to reduce the size of the solution
_compat_shuffle(seed, aug_edges)
for u, v in list(aug_edges):
# Don't remove if we know it would break connectivity
if H.degree(u) <= k or H.degree(v) <= k:
continue
H.remove_edge(u, v)
aug_edges.remove((u, v))
if not is_k_edge_connected(H, k=k):
# If removing this edge breaks feasibility, undo
H.add_edge(u, v)
aug_edges.append((u, v))
# Generate results
yield from aug_edges