找到具有相同数量的1位的下一个较小数字的有效方法

时间:2013-09-21 05:35:37

标签: algorithm binary

我正在研究这个问题,并想出一个解决方案(可能需要添加一两个条件),但不确定这是否是正确的方法,并且发现使用两个循环而不是很麻烦确定这是否是有效的方式。如果有人有一些很好的技巧,或者任何更好的方法将是受欢迎的将是伟大的:)。 (语言不是障碍)

我的算法:

  • 首先在数字
  • 中找到第一个'0'lsb位
  • 然后找到该'0'位旁边的下一个设置位
  • 将您找到的'0'位更改为1& '1'位为'0'
  • 你得到的数字是下一个更小的
  • 如果所有位都已设置,那么您没有任何数字,下一个更小的数字与“1”位相同。
void nextSmaller(int number) {
    int firstZeroBitHelper = 1, nextOneBitHelper;
    while (firstZeroBitHelper < number) {
        // when we find first lsb zero bit we'll stop
        bool bit = number & firstZeroBitHelper;
        if (bit == false)
            break;
        firstZeroBitHelper = firstZeroBitHelper << 1;
    }

    if (firstZeroBitHelper >= number) {
        cout << "No minimum number exists" << endl;
        return;
    }

    nextOneBitHelper = firstZeroBitHelper;
    nextOneBitHelper = nextOneBitHelper << 1;

    while (nextOneBitHelper < number) {
        // when we get '1' after the previous zero we stop
        bool bit = number & nextOneBitHelper;
        if (bit == true)
            break;
        nextOneBitHelper = nextOneBitHelper << 1;
    }

    // change the first zero to 1
    number = number | firstZeroBitHelper;
    // change the next set bit to zero
    number = number & ~nextOneBitHelper;

    cout << number << endl;
}

5 个答案:

答案 0 :(得分:3)

继续发表评论..

好吧,我找到了它,而且很快。参见“计算机编程艺术”第7.1.3章(第4A卷),回答问题21:“与Gosper的黑客相反”。

看起来像这样:

t = y + 1;
u = t ^ y;
v = t & y;
x = v - (v & -v) / (u + 1);

其中y是输入,x是结果。与Gosper的黑客相同的优化适用于该部门。

答案 1 :(得分:3)

向上走:

  • 在数字中找到最右边的“01”,并将其设为“10”。
  • 尽可能向右对齐所有后续1位。

向下走:

  • 在数字中找到最右边的“10”,并将其设为“01”。
  • 左对齐所有跟随 1位(即如果您刚设置的位后面跟着1,则不要做任何事情。)

使向下案例清晰的一个例子:

  • 225 = 0b11 10 0001
  • 交换:0b11 01 000 1
  • 左对齐:0b1101 1 000 = 216

我将首先解释向上的情况,因为对我来说感觉不那么棘手。我们希望找到最不重要的位置,我们可以向左移动1位的位置(换句话说,最右边的0向右移动1)。应该清楚的是,这是我们可能设置的最右边的位,因为我们需要为我们设置的每个位清除一点,我们需要在某处清除 right 我们设置的位,否则数字会变小而不是更大。

现在我们已经设置了这个位,我们想要清除一位(恢复设置位的总数),并重新洗牌剩下的位,使数字尽可能小(这使它成为 next 具有相同设置位数的最大数字)。我们可以清除我们刚设置的那个位右边的位,并且我们可以尽可能地向右移动任何剩余的1位而不用担心低于我们的原始数,因为所有不太重要的位仍然加起来少于我们设定的单个位。

找到下一个较低的数字而不是下一个较高的数字基本相同,只是我们正在寻找最右边的位置,我们可以移动一个位置正确,然后执行该操作我们希望尽可能远离 left 移动所有不太重要的位。

看起来其他人手头上已经掌握了这个版本的点点滴滴版本,但我想知道我是否可以对算法的逻辑/数学方面做出很好的解释。

答案 2 :(得分:2)

您描述的算法不太正确;除了一个细节,它做的一切正确。任何二进制数都具有以下形式,位于算法的中间:

xxxxx...10000...1111...
         ---n---// f // 

此处xxx...是任意位,连续的0和1的数量由firstZeroBitHelpernextOneBitHelperfn确定。 / p>

现在你必须减少这个数字,留下相同数量的设定位,这必然会将最重要的1变为0

xxxxx...0????...????...
         -----n+f------ 

请注意,位???的任何值都会使新数字小于原始数字,并且您确实希望选择这些位,以使得结果数字具有最大值:

xxxxx...011111...0000...
         ---f+1--//n-1// 

所以你不得不翻转2位,而是f+2位(从10的一位,以及从f+10的其他位1)。

一种方法如下。

首先关闭所有相关位:

number &= ~nextOneBitHelper;
number &= ~(nextOneBitHelper - 1);

现在从MSB开始打开所需的位:

nextOneBitHelper >>= 1;
while (firstZeroBitHelper != 0)
{
    number |= nextOneBitHelper;
    nextOneBitHelper >>= 1;
    firstZeroBitHelper >>= 1;
}

可以在没有循环的情况下实现上述的比特琐事;为此,您需要计算nf。做完后:

unsigned mask = (1 << (f + 1)) - 1; // has f+1 bits set to 1
mask <<= n - 1; // now has these bits at correct positions
number |= mask; // now the number has these bits set

答案 3 :(得分:2)

anatolyg很好地覆盖了你的算法,但是有一个更有效的解决方案。

你可以巧妙地使用Gosper's hack,如果你翻转一下这些位,那么Gosper会按降序生成值。

像这样的伪代码会起作用:

let k := number
let n := num bits in k (log base 2)
k = k ^ ((1 << n) - 1)
k = gosper(k)
k = k ^ ((1 << n) - 1)
return k

如果你认为xor是线性时间算法,这给你一个很好的O(1)(或O(log n))。 :)

在某些情况下你必须考虑,比如某些k=2^x-1的{​​{1}},但这很容易被抓住。

答案 4 :(得分:1)

#include <iostream>

bool AlmostdivBy2(int num) {
    return (-~num & (num)) == 0;
}

void toggleright(int &num) {
    int t;
    for (t = -1; num & 1; t++)
        num >>= 1;
    ++num = (num << t) | ~-(1 << t);
}

void toggleleft(int &num) {
    while (~num & 1)
        num >>= 1; //Simply keep chopping off zeros
    //~num & 1 checks if the number is even
    //Even numbers have a zero at bit at the rightmost spot
}

int main() {

    int value;
    std::cin >> value;
    if (!AlmostdivBy2(value)) {
        (~value & 1) ? toggleleft(value) : toggleright(value);
    }
    std::cout << value << "\n";
    return 0;
}

我想我可能已经考虑过这个,但这是我的解释:

  • 如果数字接近于2的幂,即1,3,7,15,31 ......之类的值,则没有小于可能具有相同数量的值的值在他们的二进制表示。因此,我们不担心这些数字。

  • 如果数字是偶数,这是另一个简单的解决方法,我们只是从末尾切断零,直到数字为奇数

  • 奇数是最具挑战性的,这就是为什么它是递归的。首先,您必须从数字右侧开始找到第一个零位。当找到它时,你将一个数字加上一个将最后一位变为1的数字。当递归展开时,你继续将位移到左边并加一个。完成后,您将拥有下一个最小的。

希望我没有混淆你

修改

更多地处理它,这里是toggleright

的非递归版本
void toggleright(int &num) {    
    int t = 1;
    while ( (num >>= 1) & 1 && t++ );
    num = (-~num << ~-t) | ~-(1 << t);    
}