[英]Python Combinatorics, part 2
這是Python中Combinatorics的后續問題
如果您將使用以下結構,我有一棵樹或有向無環圖:
其中r是根節點,p是父節點,c是子節點,b是假設分支。 根節點不直接鏈接到父節點,它只是一個引用。
我在尋找約束條件下的所有分支組合時感到非常緊張:
在此示例中,約束下只能有兩種有效組合:
combo[0] = [b[0], b[1], b[2], b[3]]
combo[1] = [b[0], b[1], b[2], b[4]]
數據結構如b是分支對象列表,它具有屬性r,c和p,例如:
b[3].r = 1
b[3].p = 3
b[3].c = 2
這個問題可以通過Python輕松優雅地解決,因為有一個名為“itertools”的模塊。
假設我們有HypotheticalBranch類型的對象,它們具有屬性r,p和c。 正如您在帖子中描述的那樣:
class HypotheticalBranch(object):
def __init__(self, r, p, c):
self.r=r
self.p=p
self.c=c
def __repr__(self):
return "HypotheticalBranch(%d,%d,%d)" % (self.r,self.p,self.c)
因此,你的假設分支
b=[ HypotheticalBranch(0,0,0),
HypotheticalBranch(0,1,1),
HypotheticalBranch(1,2,1),
HypotheticalBranch(1,3,2),
HypotheticalBranch(1,4,2) ]
返回所有可能的分支組合列表的神奇函數可以這樣寫:
import collections, itertools
def get_combos(branches):
rc=collections.defaultdict(list)
for b in branches:
rc[b.r,b.c].append(b)
return itertools.product(*rc.values())
確切地說,此函數返回一個迭代器。 通過迭代獲取列表。 這四行代碼將打印出所有可能的組合:
for combo in get_combos(b):
print "Combo:"
for branch in combo:
print " %r" % (branch,)
該程序的輸出是:
Combo:
HypotheticalBranch(0,1,1)
HypotheticalBranch(1,3,2)
HypotheticalBranch(0,0,0)
HypotheticalBranch(1,2,1)
Combo:
HypotheticalBranch(0,1,1)
HypotheticalBranch(1,4,2)
HypotheticalBranch(0,0,0)
HypotheticalBranch(1,2,1)
......這正是你想要的。
那腳本做了什么? 它為每個組合(根節點,子節點)創建所有假設分支的列表。 然后它產生這些列表的產品,即每個列表中一個項目的所有可能組合。
我希望我得到你真正想要的東西。
第二個約束意味着您需要最大組合,即所有長度等於最大組合的組合。
我將通過首先遍歷“b”結構並創建一個名為“c”的結構來存儲所有分支,以存儲到每個子節點的所有分支並由到達它的根節點進行分類。
然后構造輸出組合,對於每個子項,您可以包含每個非空的根集中的一個條目。 算法的順序(執行時間)將是輸出的順序,這是您可以獲得的最佳順序。
例如,您的“c”結構將如下所示:
c[i][j] = [b_k0, ...]
--> means c_i has b_k0, ... as branches that connect to root r_j)
對於您提供的示例:
c[0][0] = [0]
c[0][1] = []
c[1][0] = [1]
c[1][1] = [2]
c[2][0] = []
c[2][1] = [3, 4]
使用這種方法對它進行編碼應該相當容易。 您只需要遍歷所有分支“b”並填充“c”的數據結構。 然后寫一個小的遞歸函數,遍歷“c”中的所有項目。
這是代碼(為了測試,我在頂部輸入了您的示例數據):
class Branch:
def __init__(self, r, p, c):
self.r = r
self.p = p
self.c = c
b = [
Branch(0, 0, 0),
Branch(0, 1, 1),
Branch(1, 2, 1),
Branch(1, 3, 2),
Branch(1, 4, 2)
]
total_b = 5 # Number of branches
total_c = 3 # Number of child nodes
total_r = 2 # Number of roots
c = []
for i in range(total_c):
c.append([])
for j in range(total_r):
c[i].append([])
for k in range(total_b):
c[b[k].c][b[k].r].append(k)
combos = []
def list_combos(n_c, n_r, curr):
if n_c == total_c:
combos.append(curr)
elif n_r == total_r:
list_combos(n_c+1, 0, curr)
elif c[n_c][n_r]:
for k in c[n_c][n_r]:
list_combos(n_c, n_r+1, curr + [b[k]])
else:
list_combos(n_c, n_r+1, curr)
list_combos(0, 0, [])
print combos
這里確實存在兩個問題:首先,您需要制定用於解決此問題的算法,其次,您需要實現它(在Python中)。
我假設你想要一個最大的分支集合; 也就是說,一旦你不能再添加任何分支。 如果不這樣做,則可以考慮最大集合的所有子集。
因此,對於子節點,我們希望盡可能多地使用分支,但要遵守沒有兩個父節點共享根的約束。 換句話說,來自每個孩子的每個根節點附近最多可能有一個邊緣。 這似乎表明你想首先在子節點上迭代,然后在根節點的(鄰域)上迭代,最后在這些之間的邊緣上迭代。 這個概念給出了以下偽代碼:
for each child node:
for each root node:
remember each permissible edge
find all combinations of permissible edges
>>> import networkx as nx
>>> import itertools
>>>
>>> G = nx.DiGraph()
>>> G.add_nodes_from(["r0", "r1", "p0", "p1", "p2", "p3", "p4", "c0", "c1", "c2"])
>>> G.add_edges_from([("r0", "p0"), ("r0", "p1"), ("r1", "p2"), ("r1", "p3"),
... ("r1", "p4"), ("p0", "c0"), ("p1", "c1"), ("p2", "c1"),
... ("p3", "c2"), ("p4", "c2")])
>>>
>>> combs = set()
>>> leaves = [node for node in G if not G.out_degree(node)]
>>> roots = [node for node in G if not G.in_degree(node)]
>>> for leaf in leaves:
... for root in roots:
... possibilities = tuple(edge for edge in G.in_edges_iter(leaf)
... if G.has_edge(root, edge[0]))
... if possibilities: combs.add(possibilities)
...
>>> combs
set([(('p1', 'c1'),),
(('p2', 'c1'),),
(('p3', 'c2'), ('p4', 'c2')),
(('p0', 'c0'),)])
>>> print list(itertools.product(*combs))
[(('p1', 'c1'), ('p2', 'c1'), ('p3', 'c2'), ('p0', 'c0')),
(('p1', 'c1'), ('p2', 'c1'), ('p4', 'c2'), ('p0', 'c0'))]
上面似乎有用,雖然我還沒有測試過。
對於每個孩子c,假設父母p(c),根r(p(c)),從p(c)中為r(p(c))中的每個根r選擇恰好一個父p(這樣r是p)的根,並在組合中包括b,其中b將p連接到c(假設只有一個這樣的b,意味着它不是多圖)。 組合的數量將是每個孩子假設連接到每個根的父母數量的乘積。 換句話說,該組合的大小將等於所有子根對的假設連接的乘積。 在您的示例中,所有此類子根對只有一個路徑,r1-c2除外,它有兩個路徑,因此組合集的大小為2。
這滿足了無組合作為另一個子集的約束,因為通過為每個子節點的每個根精確選擇一個父節點,我們最大化連接數。 隨后添加任何邊緣b將導致其根連接到其子節點兩次,這是不允許的。 由於我們只選擇一個,所有組合的長度都完全相同。
遞歸地實現該選擇將產生期望的組合。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.