首页 > 代码库 > 取子集

取子集

</pre><pre name="code" class="cpp">#include<cstdio>
#include<cstring>
#include<iostream>
using namespace std;
void print(int n)//二进制观察,从左到右对应低位到高位
{
    for(int i=0;(1<<i)<=n;i++)
        if(n&(1<<i))
            cout<<1;
        else cout<<0;
    cout<<endl;
}

int main()
{
    int sum;
    while(scanf("%d",&sum)!=EOF)
    {
        cout<<"sum : ";print(sum);
        for(int i=sum;i;i=(i-1)&sum)
            print(i);
        cout<<endl;
    }
    return 0;
}

关键代码

 for(int i=sum;i;i=(i-1)&sum)
附:这段代码源于我在大白上看到的类似代码


对于集合sum。1表示该物品可取,0表示不可取,求出所有的可取状态。

上述代码不重复地取出了所有的子集!


下面给出i=(i-1)&sum这一表达式的证明

1.不重复性

证明:      设j=(i-1)&sum;( i 是已知的正确的状态)

由于取了&sum说明 j 也是正确的状态。

而  j是由(i-1)和某数按位与得到的,

则    j<i-1<i  

得证(i-1)&sum能取出不重复的正确的状态来。


2.完整性

证明:    上面谈到了不重复性的证明,其实就是 i 的单调递减的证明

现在我们只需要证明   j=(i-1)&sum  得到的 j 是第一个比 i 小的状态。

设 状态 i  是  abcdef 10000   ; 

i-1     是        abcdef  01111   ;

那么   (i-1)&sum会得到什么呢

我们将  i-1 分解来看  为  (abcdef 00000  + 000000 01111)&sum =(abcdef 00000&sum) + (000000 01111&sum)

                                                                                                                           = abcdef 00000 + (000000 01111&sum)

                注意到    i&sum=i  ( i 是已知的正确状态)

将 i 也分解来看看             (abcdedf 00000  + 000000 10000) &sum = abcdef 00000 + (000000 10000&sum)

假设   状态   s=abcdef 0****是第一个比 i 小的状态

可得:

①    s>=j

        ②    s<i<(abcdef 10000&sum)  <=  (abcdef 01111&sum)  =  (i-1)&sum  =  j            即   s<=j

由①②可得    s=j  ;

得证  j  是第一个比  i  小的状态 

得证 完整性


由1,2可以证明该算法的正确性 ,并可算出时间复杂度为( 2^n , n为可选物品数)。而所有的子集也为 2^n 个,因此该算法相当的好啊。




这神奇的取子集方法~~


取子集