zoukankan      html  css  js  c++  java
  • 下一个组合,位运算,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

  • 相关阅读:
    布局重用 include merge ViewStub
    AS 常用插件 MD
    AS 2.0新功能 Instant Run
    AS .ignore插件 忽略文件
    AS Gradle构建工具与Android plugin插件【大全】
    如何开通www国际域名个人网站
    倒计时实现方案总结 Timer Handler
    AS 进行单元测试
    RxJava 设计理念 观察者模式 Observable lambdas MD
    retrofit okhttp RxJava bk Gson Lambda 综合示例【配置】
  • 原文地址:https://www.cnblogs.com/threef/p/3202386.html
Copyright © 2011-2022 走看看