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。spa

輸入格式blog

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

輸出格式字符串

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

輸入樣例io

3table

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=六、十、1四、四、十二、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;

}

相關文章
相關標籤/搜索