編程珠璣第一章習題答案


習題

1.1 

    如果不缺內存,如何使用一個具有庫的語言來實現一種排序算法?

    因為C++有sort,JAVA也有,這里以C++為例給出,記住如果用set集合來排序時,是不可以有元素重復的

    代碼:   

 #include <iostream>
 #include <cstring>
 #include <cmath>
 #include <queue>
 #include <stack>
 #include <list>
 #include <map>
 #include <set>
 #include <sstream>
 #include <string>
 #include <vector>
 #include <cstdio>
 #include <algorithm>
 using namespace std;
 void print(int a[],int n)
 {
     for(int i=0;i<n;i++)
     {
         cout<<" "<<a[i];
     }
     cout<<endl;
 }
 bool cmp(int a,int b)
 {
     return a>b;
 }
int main()
{

    int a[9]={9,6,5,5,4,11,23,8,99};
    int b[9]={9,6,5,5,4,11,23,8,99};
    sort(a,a+9);//從小到大
    print(a,9);
    sort(b,b+9,cmp);//從大到小
    print(b,9);
    return 0;
}

  

1.2  

     如何使用位邏輯運算來實現位向量?

可能會出現以下問題:(這部分和第3題有重復,讀者可直接看第4題)

1) 找到數據i所對應的字節位置 

2)找到數據i對應的字節中位位置

3) 判斷某位是否為1, 置某位為1  

解決它:

1) 找到 對應字節位置: 32系統,相當於 i/32, 使用位操作 數據i >> 5

2)  找到對應字節的位位置: 32系統相當於 i%32, 使用位操作 數據 i&0x1F(31)

   附:(m mod n 運算,當n = 2的X次冪的時候,m mod n = m&(n-1)  ,這里32為2^5,所以是i&0X1F)

 n=2^x 寫成二進制就是1后面x個0,m%n就是m的低x位。 

n-1=2^x-1 寫成二進制是x個1,m&(n-1)也是取m的低x位。

3)數字 a 的 第i位 是1: 方法 a & (1 << i) ,將數據a的第 i位 置1: a | (1 << i)

#define SHIFT 5
#define MASK 0x1F
#define N 10000000
int a[1 + N/BITSPERWORD];

void set(int i) {        a[i>>SHIFT] |=  (1<<(i & MASK)); }
void clr(int i) {        a[i>>SHIFT] &= ~(1<<(i & MASK)); }
int  test(int i){ return a[i>>SHIFT] &   (1<<(i & MASK)); }

  

1.3

        題目大意就是實現位圖排序,有不重復的100萬個整數,最大為1000萬,我有詳細解釋,見本人博客位圖排序。(不過建議你先看第4題)

1.4

   如何生成小於n且沒有重復的k個整數的問題。最簡單的方法是、就是使用前k個正整數。這個極端的數據集合將不明顯的改變位圖方法的運行時間,但是可能會歪曲系統排序的運行時間。如何生成位於0至n-1之間的k個不同的隨機順序的隨機整數?

    利用rand()函數可以產生0到RAND_MAX范圍內的隨機整數。

    RAND_MAX是在前面頭文件中定義的宏,具體大小與實現有關,至少為32767.

   一般32位機,int型為4字節,故RAND_MAX大小為2147483647(2^31-1).

    看代碼:

int Bigrand()// 產生很大隨機整數
{
    return RAND_MAX*rand()+rand();
}
int Randl_r(int l,int r)//產生很l~r之間的隨機數
{
    return l+Bigrand()%(r-l+1);
}

  然后利用這兩個函數產生K個不重復無序隨機數。(基本原理就是在長為n的有序區間里,把前K個元素隨機打亂)

    代碼:

     

void make_data(int num)
{
    int *temp = new int[MAXN_NUM];
    for(int i = 0; i <MAXN_NUM;i++)//MAXN_NUM指隨機數最大可能是多少
    {
        temp[i] = i + 1;
    }
    for(int i = 0; i < DATA_NUM; i++)//DATA_NUM指隨機數有多少個
    {
                int t = Randl_r(i,MAXN_NUM) % MAXN_NUM;
                swap(temp[i], temp[t]);
    }
} 
 

 

