重复的选择算法 selection(计数法)

重复的选择算法 selection(计数法)

源代码:
http://blog.csdn.net/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;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值