C語言程序設計100例之(26):二進制數中1的個數


例26   二進制數中1的個數

問題描述

如果一個正整數m表示成二進制,它的位數為n(不包含前導0),稱它為一個n位二進制數。所有的n位二進制數中,1的總個數是多少呢?

例如,3位二進制數總共有4個,分別是4(100)、5(101)、6(110)、7(111),它們中1的個數一共是1+2+2+3=8,所以所有3位二進制數中,1的總個數為8。

輸入格式

一個整數T,表示輸入數據的組數,接下來有T行,每行包含一個正整數 n(1<=n<=20)。

輸出格式

對於每個n ,在一行內輸出n位二進制數中1的總個數。

輸入樣例

3

1

2

3

輸出樣例

1

3

8

        (1)編程思路1。

        對於輸入的n,n位二進制數m是位數為n並且首位為1的二進制數,且滿足:

      2n-1 ≤ n位二進制數m  <  2n

  因為首位為1,n位二進制數的個數就是n-1位的0和1的組合數,即2n-1個。

  第1位必須為1,所以第1位的1的個數為2n-1個。

  其他n-1位,總位數為(n-1)* 2n-1。其中0和1的個數是一半對一半,所以1的個數為(n-1)* 2n-1/2。

  合計1的位數為:2n-1 +(n-1)* 2n-1/2。

        因此,n位二進制數中1的個數直接用上式計算出來。計算時,用移位運算來計算2的n次方是一種快速的計算方法。

        即n位二進制數中1的個數為 :1<<(n-1)+(n-1)*(1<<(n-2))。

        (2)源程序1。

#include <stdio.h>

int main()

{

    int t,n;

    scanf("%d",&t);

    while(t--)

    {

        scanf("%d",&n);

        int ans=(1<<(n-1))+(n-1)*(1<<(n-2));

        printf("%d\n",ans);

    }

    return 0;

}

        (3)編程思路2。

        設數組元素f[i]的值表示i位二進制數中1的個數。因為i位二進制數可以看成是i-1位二進制數的每個數在其最右邊分別加上1或0得到的。因此,i位二進制數中1的個數一定是i-1位二進制數中1的個數的二倍,再加上i-1位二進制數的個數(因為每個數最右邊如果加上1,1的個數會增加1個,加上0不會增加)。

        即  f[i]=2*f[i-1]+2i-2   

        初始時,f[1]=1,  f[2]=2*f[1]+2^0=3 。

        (4)源程序2。

#include <stdio.h>

int main()

{

    int f[21]={0,1};

    for(int i=2;i<=20;i++)

    {

        f[i]=2*f[i-1]+(1<<(i-2));

    }

    int t,n;

    scanf("%d",&t);

    while(t--)

    {

        scanf("%d",&n);

        printf("%d\n",f[n]);

    }

    return 0;

}

習題26

26-1  1的個數相同

問題描述

給定一個大於0的整數n,把它轉換為二進制,則其二進制數中至少有1位是“1”。編寫一個程序,找出比給定的整數n大的最小整數m。要求m和n兩個整數轉換成二進制數后,二進制數中包含的1的個數相同。

例如,120的二進制數為01111000,則比120大且二進制數中1的個數相同的最小整數為135(10000111)。

輸入格式

輸入包含若干組數據。每組數據是一個整數 N (1<=N <=65535)。N = 0 時輸入結束。

輸出格式

對於每組數據,在單獨的一行輸出一個整數m。

樣例輸入

92

120

0

樣例輸出

99

135

        (1)編程思路1。

        尋找比n大的最小的整數m,最容易想到的方法是從n+1開始窮舉。首先把十進制整數n轉化為二進制,然后窮舉比這個十進制整數大的數m,判斷m和n兩個數對應的二進制數中1的個數是否相同。判斷的方法就是,把十進制數用n&1的位運算依次取出末位然后全部加起來,若兩個數的所有二進制位加起來相等,則這兩個數的二進制位一定有相同個1。

       (2)源程序1。

#include <stdio.h>

int main()

{

    int n,a,b,d,m;

         while (scanf("%d",&n) && n!=0)

    {

        a=n;  b=0;

        while(a)

        {

           b+=a&1;    a>>=1;

        }

        m=n;

        do {

           d=0;  m++;   a=m;

           while(a)

           {

               d+=a&1;   a>>=1;

           }

        } while(d!=b);

        printf("%d\n",m);

    }

    return 0;

}

(3)編程思路2。