1.5

    那個程序員說他又1MB的可用存儲空間,但是我們概要描述的代碼需要1.25MB的空間。他可用不費力氣的索取到額外的空間。如果1MB空間是嚴格的邊界,你會推薦如何處理呢?

    解答:我們可以采用多趟算法,如2趟,首先采用500萬/8字節的空間(100字節約為1M),來排序0到4999999的數,第二趟再排序5000000到9999999的數!K趟算法空間需求n/k,時間開銷kn.典型的時間換空間。代碼:

#include <iostream>
#include <algorithm>
#include <time.h>
#include <bitset>
#include <iostream>
#include <cstring>
#include <cmath>
#include <queue>
#include <stack>
#include <list>
#include <map>
#include <set>
#include <sstream>
#include <string>
#include <bitset>
#include <vector>
#include <cstdio>
#include <algorithm>
#include <fstream>
using namespace std;

#define DATA_NUM 1000000          //生成的隨機數的個數
#define MAXN_NUM 1000000         //隨機數最大為多少
#define SOURCE     "data.txt"        //保存隨機數的文件名稱
#define RESULT     "result.txt"    //保存排序結果的文件名稱

//功能:生成隨機數文件
//參數:num 生成隨機數的個數
int Bigrand()// 產生很大隨機整數
{
    return RAND_MAX*rand()+rand();
}
int Randl_r(int l,int r)//產生很l~r之間的隨機數
{
    return l+Bigrand()%(r-l+1);
}
void make_data(int num)
{
    int *temp = new int[MAXN_NUM];
    if(temp == NULL)
    {
        cout << "new error in make_data()" << endl;
        return;
    }

    for(int i = 0; i <MAXN_NUM;i++)
    {
        temp[i] = i + 1;
    }

    for(int i = 0; i < DATA_NUM; i++)
    {
        int t = Randl_r(i,MAXN_NUM) % MAXN_NUM;
        swap(temp[i], temp[t]);
    }

    //寫入文件
    FILE *fp;
    fp = fopen(SOURCE, "w");
    for(int i = 0; i < DATA_NUM; i++)
    {
        fprintf(fp, "%d ", temp[i]);
    }
    fclose(fp);
    cout << "隨機數文件生成成功." << endl;
}

void BitMapSort()
{
    bitset<MAXN_NUM+5> bitmap;
    bitmap.reset();
    FILE *fpsrc;
    fpsrc = fopen(SOURCE, "r");
    int data;
    while(fscanf(fpsrc, "%d ", &data) != EOF)
    {
        if(data <= MAXN_NUM / 2)
        {
            bitmap.set(data, 1);
        }
    }
    //將排序好的數寫入到結果文件中
    FILE *fpdst;
    fpdst = fopen(RESULT, "w");
    for(int i = 0; i <MAXN_NUM+5; i++)
    {
        if(bitmap[i] == 1)
        {
            fprintf(fpdst, "%d ", i);
        }
    }

    //處理剩下的數據
    int res = fseek(fpsrc, 0, SEEK_SET);
    if(res)
    {
        cout << "fseek() error in BitMapSet()." << endl;
        return;
    }
    bitmap.reset();
    while(fscanf(fpsrc, "%d ", &data) != EOF)
    {
        if(data <= MAXN_NUM&& data > MAXN_NUM/ 2)
        {
            data = data -MAXN_NUM / 2;
            bitmap.set(data, 1);
        }
    }

    for(int i = 0; i <= MAXN_NUM/ 2 + 1; i++)
    {
        if(bitmap[i] == 1)
        {
            fprintf(fpdst, "%d ", i + MAXN_NUM / 2);
        }
    }
    cout << "排序成功." << endl;
    fclose(fpdst);
    fclose(fpdst);
}


