(1)開篇

一、抽象問題

    作者在被問到一個問題:“怎樣給一個磁盤文件排序”時,起初想到的是一些有關如何在磁盤上實現歸併排序的簡單思想。而當深究問題的細節時會發現,通過磁盤上的歸併排序不能是的問題被很好的解決。於是,對問題進行更客觀、更易用的形式描述:

    輸入:一個最多包含n個正整數的文件,每個數都小於n,其中n=10^7。如果輸入文件中有重複的數則出錯。

    輸出:升序排列輸入整數的列表,並重寫入原文件中。

    約束:最大有1MB的內存空間可用,有充足的磁盤存儲空間可用。運行時間若能在10秒以內則不需要進一步優化。

二、程序設計

    1、一種直觀的利用磁盤歸併排序的思想的解決方案是如果用32位整數來存儲每個7位數字的話,1MB空間內科存儲250000個號碼。因此,可以使用遍歷輸入文件40次的程序來完成排序。第一次對0~249999之間的整數排序,第二次對250000到499999之間的數排序,以此類推,最後經過40次的文件讀寫,完成整個文件的排序。此方法有多次的文件的讀寫,運行時間會很長,不可取。

    2、用位圖或位向量表示集合。每個7位十進制的整數表示一個小於1000萬的整數,我們使用一個具有1000萬位的字符串來表示這個文件。若整數i存在於文件中,則第i位爲1,否則爲0。這種表示利用了一些屬性:輸入數據限制在相對較小的範圍內;數據沒有重複;對每條記錄而言,除了單一整數外,沒有任何其他相關數據。

    可分三個階段來編程,第一階段將所有位初始化爲0,第二階段讀入文件中的每個整數來建立集合,將每個對應的位都設置爲1,第三階段檢驗每一位,如果該位爲1,就輸出對應的整數,由此產生有序的輸出文件。

三、原理

    對小問題的仔細分析有時可得到明顯的益處。該實例闡明瞭一下原理:

    1、明確的問題;

    2、位圖數據結構,該數據結構描述了一個有限定義域內的稠密集合,其中每一個元素最多出現一次並且沒有其他任何數據與該元素相關聯;

    3、多趟算法,多次讀入數據,每次完成一步;

    4、時間-空間的權衡;

    5、簡單的設計,簡單的設計的標準不是不能再增加任何東西,而是不能再減少任何東西

四、本章問題的一個位圖算法實現

//位圖排序法,時空高效的至高境界
#include <stdio.h>
#include <math.h>
#include <time.h>
 
 #define BITSPERWORD 32
 #define SHIFT 5
 #define MASK 0x1F
 #define N 10000000
 #define M 20
 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));
}

int myRand()   /* 產生一個0~1之間的隨機數 */
{
  int num;
  num = rand() % 10000000;
  return num;
}
int main(void) {
    int i;
    int j;
    int arr[M];
    int count=0;
    for (i = 0; i < N; i++) {
        clr(i);
    }
    //while (scanf("%d", &i) != EOF) {
    //    set(i);
    //}
    srand( (unsigned)time( NULL ) );     //注意這個隨機數種子不能放在產生隨機數myRand()函數中,否則每次調用都會產生幾乎同一個隨機數
    printf("The count of array is %d:/n",M);
    for (j = 0; j < M; j++) {    //供簡單的正確性測試
        arr[j]=myRand();            //注意,輸入的數不能重複     //否則當只輸入一次
    printf("%d/t",arr[j]);
    }
   
    for (j = 0; j < M; j++) {    //供簡單的正確性測試
        set(arr[j]);
    }
   
    printf("/nAfter Sorted:/n");
    for (i = 0; i < N; i++) {
        if (test(i)) {
        printf("%d/t", i);
        count++;
    }
    }
    printf("/nAfter sorted count is %d/n",count);    //打印出排序後的數字個數,如果有重複數字作爲輸入,則排序後數字的個數會比排序前少。
    return 0;
} 

五、習題1.9解答

    使用更多的空間來換取更少的時間存在一個問題:初始化空間本身需要消耗大量的時間。設計一種技術,在第一次訪問響亮的時將其初始化爲0。方案應該使用常量時間進行初始化和向量訪問,使用的額外空間應正比於向量的大小。此方法在空間很廉價、時間很寶貴且向量很稀疏的情況下才可考慮使用。

    解答:藉助於兩個額外的n元向量from、to和一個整數top。就可以使用標識來初始化向量data[0...n-1]。如果元素data[I]已被初始化,那麼from[i]<toop且to[from[i]]=i。因此,from是一個簡單的標識,to和top一起確保了from中不會被寫入內存裏的隨機內容。data、from、to向量關係如下所示:


    變量top初始爲0,下面的代碼實現對數組元素i的首次訪問:

    from[i] = top;

    to[top] = i;

    data[i] = 0;

    top++;

發佈了61 篇原創文章 · 獲贊 19 · 訪問量 7萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章