對十進制數n轉化成的二進制數直接進行位變換,求出最小的整數m。

        具體方法是:先找到整數n對應的二進制數的最右邊的1個1,從這個1開始,從右向左將連續出現的k個1變為0后,高1位的0變為1,再從最低位開始,將k-1個0變為1,即可得到最小的數n。

        例如,32 對應的二進制數為00100000,將最右邊的連續1個1變為0,高1位0變為1,即為01000000,對應整數為64。

        又如,92 對應的二進制數為 01011100,將最右邊的連續3個1變為0(得01000000),高1位變為1(得01100000),再將最低位的2(3-1)個0變為1,即為01100011,對應整數為99。

        (4)源程序2。

#include <stdio.h>

int main()

{

    int n,a,b,k,m;

         while (scanf("%d",&n) && n!=0)

    {

        for (a=0; (n & (1<<a))==0; a++) ;    // 找到最右邊的1個1所在位置a

             for (b=a; (n & (1<<b))!=0; b++) ;    // 找到從a位開始向左的連續個1

             m =n | (1<<b);                   // 把b位改成1

        for (k=a; k<b; k++)  m^=(1<<k);   // 將從a位到b-1位的1全部取反變為0

        for (k=0; k<b-a-1; k++) m |= 1<<k;  // 將最低的b-a-1個位的0變為1

        printf("%d\n",m);

    }

    return 0;

}

(5)編程思路3。

        仔細琢磨整數的補碼表示和位運算,可以將上面程序中的幾個循環用一個表達式來完成。

        1)按補碼的表示法,正數的補碼與原碼相同,負數的補碼是相應正數的補碼的各位取反后加1。例如,以8位為例,32的補碼是00100000,-32的補碼是11100000;又如,92的補碼是 01011100,-92的補碼是 10100100。可以看出,把絕對值相等的正負兩個整數用二進制數補碼表示出來,從最低位開始到第1次出現1的地方為止,兩者是一致的,高位部分的0和1恰好是相反的。利用這個特性,將正數m和相應的負數 –m進行邏輯與(&)的話,就能得到最初1出現的地方。

        設x是整數n的二進制數保留最右邊一個1,其余各位變為0后,所得到的數,則x = n&(-n)。

        例如,n=92(01011100),則 -n=-92(10100100),x = n&(-n)  = 01011100&10100100 = 00000100。

        2)n+x 是從右往左將整數n的第一個01轉化為10。這是因為從最右邊的一個1到第一個01,之間必然全是1,加上x后會一直進位,直到把01變為10,此時10的右邊必然全是0。

        例如,n=92,則 n+x=01011100 + 00000100=01100000。

        3)表達式 n^(n+x) 可將整數n中最右邊的第1個1開始,連續出現的1保留下來,且第1個01轉化成的10中的1也保留下來,其余位全部為0。 n/x可以去掉最右邊的所有0。

        例如,n=92,n^(n+x) = 01011100^01100000 = 00111100。

                   n^(n+x)/x = 00111100/00000100 =00001111。

        即 n^(n+x)/x 相當將k+1(k為從整數n的最右邊的1個1開始,從右向左連續出現的1的個數)個1全部右移到最右邊,且左邊全部清0。由於最右邊只需將k-1個0變為1,因此,將n^(n+x)/x /4可以右移兩位,去掉兩個1。

        4)n+x+(n^(n+x))/x/4就是所求的最小整數。

(6)源程序3。

#include <stdio.h>

int main()

{

    int n,x,m;

         while (scanf("%d",&n) && n!=0)

    {

        x=n&-n;

        m= n+x+(n^(n+x))/x/4;

        printf("%d\n",m);

    }

    return 0;

}

26-2  二進制