int main()
{
    make_data(DATA_NUM);
    clock_t start = clock();
    BitMapSort();
    clock_t end = clock();
    cout << "排序所用時間為:" << (end - start) * 1.0 / CLK_TCK << "s" << endl;
    return 0;
}

1.6

   如果那個程序員說的不是每個整數最多出現一次,而是每個整數最多出現10次,你又如何建議他呢?你的解決方案如何隨着可用存儲空間總量的變化而變化呢?
思路
    如果每個整數最多出現10次,那么我們可以用4位(2^3<10<2^4)(半個字節)來統計每個整數的出現次數。可以利用問題5中的方法,利用10000000/2個字節的空間遍歷一次來完成對整個文件的排序。當保存的數字量變化時,分成更多的份,就可以再更小的空間內完成,如10000000/2k的空間內。

    問題是如何實現?同樣,我們還會遇到以下問題:

    1) 找到數據i所對應的字節位置 

       2) 找到數據i對應的字節中位位置

       3) 判斷某位有幾個數字(即這個數字出現了幾次) 

 下面我來解釋:

      1)因為32位機int有4個字節,32位,我們用一個int可以記錄8個數字的情況,所以字節位置可以用 i/8(i>>3)找到,這個不難想。

      2)如何找到保存這個數字情況的4位呢?看下面

           假設下面這是a[1]的前12位(總共32位,從右向左表示從低到高位),它可以表示8到10這幾個數  我們先假設此時已有5個8,1個9,8個10


                                                10          9            8                                             (這一行表示這四位記錄數字i的狀態)

                                                 2           1            0                                            (這一行表示四位四位的進行編號,相當於i%8(i&0X07))

                                                                                                                            我們可以知道數字i的狀態由編號為i%8的那四個位來表示

                                  .......    1 0 0 0    0 0 0 1    0 1 0 1                                       (這一行是i的前12位)

好了,假如此時有有一個9需要加入,我們怎么加?

                       當然是  .......     1 0 0 0    0 0 0 1    0 1 0 1                

                                    +       0 0 0 0    0 0 0 1    0 0 0 0

                    仔細觀察其實就是   0 0 0 0    0 0 0 0    0 0 0 1向左移四位,可以表示為1<<(4*(i&0X07));

                    所以,這道題的置位函數set(int i) 可以表示為 a[i>>3]+=1<<(4*(i&0X07));


 然后我們來看清位函數clr(int i)如何寫?此時我們已有5個8,2個9,8個10

                                                10          9            8   

                                                 2           1            0   

                                           1 0 0 0    0 0 1 0    0 1 0 1   

如果要把9的個數置0而又不影響其他位,就只能用它和另一個數按位與&了(意思就是按位與的另一個數除了需要被置零的那四位為0,其余位都要為1,如(1)所示)

                            就像這樣: 1 0 0 0    0 0 1 0    0 1 0 1   

                                    &     1 1 1 1    0 0 0 0    1 1 1 1   (1)

                                    =     1  1 1 1   0 0 0 0    0 1 0 1

可以看出他們按位與后只有表示9的4位都置零了,而其他位並未受影響,如何得到這個數呢?

                        觀察發現可以對 0 0 0 0  1 1 1 1   0 0 0 0取反得到,而0 0 0 0  1 1 1 1  0 0 0 0可以將1 1 1 1 左移四位得到,即(0X0F<<(4*i&(0X07))

所以clr函數可以用表示為 a[i>>3]&=~(0X0F<<(4*(i&0X07)));


 接下來就是搞定測試函數test(int i)了,同樣接上面舉例,假如要測試存在幾個9

                                                10          9            8   

                                                 2                      0   

                                           1 0 0 0    0 0 1 0    0 1 0 1   

那我們只要用1 1 1 1 與表示9的四位按位與就知道有幾個了,即

                                           1 0 0 0    0 0 1 0    0 1 0 1  

                                    &     0 0 0 0    1 1 1 1    0 0 0 0

                                    =     0 0 0 0    0 0 1 0    0 0 0 0  (2)

  可以看出答案就是將(2)式右移4位,於是乎,答案也就呼之欲出了

                                    a[i>>3]  & (0X0F<<(4*i&(0X07))))>>4*(i&0X07)

