簡體   English   中英

Python Combinatorics,第2部分

[英]Python Combinatorics, part 2

這是Python中Combinatorics的后續問題

如果您將使用以下結構,我有一棵樹或有向無環圖:

替代文字

其中r是根節點,p是父節點,c是子節點,b是假設分支。 根節點不直接鏈接到父節點,它只是一個引用。

我在尋找約束條件下的所有分支組合時感到非常緊張:

  1. 如果這些父節點不共享根節點,則可以由任意數量的父節點共享子節點。
  2. 有效組合不應是另一種組合的子集

在此示例中,約束下只能有兩種有效組合:

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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM