位运算技巧,继续
O(1)的时间,根据前一组合枚举下一组合
using System;
namespace ConsoleApplication2
{
class Program
{
static void Main(string[] args)
{
combian(5, 3);
Console.Read();
}
static void combian(int n, int k)
{
if (!(n >= k && k > 0 && n < 30))
{
return;
}
int ik = (1 << k) - 1;
while (ik != 0)
{
// 输出组合
for (int i = 0; i < n; ++i)
{
if ((ik & (1 << i)) != 0)
{
Console.Write(i);
}
}
//输出空行分隔之
Console.WriteLine();
ik = next_combination(n, ik);
}
}
static int next_combination(int n, int k)
{
int ret, b = k & -k, t = (k + b);
ret = (((t ^ k) >> 2) / b) | t;
if ((1 << ret) < k)
{
return 0;
}
else
{
return ret;
}
}
}
}
根据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求和,或者求并均可。
输出结果是:
0 1 2
0 1 3
0 2 3
1 2 3
0 1 4
0 2 4
1 2 4
0 3 4
1 3 4
2 3 4