下面給出設置,清除,以及測試函數(但是我發現了一個問題那就是,開數組時必須用無符號型int,即unsigned int,因為有符號型第32位是表示正負的標志位,如果用有符號的,會出錯,讀者可以自己考慮出錯在哪里),有了這3個,第6題就做出了大半,其余的參考第五題。    

   #define SHIFT 3
   #define MASK 0X07
   #define MASK1 0X0F

void set_(int i)//i的個數加一
{
    a[i>>SHIFT]+=(1<<(4*(i&MASK)));
}
void clr(int i)//清0
{
    a[i>>SHIFT]&=~(MASK1<<4*(i&MASK));
}
int test(int i)//返回有幾個i
{
    return (a[i>>SHIFT] & (MASK1<<4*(i&MASK)))>>4*(i&MASK);
}

1.7

    考慮中........

1.8

     當那個程序員解決該問題的時候,美國所有的免費電話的區號是800。現在免費電話的區號包括800、877和888,而且還在增多。如何在1MB空間內完成對所有這些免費電話的號碼的排序?如何將免費電話號碼存儲在一個集合中,要求可以實現非常快速的查找以判定一個給定的免費電話號碼是否可用或者已經存在?

   解答:  每個區號建立一個set,查找的時候效率是O(logn)。

1.9

      使用更多的空間來換取更少的運行時間存在一個問題:初始化空間本身需要消耗大量的時間。說明如何設計一種技術,在第一次訪問向量的項時將其初始化為0。你的方案應該使用常量時間進行初始化和向量訪問,使用的額外空間應正比於向量的大小。因為該方法通過進一步增加空間減少初始化的時間,所以僅在空間很廉價、時間很寶貴且向量很稀疏的情況下才考慮。

    ||:書上給出的解決方法是使用兩個額外的向量:from和to,還有一個變量top。如果對i位置進行初始化,進行以下步驟:



     from[i] = top;
     to[top] = i;
     data[i] = 0;
     top++;

 


        說實話,我還在思考中,不過最終,我會完善這個答案的。

1.10

      在成本低廉的隔日送達時代之前,商店允許顧客通過電話訂購商品,並在幾天后上門自取。商店數據庫使用客戶的電話號碼作為其檢索的主關鍵字(客戶知道他們的電話號碼,並且這些關鍵字幾乎都是唯一的)。你如何組織商店的數據庫,以允許高效的插入和檢索操作?

    ||:

      根據電話號碼的最后兩位作為客戶的散列索引,進行分類,當顧客打電話下訂單的時候,它被放置在一個合適的位置。然后當顧客抵達進行檢索商品時,營業員按順序檢索訂單,這是經典的用“順序搜索來解決沖突的開放散列”:通過順序檢索。電話號碼的最后兩位數字是相當隨機的,而電話號碼的前兩位作為索引行不通,因為很多電話號碼的前兩位是相同的。(定過外賣沒?外送員是怎樣確定顧客的?嘿嘿)

1.11

    這個嘛,略。。。

1.12

   題目就不打詳細了,大概意思是載人航天需要在外太空的極端環境下實現書寫。民間盛傳美國國家航天局花費100萬美元研發了一種特殊的鋼筆來解決這個問題。那么前蘇聯如何解決這個問題的?

   ||:額,這個答案我還小時就知道了,鉛筆,但是當時我是覺得這大概是有原因的。。。。額,不裝B了,其實在看三傻大鬧寶萊塢時我從那個校長嘴里知道了為啥在要研究這這么昂貴的鋼筆,它是有原因的,順便提一下,三傻大鬧寶萊塢,不錯!

如果你想知道原因,要么去看三傻,要么去看這篇博客:航天員太空用筆:一個被誤會10年的故事(推薦看電影,學習也要勞逸結合嘛!)


免責聲明!

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



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