• 下一个组合,位运算,bits,combination,next_combination


    炮姐v587: 

    http://misakamm.com/blog/25


    有一个排列,我们可以轻易得到下一个排列:

    http://www.cnblogs.com/threef/p/3200507.html

    怎么枚举下一个组合呢:

    科普:


    x - 1 : 100 后缀反转

    m100 -> m011

    x&(x - 1)消除最后一个有效位

    x^(x - 1):100后缀掩码


    x + 1:  011 后缀反转

    m011 -> m100

    x&(x + 1)消除后缀连续连续有效位

    x^(x + 1):011后缀掩码


    -x: 求反加1

    x& -x: 得到最低有次位掩码(有木有想到树状数组)

    x + (x& -x): m0111000 -> m1000000

    x^(x + (x & -x)): m0111000 -> 01111000



    通常,我们使用位运算来枚举组合的时候,对那个int自加,然后判断它的1的个数(判断1的个数用(n&(n-1))循环得到),而当那个int的1的个数,等于选取的个数的时候,我们就得到下一个组合了。

    不过这样做,显然太笨,因为如果是30选1,那你的程序要循环多少次呢。。。2^30已经能感觉到明显的延迟了。。。

    好了,现在我们来改进这个算法,我们最好是以O(1)的时间直接得到下一个组合,而不要一个个枚举尝试。
    这个想法很不错,不过代码要怎么写呢?

    见以下代码,运行时,输入"5 3"(不包含引号),再回车,看看结果:

    #include <stdio.h>
    int next_combination(int n, int k) //根据前一组合枚举下一组合
    {
        int ret, b = k & -k, t = (k + b);
        ret = (((t ^ k) >> 2) / b) | t;
        if ((1 << n) < ret) return 0;
        return ret;
    }
    
    int main()
    {
        int n, k;
        while (scanf("%d%d", &n, &k) != EOF && n >= k && k>0 && n<30)
        {
            int ik = (1 << k) - 1, i; //初始化
            do
            {
                // 输出组合
                for (i = 0; i < n; ++i)
                {
                    if (ik & (1 << i))
                        printf("%d ", i);
                }
                //输出空行分隔之
                puts("");
            }
            while (ik = next_combination(n, ik));
        }
        return 0;
    }

    输出结果是:
    5 3    5  4  3  2  1  0
    0 1 2          0   0  1  1  1   init
    0 1 3      0   1      0  1  1 
    0 2 3      0     1  1  0  1
    1 2 3      0   1   1  1  0      
    0 1 4      1   0   0  1  1
    0 2 4      1   0   1  0  1
    1 2 4       1  0   1  1  0
    0 3 4       1   1  0  0  1
    1 3 4       1   1  0  1  0
    2 3 4      1  1  1  0  0

            1  0  0  1  1  1  > (1 >> 5) 溢出

    嗯,很完美的得到了所有的组合

    在这里,main函数没什么好说的,很容易看明白,关键是那个next_combination函数,这个函数是什么意思?

    其实那个next_combination函数,是根据k,得到比k大,并且二进制下有相同个数的'1',而且是当中最小的数
    比如把1011,变成1101,这个到底是怎么算的呢?

    如果你笔算一下,不难发现规律,并且是不太复杂的,首先,因为1的个数要相同,并且比原数要大,那么,先要找出右起第一次出现1的位置,对这个数加上1,然后在最右边补上少了的1就可以了。
    找出右起第一次出现1的位置的算法很简单,就是(n & -n),这里的b就是得到这个位置,t就是加了以后的结果,这个应该不难明白,关键的,是后面的计算。

    后面的计算,主要是针对右边补1的个数,细心想一下,你就知道,要补的1的个数,等于原数右起第一个1向左数,连续的1的个数减1,然后,t^k是什么意思呢?这个就非常有技巧了,它的效果其实和x ^ (x - 1)很类似。

    而x ^ (x - 1)的作用,是保留右起第一个“1”,同时把右起第1个“1”右边全部变为“1”,类似1101000 -> 1111
    逆向过来说,就是k = 1100111,对它 +1后,得到t = 1101000,用这个运算可以得到1111,位数和少掉的1成常数差的关系
    事实上,这样我们变相地得到少掉的1的个数(这个例子中是少了两个1),我们只需要对运算结果中1的个数减2即可,用>>2解决之

    不过,在当k最右边不是1,有若干个0的时候,前一个步骤得到的数的最右边,就会有同样多的0,如何去掉这些0?
    这时候,我们最初计算的b,即(n & -n)就太有作用了,只要除以这个b,0就没有了,最后,就是最右边应该补上的值,和前面的t求和,或者求并均可。


     这一篇也是这个玩意:讲的挺好的!

    http://blog.csdn.net/w57w57w57/article/details/6657547

  • 相关阅读:
    JavaSE基础day23 死锁、线程生命周期、枚举
    JavaSE基础day21 Thread类
    JavaSE基础day20 多线程3种实现方式
    JavaSE基础day22 线程安全问题、锁
    JavaSE基础day24 枚举常用方法、反射
    使用Blazor开发WinForm程序
    Blazor使用PDFObject预览pdf文件
    kubernetes的原理和使用
    docker的产生及应用
    JVM:方法区、堆
  • 原文地址:https://www.cnblogs.com/threef/p/3202386.html
Copyright © 2020-2023  润新知