由n个元素构成的集合有2^n个子集,例如,集合{1,2,3}的子集有:
空集:1个
{}
一元子集:3个
{1},{2},{3}
二元子集:3个
{1,2},{1,3},{2,3}
三元子集:1个
{1,2,3}
共8个子集。
下面我们考虑一个集合S的所有子集的枚举方法。
首先,空集仅有一个子集,即空集本身。
其次,当S非空集时,我们可以从集合S中任取一个元素X,将子集的枚举划分成两个子问题:
(1) 子集中含X的,我们可以先枚举S-X的所有子集,然后将X放入生成的结果中,从而构成S的一个含X的子集;
(2) 子集不含X的,我们可以枚举S-X的所有子集,将它们直接作为S的子集。
根据上述思想设计的Prolog程序如下:
% File: subset.pro
domains
ilist=integer*
predicates
nondeterm subset(ilist, ilist)
clauses
subset([], []).
subset([X|Set], [X|SubSet]):-subset(Set, SubSet).
subset([_|Set], SubSet):-subset(Set, SubSet).
goal
subset([1,2,3], Subset),
write(Subset), nl,
fail.
上述目标生成的结果如下:
[1,2,3]
[1,2]
[1,3]
[1]
[2,3]
[2]
[3]
[]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
下面我们考虑集合的所有K元子集的生成方法。与生成所有子集不同的是,当集合为空集时,只能生成1个0元子集,即空子集;在集合非空的情况下,若子集中含X的,我们可以先枚举S-X的所有(k-1)元子集,然后将X放入生成的结果中,从而构成S的一个含X的k元子集;或者直接枚举S-X的所有k元子集,将它们直接作为S的k元子集。
因此,与生成子集的谓词不同的是,我们需要引入一个三元谓词
k_subset(K, Set, SubSet)
其中K表示子集元素个数,Set为原集合,而Subset为生成的K元子集。相应的Prolog程序如下:
% File: k-subset.pro
domains
ilist=integer*
predicates
nondeterm k_subset(integer, ilist, ilist)
clauses
k_subset(0, [], []).
k_subset(K, [X|Set], [X|SubSet]):-
K > 0,
K1 = K - 1,
k_subset(K1, Set, SubSet).
k_subset(K, [_|Set], SubSet):-
k_subset(K, Set, SubSet).
goal
k_subset(3, [1,2,3,4,5], Subset),
write(Subset), nl,
fail.
上述目标生成5个元素的全部3元子集:
[1,2,3]
[1,2,4]
[1,2,5]
[1,3,4]
[1,3,5]
[1,4,5]
[2,3,4]
[2,3,5]
[2,4,5]
[3,4,5]
实际上,生成k元子集的方法就是生成n个元素的k个元素的组合方法。