这是一道挺有意思的利用二进制的性质进行打印n个数字(从0到n)的所有组合题目。

首先:辨析一下排列和组合的区别。

排列组合

排列:

组合:

可以看出排列的数目大于等于组合的数目,以及他们的计算方式如上图。公式来源详见解析链接,这很快帮助你回忆如何计算。


回归题目:

n个元素的子集一共有 $2^n$个(从下面的对应关系中的二进制数也可看出的确是$2^n$)

那么他们此间的对应关系是这样的:

这个神奇的的排列规律是我们进行下面编程的理论(可以从二进制看子集与十进制)

#include<bits/stdc++.h>
using namespace std;
int main(){
    int n=3;
   for(int i=0;i<(1<<n);i++)//解释一下1<<n是位运算,结果是2^n
    {
        for(int j=0;j<n;j++){
            if(i& (1<<j))//其中i只在第一次空集的时发挥作用。
            
            //这里巧妙地运用了二进制的知识,并且运用位运算的性质,得到结果
            
            cout<<j<<" ";
        }
        cout<<endl;
    }
    return 0;
}

输出结果:

0
1
0 1
2
0 2
1 2
0 1 2

刚开始,觉得这个输出很神奇,直接输出了n个数字的所有集合(还按照一定的顺序),代码还十分简洁。

其中两个for循环中的if判断可谓是亮点了。

那么究竟是什么意思呢,记住这里还要会回顾一下那个表格了:

if (i & ( 1<<j ) )

首先明确一下 “&”:, 即在二进制中 同一为一(取每一位二进制的数字,两者均为1时为1):

例如 0001 & 0011为 1;

​ 0010 & 0001为0。

此行代码中的’ i ‘指的是二进制数中1的个数,可以发现在二进制中第一次i==0,而二进制数中不含1的只有空集,与结果相对应;

接着我们分析i==1时刻,判断条件其实可以替换为(0001 & ( i<<j )),这是说明只有二进制数最后一位为1的才能呵前面$1_{(10)}$或者说$0001_{(2)}$相 “与&” 为真。那么我们可以看出只有1 3 5 7这样的奇数才满足尾数为1,才能使得条件成立。BUT我们分析一下判断条件里的另一个变量1<<j,代表的是$2^n$,也就是说只有j==0的时候,才成立,这也就是第二行结果“0”的来历;

同理我们可以分析出i==2,即二进制的0010的时候只有j==1的时候才成立;

i==3的时候,即左边为$0011_{(2)}$的时候这个时候只要是 在最后一位为一或者倒数第二位数字是一的二进制数即可,也就是说 十进制的0,1,3,5,7都可成立。但是 要注意的是 (i<<j)只能为十进制的$2^n$,所含的奇数只有1。所以除0,1外的j均不成立。

经过上面的分析我们不得不对代码的正确性给予了肯定,但好像还是不太清楚为什么能够这样做。

那我们不妨回首二进制。

不难发现,我们最终并没有用到3,4,5,6,7这样的十进制数字(尽管他们在上表中有所对应,但那也不过是各种情况的编号罢了,),只是用了0,1,2这恰好是[0,n)的范围内的数字。大胆推测 只有当i的数字越大那么在j中二进制中的1 出现的概率越大,(tips,$0111_{(2)}$恰好是7,二进制中的1的个数是(2+1)个1 emm~)。例如$7_{(10)}$ 对应$0111_{(2)}$那么他就可以有三个数字的集合成立了(果然是012)

上面一段有点饶头,仔细想想,都是对的,但似乎没什么用~可以无视~~

自己在不断探索的过程中,仍能够不断的发现二进制的奇妙用途,和神奇1 的对应。此题还需慢慢回味。


​ 数日后,再次回顾此代码,发现有着许多的新的感悟:之前不太明白的地方都一一揭开:

首先就是

哈哈,这个和上图的有什么区别呢?并没有,只是多了一个标识i

if (i & ( 1<<j ) )

回顾代码:不难看出其实对于作者的行为我是有一些困惑的。为什么要这样做?

那我们不妨解释一下i和j的作用好了

i :其实对应的是上表中的对应的十进制:也就是说,我们将i循环了$n^2$次,其实就是恰好我们将这n个子集打印出来。

j :其实是二进制的从低到高位依次赋值1,并判断是否&,1<<j 其实就是 $2^j$ 次方wow,当j为0时其实就是二进制的最后一位为1,j=1时判断倒数第二位是否为1。那么判断的另一个对象是谁呢?当然是i。

每一个i的二进制都不相同,我们就可以依次利用j来判断该位上是否有数字有的话就把j输出出去,j是[0,n)所以可以利用二进制完美地实现所有子集的输出。