本題選自洛谷題庫 (https://www.luogu.org/problem/P2104)

題目描述

小Z最近學會了二進制數,他覺得太小的二進制數太沒意思,於是他想對一個巨大二進制數做以下 4 種基礎運算:

運算 1:將整個二進制數加 1

運算 2:將整個二進制數減 1

運算 3:將整個二進制數乘 2

運算 4:將整個二進制數整除 2

小Z很想知道運算后的結果,他只好向你求助。

(Ps:為了簡化問題,數據保證+,-操作不會導致最高位的進位與退位)

輸入格式

第一行兩個正整數 n,m,表示原二進制數的長度以及運算數。

接下來一行 n 個字符,分別為‘0’或‘1’表示這個二進制數。

第三行 m 個字符,分別為‘+’,‘-’,‘*’,‘/’,對應運算 1,2,3,4。

輸出格式

一行若干個字符,表示經過運算后的二進制數。

輸入樣例

4 10

1101

*/-*-*-/*/

輸出樣例

10110

        (1)編程思路。

        由於數據保證+,-操作不會導致最高位的進位與退位,因此直接根據運算符進行模擬運算即可。各算符的模擬運算方法分別為:

        1)“+”: 從最后一個數(串中元素num[n-1])開始向前搜索,直到遇到“0”為止,中途所遇到的每個字符“1”都變成字符“0”(相當於二進制數+1,且進位),最后遇到的“0”變成“1”。

        2)“-”: 從最后一個數(串中元素num[n-1])開始向前搜索,直到遇到“1”為止,中途所遇到的每個字符“0”都變成字符“1”(相當於二進制數-1,且向前借位),最后遇到的“1”變成“0”。

        3)“*”:在字符串末尾增加一個“0”。

        4)“/”:將字符串最后一位刪除。

       (2)源程序。

#include <stdio.h>

char num[100000000]={0},op[6000000];

int main()

{

    int n,m;

    scanf("%d%d",&n,&m);

    scanf("%s%s",num,op);

    for (int k=0;k<m;k++)

    {

        int i;

        switch(op[k])

        {

            case '+': for (i=n-1;num[i]!='0'; i--)

                           num[i]='0';

                      num[i]='1';

                      break;

            case '-': for (i=n-1;num[i]!='1'; i--)

                           num[i]='1';

                      num[i]='0';

                      break;

            case '*': num[n++]='0';  num[n]='\0';

                      break;

            case '/': num[--n]='\0';

        }

    }

    printf("%s\n",num);

    return 0;

}

26-3  完全二叉搜索樹

問題描述

二叉搜索樹BST(Binary Search Tree)是這樣一棵樹,它或者是一棵空樹,或者是一棵具有下列特性的非空二叉樹:

(1)若它的左子樹不空,則左子樹上所有結點的值均小於它的根結點的值。

(2)若它的右子樹不空,則右子樹上所有結點的值均大於它的根結點的值。

(3)它的左、右子樹也分別為二叉搜索樹。

若一棵二叉樹既是一棵滿二叉樹,又是一棵二叉搜索數,則這棵樹是一棵完全二叉搜索樹。例如,圖1給出的就是一棵由1~15共15個整數構成的完全二叉搜索樹。

 

圖1 一棵完全二叉搜索樹

設有一棵由整數1~構成的完全二叉搜索樹,編寫一個程序,輸入一個整數num(),輸出在完全二叉搜索樹中以該整數為根結點的子樹的所有結點值中的最小值和最大值。例如,輸入12,輸出9和15;輸入14,輸出13和15;輸入13,輸出13和13。

輸入格式

一個整數num。

輸出格式

兩個整數,分別表示以整數num為根結點的子樹的所有結點值中的最小值和最大值。

輸入樣例

12

輸出樣例

9 15

       (1)編程思路。

         將個整數的完全二叉搜索樹先構造出來,然后找到整數num所在的結點p,則以p結點為根的子樹的中序遍歷序列的第1個結點就是所求的最小值、中序遍歷的最后一個結點就是所求得最大值。這樣雖然能夠解決問題,但顯然不是一個好的辦法。

         將圖1所示的完全二叉搜索樹中整數全部寫成二進制數,可以發現:

        1)奇數全部在最底層。最底層數據的二進制數的最右邊一定是1(即=1)。

        2)倒數第2層為2的倍數,其二進制數據的最右邊只有一個0,即=0、=1。

        3)倒數第3層為4的倍數,其二進制數據的最右邊有兩個0,即=0、=0、=1。

        4)倒數第4層為8的倍數,其二進制數據的最右邊有三個0,即=0、=1。

        將整數num(num=6、10、14、4、12、8)及所求的最小值和最大值列成如表1所示的表格。

表1 以num為根結點的BST的最小值和最大值(括號中為對應二進制數)

num

最小值

最大值

6  (0110)

5  (0101)

7  (0111)

10 (1010)

9  (1001)

11 (1011)

14 (1110)

13 (1111)

15 (1111)

4  (0100)

1  (0001)

7  (0111)

12 (1100)

9  (1001)

15 (1111)

8  (1000)

1  (0001)

15 (1111)

        觀察表1中的二進制數據,不難得出結論:

         1)以二進制數 X 為根的子樹的最小值是將 X 最右之 1 換成 0,再加 1 所得的數。

         2)以二進制數 X為根的子樹的最大值是將 X 最右之1 右邊的 0 全換成 1 所得的數。

         設二進制數X最右邊有連續k個0,若連續k個1組成的二進制數為P,則按上面的結論:最小值為X-P,最大值為X+P。

(2)源程序。

#include <stdio.h>

int main()

{

      int a,p;

      scanf("%d",&a);

      for(p=2;a%p==0;p*=2);

      p=p/2-1;

      printf("%d %d\n",a-p,a+p);

      return 0;

}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM