重复的选择算法 selection(计数法)
源代码:
http://blog.youkuaiyun.com/cxjddd/archive/2004/07/29/55752.aspx
对不起,我无法使用一个准确的标题,下面描述问题:有一定数量元素(假
定无重复),每次都从中复制出一个元素;列出复制若干次时所有可能的结果。
比如,从 {1, 2, 3} 里复制 2 次,可以有 (1, 1)、(1, 2)、(1, 3)、(2, 2)、
(2, 3)、(3, 3),共六种。
每组结果都可以用各元素的个数表示出来,如前面的 (1, 1),其中 1 的个
数有 2 个,其它两个元素没有,可以表示成 [2, 0, 0];而 (2, 3) 可以表示
成 [0, 1, 1];同理,[0, 0, 2] 可以表示 (3, 3)。这种用各元素的个数来表
示的方法,可以称为“计数法”。
如果结果按照字典序排列,则前例的计数法的排列是:
[2, 0, 0]、
[1, 1, 0]、
[1, 0, 1]、
[0, 2, 0]、
[0, 1, 1]、
[0, 0, 2]。
可以看出,计数法的起始状态很简单,就是 [n, 0, ..., 0],而结束状态
也很简单,就是 [0, 0, ..., 0, n]。只要可以把前者变成后者,就相当于完成
了所有的排列。
显然,[n, 0, 0, ..., 0] 的下一个结果会是 [n-1, 1, 0, ..., 0];如果
我们把后面的 [1, 0, ..., 0] 看成是“新”的计数的话,显然可以变化成 [0,
1, ..., 0],进而可以一直变化到 [0, 0, ..., 1];则现在可以将 [n, 0, 0,
..., 0] 变化成 [n-1, 0, 0, ..., 1]。
[n-1, 0, 0, ..., 1] 的下一个会是什么呢?可以肯定,它是 [n-2, 2, 0,
..., 0]。因为 (1, 3) 的下一个结果是 (2, 2),这是最小的变化了。这就相当
于向“高位”借一。类似的,我们可以用“递归”的办法让 [2, 0, ..., 0] 变
化到 [0, 0, ..., 2]。于是现在变到了 [n-2, 0, 0, ..., 2]。
([2, 0, ..., 0] 可以变成 [1, 1, ..., 0],然后是 [1, 0, ..., 1],
再变成 [0, 2, ..., 0]。以此递归。最后会有 [2, 0] 这样的情况,自然可以
变成 [1, 1],然后“借一”变成 [0, 2])
类似的,[n-2, 0, 0, ..., 2] 可以变成 [n-3, 3, 0, ..., 0];然后变成
[n-3, 0, 0, ..., 3]。由此下去,可以变化到 [1, 0, 0, ..., n-1]。这时再
借一,就会变成 [0, n, 0, ..., 0],至此第一个元素就完成了所有的结果。当
然,一个新的 [n, 0, ..., 0] 出现了,同样可以完成了。
特殊的,会出现 [n, 0] 的情况,这种就是从 [n-1, 1]、[n-2, 2] 等一直
到 [1, n-1]、[0, n] 完成。
总结以上情况:
[n, 0, 0, ..., 0, 0]
[n-1, 1, 0, ..., 0, 0]
[n-1, 0, 1, ..., 0, 0]
...
[n-1, 0, 0, ..., 0, 1]
[n-2, 2, 0, ..., 0, 0]
[n-2, 1, 1, ..., 0, 0]
...
[n-2, 1, 0, ..., 0, 1]
[n-2, 0, 2, ..., 0, 0]
...
[n-2, 0, 0, ..., 2, 0]
[n-2, 0, 0, ..., 1, 1]
[n-2, 0, 0, ..., 0, 2]
[n-3, 3, 0, ..., 0, 0]
...
[1, n-1, 0, ..., 0, 0]
...
[1, 0, 0, ..., 0, n-1]
[0, n, 0, ..., 0, 0]
...
[0, 0, 0, ..., n, 0]
[0, 0, 0, ..., n-1, 1]
[0, 0, 0, ..., n-2, 2]
...
[0, 0, 0, ..., 1, n-1]
[0, 0, 0, ..., 0, n]
对上面所说的情况,可以用两种情况总结:从最右边开始,
一、对于 [n, 0, ..., 0],变成 [n-1, 1, ..., 0];
二、对于 [n, 0, ..., m],变成 [n-1, m+1, ..., 0]。
(注:[n, m](m 和 n 都大于 0)依“情况二”变)
我在写程序的过程中,可对上面的情况加以变形,分成 [n, 0, ..., 0, m]
和 [n, m] 两种,其中 n > 0,m >= 0。前种变成 [n-1, m+1, ..., 0, 0];而
后者变成 [n-1, m+1]。
程序如下:
template
bool
next_selection (BiIter first, BiIter last)
{
if (first == last)
return false;
BiIter i = last;
if (--i == first)
return false;
BiIter j = i;
if (*--i == 0)
{
while ((i != first) && (*i == 0))
--i;
if (*i == 0)
{
iter_swap (first, j);
return false;
}
BiIter ii = i;
*(++ii) = *j + 1;
*j = 0;
--*i;
}
else
{
--*i;
++*j;
}
return true;
}
前面是 next_selection,字典序;而 prev_selection(相当于逆字典序)
也可以较简单地实现。
观察可知:对 [m, n](其中 n > 0),变成 [m+1, n-1];对 [m, n, 0,
..., 0](其中 n > 0),变成 [m+1, 0, 0, ..., n-1]。程序如下:
template
bool
prev_selection (BiIter first, BiIter last)
{
if (first == last)
return false;
BiIter i = last;
--i;
if (first == i)
return false;
if (*i == 0)
{
while (*--i == 0)
;
if (i == first)
{
*--last = *i;
*i = 0;
return false;
}
BiIter j = i;
--j;
++*j;
--*i;
iter_swap (i, --last);
}
else
{
BiIter j = i;
--j;
++*j;
--*i;
}
return true;
}