与えられた集合の部分集合は非常に多い. 例えば,要素数が$n$の集合の部分集合は全部で$2^n$個になる.
しかし,$n$があまり多くない場合には,部分集合の列挙が役に立つ場合も多い. 以下に,部分集合を列挙するPythonコードを関数enumerate_all_subsetsとして定義する.
def subset_from_vector(v, a_set):
'''特性列と集合を元に,部分集合を返す関数.
'''
if len(v) != len(a_set):
return []
subset = []
for vi, element in zip(v, a_set):
if vi == 1:
subset += [element]
return subset
def enumerate_all_subsets(a_set=['a', 'b', 'c']):
'''部分集合を列挙する関数.
深さ優先探索を元に部分集合の特性列(characteristic sequence)を列挙する.
列挙された部分集合をまとめて最後に返す.
'''
subsets = [] # 部分集合を保存するためのリスト
S = [[]] # スタック
while len(S) > 0: # スタックが空でない限り以下を繰り返す.
v = S.pop() # スタックから0-1列を1つ取り出す.
if len(v) == len(a_set): # 0-1列が集合の要素数と同じ長さならば,
subsets += [subset_from_vector(v, a_set)] # その0-1ベクトルが特性列であると見なして,部分集合を作り,保存する.
else: # 0-1列が集合の要素数よりも短いならば,
S += [v + [0]] # それに0を追加した列をスタックに入れる.
S += [v + [1]] # それに1を追加した列もスタックに入れる.
return subsets # 最後にまとめて,全ての部分集合を返す.
3つの要素からなる集合をenumerate_all_subsetsに与えてみる.
enumerate_all_subsets(['a', 'b', 'c'])
[['a', 'b', 'c'], ['a', 'b'], ['a', 'c'], ['a'], ['b', 'c'], ['b'], ['c'], []]
確かに列挙できているようだ.
この例では,列挙したものを最後にまとめて返している. しかし,多くの場合では,それぞれの部分集合が出来た時点で何らかの処理を施す方が良い.
また,部分集合をすべて必要とはせず,「何らかの条件を満たす」部分集合だけを欲しい場合も多い. そのような場合には上記のコードを適切にカスタマイズする能力が要求される.
$n$個の要素を並べるやり方は$n!$通りある.
しかし,$n$があまり多くない場合には,順列の列挙が役に立つ場合も多い. 以下に,順列を列挙するPythonコードを関数enumerate_all_permutationsとして定義する.
def enumerate_all_permutations(sequence=['a', 'b', 'c']):
'''順列を列挙する関数.
深さ優先探索探索を元に順列を列挙する.
'''
permutations = [] # 順列を保存するためのリスト
S = [([], sequence)] # 「順列の部分列」と「部分列で使われていない残り」の組をスタックに保存する.
while len(S) > 0: # スタックが空でない限り以下を繰り返す.
subsequence, remaining = S.pop() # スタックから(「部分列」,「残り」)を1つ取り出す.
if len(remaining) == 0: # 「部分列で使われていない残り」が空ならば,部分列は順列になっているはずなので,
permutations += [subsequence] # その部分列を順列に加える.
else: # 「部分列で使われていない残り」があるならば
for r in remaining: # その残りのそれぞれに関して,
remaining_copy = remaining[:]
remaining_copy.remove(r) # 残りを1つ取り除いて,
S += [(subsequence + [r], remaining_copy)] # それを部分列に追加した列と残りをスタックに入れる.
return permutations # 最後にまとめて,全ての部分集合を返す.
enumerate_all_permutations(['a', 'b', 'c'])
[['c', 'b', 'a'], ['c', 'a', 'b'], ['b', 'c', 'a'], ['b', 'a', 'c'], ['a', 'c', 'b'], ['a', 'b', 'c']]
確かに列挙できているようだ.
この例では,列挙したものを最後にまとめて返している. しかし,多くの場合では,それぞれの順列が出来た時点で何らかの処理を施す方が良い.
実はPythonの標準モジュールであるitertoolsには順列を列挙してくれる関数が用意されている. しかし,一般に順列の個数は非常に多い. よって「順列のうち特定の条件を満たすものだけを列挙したい,しかも条件を満たすものは多くない」という場合には,順列をすべて列挙しておいて条件に合わないものを省くという方策は使えない. そのような場合には上記の方法で列挙しつつ,条件に合わないものはスタックに入れないという方法が有効である.