Calculating the next higher number which has same number of set bits?

后端 未结 3 447
不思量自难忘°
不思量自难忘° 2021-01-17 06:35

A solution is given to this question on geeksforgeeks website.

I wish to know does there exist a better and a simpler solution? This is a bit complicated to understa

相关标签:
3条回答
  • 2021-01-17 06:54

    There is a simpler, though definitely less efficient one. It follows:

    • Count the number of bits in your number (right shift your number until it reaches zero, and count the number of times the rightmost bit is 1).
    • Increment the number until you get the same result.

    Of course it is extremely inefficient. Consider a number that's a power of 2 (having 1 bit set). You'll have to double this number to get your answer, incrementing the number by 1 in each iteration. Of course it won't work.

    If you want a simpler efficient algorithm, I don't think there is one. In fact, it seems pretty simple and straightforward to me.

    Edit: By "simpler", I mean it's mpre straightforward to implement, and possibly has a little less code lines.

    0 讨论(0)
  • 2021-01-17 07:04

    Based on some code I happened to have kicking around which is quite similar to the geeksforgeeks solution (see this answer: https://stackoverflow.com/a/14717440/1566221) and a highly optimized version of @QuestionC's answer which avoids some of the shifting, I concluded that division is slow enough on some CPUs (that is, on my Intel i5 laptop) that looping actually wins out.

    However, it is possible to replace the division in the g-for-g solution with a shift loop, and that turned out to be the fastest algorithm, again just on my machine. I'm pasting the code here for anyone who wants to test it.

    For any implementation, there are two annoying corner cases: one is where the given integer is 0; the other is where the integer is the largest possible value. The following functions all have the same behaviour: if given the largest integer with k bits, they return the smallest integer with k bits, thereby restarting the loop. (That works for 0, too: it means that given 0, the functions return 0.)

    Bit-hack solution with division:

    template<typename UnsignedInteger>
    UnsignedInteger next_combination_1(UnsignedInteger comb) {
      UnsignedInteger last_one = comb & -comb;
      UnsignedInteger last_zero = (comb + last_one) &~ comb;
      if (last_zero)
        return comb + last_one + ((last_zero / last_one) >> 1) - 1;
      else if (last_one)
        return UnsignedInteger(-1) / last_one;
      else
        return 0;
    }
    

    Bit-hack solution with division replaced by a shift loop

    template<typename UnsignedInteger>
    UnsignedInteger next_combination_2(UnsignedInteger comb) {
      UnsignedInteger last_one = comb & -comb;
      UnsignedInteger last_zero = (comb + last_one) &~ comb;
      UnsignedInteger ones = (last_zero - 1) & ~(last_one - 1);
      if (ones) while (!(ones & 1)) ones >>= 1;
      comb += last_one;
      if (comb) comb += ones >> 1; else comb = ones;
      return comb;
    }
    

    Optimized shifting solution

    template<typename UnsignedInteger>
    UnsignedInteger next_combination_3(UnsignedInteger comb) {
      if (comb) {
        // Shift the trailing zeros, keeping a count.
        int zeros = 0; for (; !(comb & 1); comb >>= 1, ++zeros);
        // Adding one at this point turns all the trailing ones into
        // trailing zeros, and also changes the 0 before them into a 1.
        // In effect, this is steps 3, 4 and 5 of QuestionC's solution,
        // without actually shifting the 1s.
        UnsignedInteger res = comb + 1U;
        // We need to put some ones back on the end of the value.
        // The ones to put back are precisely the ones which were at
        // the end of the value before we added 1, except we want to
        // put back one less (because the 1 we added counts). We get
        // the old trailing ones with a bit-hack.
        UnsignedInteger ones = comb &~ res;
        // Now, we finish shifting the result back to the left
        res <<= zeros;
        // And we add the trailing ones. If res is 0 at this point,
        // we started with the largest value, and ones is the smallest
        // value.
        if (res) res += ones >> 1;
        else res = ones;
        comb = res;
      }
      return comb;
    }
    

    (Some would say that the above is yet another bit-hack, and I won't argue.)

    Highly non-representative benchmark

    I tested this by running through all 32-bit numbers. (That is, I create the smallest pattern with i ones and then cycle through all the possibilities, for each value of i from 0 to 32.):

    #include <iostream>
    int main(int argc, char** argv) {
      uint64_t count = 0;
      for (int i = 0; i <= 32; ++i) {
        unsigned comb = (1ULL << i) - 1;
        unsigned start = comb;
        do {
          comb = next_combination_x(comb);
          ++count;
        } while (comb != start);
      }
      std::cout << "Found " << count << " combinations; expected " << (1ULL << 32) << '\n';
      return 0;
    }
    

    The result:

    1. Bit-hack with division: 43.6 seconds
    2. Bit-hack with shifting: 15.5 seconds 
    3. Shifting algorithm:     19.0 seconds
    
    0 讨论(0)
  • 2021-01-17 07:13

    I am pretty sure this algorithm is as efficient and easier to understand than your linked algorithm.

    The strategy here is to understand that the only way to make a number bigger without increasing its number of 1's is to carry a 1, but if you carry multiple 1's then you must add them back in.

    • Given a number 1001 1100

    • Right shift it until the value is odd, 0010 0111. Remember the number of shifts: shifts = 2;

    • Right shift it until the value is even, 0000 0100. Remember the number of shifts performed and bits consumed. shifts += 3; bits = 3;

    • So far, we have taken 5 shifts and 3 bits from the algorithm to carry the lowest digit possible. Now we pay it back.

    • Make the rightmost bit 1. 0000 0101. We now owe it 2 bits. bits -= 1

    • Shift left 3 times to add the 0's. 0010 1000. We do it three times because shifts - bits == 3 shifts -= 3

    • Now we owe the number two bits and two shifts. So shift it left twice, setting the leftmost bit to 1 each time. 1010 0011. We've paid back all the bits and all the shifts. bits -= 2; shifts -= 2; bits == 0; shifts == 0

    Here's a few other examples... each step is shown as current_val, shifts_owed, bits_owed

    0000 0110
    0000 0110, 0, 0 # Start
    0000 0011, 1, 0 # Shift right till odd
    0000 0000, 3, 2 # Shift right till even
    0000 0001, 3, 1 # Set LSB
    0000 0100, 1, 1 # Shift left 0's
    0000 1001, 0, 0 # Shift left 1's
    

    0011 0011
    0011 0011, 0, 0 # Start
    0011 0011, 0, 0 # Shift right till odd
    0000 1100, 2, 2 # Shift right till even
    0000 1101, 2, 1 # Set LSB
    0001 1010, 1, 1 # Shift left 0's
    0011 0101, 0, 0 # Shift left 1's
    

    0 讨论(0)
提交回复
热议问题