如何從40億整數中找到不存在的一個


原文地址:https://www.yanbinghu.com/2018/12/22/40915.html

前言

給定一個最多包含40億個隨機排列的32位的順序整數的順序文件,找出一個不在文件中的32位整數。(在文件中至少確實一個這樣的數-為什么?)。在具有足夠內存的情況下,如何解決該問題?如果有幾個外部的“臨時”文件可用,但是僅有幾百字節的內存,又該如何解決該問題?

分析

這仍然是《編程珠璣》中的一個問題。前面我們曾經提到過《位圖法》,我們使用位圖法解決了這個問題。32位整型最多有4294967296個整數,而很顯然40億個數中必然會至少缺一個。我們同樣也可以嘗試使用位圖法解決該問題,使用536 870 912個字節,約512M內存存儲這40億整數,存在該整數的位置1,最后遍歷比特位,輸出第一個比特位為0的位置即可。那如果僅借助幾個“臨時”文件,使用幾百字節的內存的情況下該如何處理呢?

能否使用二分搜索呢?這40億個整數是隨機排列的,因此普通的二分搜索不能找到那個不存在的數。但是我們可以基於二分搜索的思想。

一個整數有32位,我們按照每個比特位是0還是1,將要查找的數據范圍一分為二。從最高比特位開始:

  • 將最高比特位為0的放在一堆,為1的放在另外一堆
  • 如果一樣多,則隨意選擇一堆,例如選0,則該位為0
  • 如果不一樣多,選擇少的一堆繼續,如1更少,則該位為1

這里需要做一些解釋:

  • 由於2^32個整數中,每一個比特位是1還是0的個數是相同的。如果在這40億個整數中,某比特位為1和0的個數是相同的,則說明兩邊都有不存在的數。因此選擇任意一堆即可。
  • 如果比特位1的整數比0的整數多,則說明,比特位為0的一堆數中,肯定缺少了一些數。而比特位為1的一堆數中,可能缺少一些數。因此,我們選擇少的,也就是比特位為0的那一堆數。
  • 每一次選擇,都記錄選擇的是0還是1,最多32次選擇后,便可以至少找到一個整數,不存在這40億數中。

實例說明

由於32位的整型數據量太多,不便說明,我們用一個4比特的數據對上面的思路再做一個說明。4比特最多有16個數。
假設有以下源數據:

3 5 2 6 7 -1 -4 -6 -3 1 -5

對應二進制形式如下(負數在內存中以補碼形式存儲):

0011 0101 0010 0110 0111 1111 1100 1010 1101 0001 1011

1.處理第1比特位被分為兩部分數據,分別為:

  • 比特位為0的
0011 0101 0010 0110 0111 0001 
  • 比特位為1的
1111 1100 1010 1101 1011

可以看到,第一比特位為1的數為5個,比比特位為0的數要少,因此選擇比特位為1的數,繼續處理。且第一比特位,獲得1.

3.處理第2比特位仍然分為兩部分數據,分別為:

  • 比特位為0的
1010 1011
  • 比特位為1的
1111 1100  1101 

可以看到,第一比特位為1的數為3個,比比特位為0的數要多,因此選擇比特位為0的數,繼續處理。且第二比特位,獲得0

2.處理第3比特位仍然被分為兩部分數據,分別為:

  • 比特位為0的

  • 比特位為1的
1010 1011

明顯看到第三比特位為0的數沒有,因此選擇比特位0,獲得0。至此,已經沒有必要繼續查找了。

我們最終得到了前三個比特位100,因此不存在於這些數中至少有1000,1001,即-8,-7。

代碼實現

C語言實現:

//binarySearch.c
#include <stdio.h>
#include <stdlib.h>

#define MAX_STR 10
#define SOURCE_FILE "source.txt" //最原始文件,需要保留
#define SRC_FILE "src.txt"       //需要分類的文件
#define BIT_1_FILE "bit1.txt"
#define BIT_0_FILE "bit0.txt"
#define INT_BIT_NUM  32
/*
FILE *src   源數據文件指針
FILE *fpBit1 存儲要處理的比特位為1的數據
FILE *fpBit0 存儲要處理的比特位為0的數據
int bit     要處理的比特位
返回值
0:選擇比特位為0的數據繼續處理
1:選擇比特位為1的數據繼續處理
-1:出錯
*/
int splitByBit(FILE *src,FILE *fpBit1,FILE *fpBit0,int bit,int *nums)
{
    /*入參檢查*/
    if(NULL == src || NULL == fpBit1 || NULL == fpBit0 || NULL == nums)
    {
        printf("input para is NULL");
        return -1;
    }
    /*bit位檢查*/
    if(bit < 0 || bit > INT_BIT_NUM )
    {
        printf("the bit is wrong");
        return -1;
    }
    char string[MAX_STR] = {0};
    int mask = 1<< bit;
    int bit0num = 0;
    int bit1num = 0;
    int num = 0;
    //printf("mask is %x\n",mask);
    /*循環讀取源數據*/
    while(fgets(string, MAX_STR, src ) != NULL)
    {
        num = atoi(string);
        //printf("%d&%d %d\n",num,mask, num&mask);
        /*根據比特位的值,將數據寫到不同的位置,注意優先級問題*/
        if(0 == (num&mask))
        {
            //printf("bit 0 %d\n",num);
            fprintf(fpBit0, "%d\n", num);
            bit0num++;
        }
        else
        {
            //printf("bit 1 %d\n",num);
            fprintf(fpBit1, "%d\n", num);
            bit1num++;
        }
    }
    //printf("bit0num:%d,bit1num:%d\n",bit0num,bit1num);
    if(bit0num > bit1num)
    {
        /*說明比特位為1的數少*/
        *nums = bit1num;
        return 1;
    }
    else
    {
        *nums = bit0num;
        return 0;
    }
}
/***
 *關閉所有文件描述符
 *
 * **/
void closeAllFile(FILE **src,FILE **bit0,FILE **bit1)
{
    if(NULL != src && NULL != *src)
    {
        fclose(*src);
        *src = NULL;
    }       
    if(NULL != bit1 && NULL != *bit1)
    {
        fclose(*bit1);
        *bit1 = NULL;
    }       
    if(NULL != bit0 && NULL != *bit0)
    {
        fclose(*bit0);
        *bit0 = NULL;
    }       
}
int findNum(int *findNum)
{
    int loop = 0;
    /*打開最原始文件*/
    FILE *src = fopen(SOURCE_FILE,"r");
    if(NULL == src)
    {
        printf("failed to open %s",SOURCE_FILE);
        return -1;
    }
    FILE *bit1 = NULL;
    FILE *bit0 = NULL;
       int num = 0;
    int bitNums = 0; //得到比特位的數字數量
    int findBit = 0; //當前得到的比特位
    for(loop = 0; loop < INT_BIT_NUM;loop++)
    {
        /*第一次循環不會打開,保留源文件*/
        if(NULL == src)
        {
            src = fopen(SRC_FILE,"r");
        }
        if(NULL == src)
        {
            return -1;
        }

        /**打開失敗時,注意關閉所有打開的文件描述符**/
        bit1 = fopen(BIT_1_FILE,"w+");
        if(NULL == bit1)
        {
            closeAllFile(&src,&bit1,&bit0);
            printf("failed to open %s",BIT_1_FILE);
            return -1;
        }
        bit0 = fopen(BIT_0_FILE,"w+");
        if(NULL == bit0)
        {
            closeAllFile(&src,&bit1,&bit0);
            printf("failed to open %s",BIT_0_FILE);
            return -1;
        }
        findBit = splitByBit(src,bit1,bit0,loop,&bitNums);
        if(-1 == findBit)
        {
            printf("process error\n");
            closeAllFile(&src,&bit1,&bit0);
            return -1;
        }
        closeAllFile(&src,&bit1,&bit0);
        //printf("find bit %d\n",findBit);
        /*將某比特位數量少的文件重命名為新的src.txt,以便進行下一次處理*/
        if(1 == findBit)
        {
            rename(BIT_1_FILE,SRC_FILE);
            num |=  (1 << loop);
            printf("mv bit1 file to src file\n");
        }
        else
        {
            printf("mv bit0 file to src file\n");
            rename(BIT_0_FILE,SRC_FILE);
        }

        /*如果某個文件數量為0,則沒有必要繼續尋找下去*/
        if(0 == bitNums)
        {
            printf("no need to continue\n");
            break;
        }
    }
    *findNum = num;
    return 0;
}
int main()
{
    int num = 0;
    findNum(&num);
    printf("final num is %d or 0x%x\n",num,num);
    return 0;
}

代碼說明:

  • 這里的splitByBit函數根據比特位將數據分為兩部分
  • closeAllFile用於關閉文件描述符
  • findNum函數循環32個比特位,每處理一次得到一個比特位,最終可以得到不存在其中的整數。

利用腳本產生了約2000萬個整數:

wc -l source.txt 
20000001 source.txt

編譯運行:

$ gcc -o binarySearch binarySearch.c
$ time ./binarySearch
final num is 18950401 or 0x1212901

real    0m8.001s
user    0m6.466s
sys    0m0.445s

程序的主要時間花在了讀寫文件,且占用內存極小。

總結

本文從一個特別的角度用最常見的二分搜索解決了該問題,最多拆分32次,便可從中找到不存在的整數。你有什么更好的思路或優化點,歡迎留言。

微信公眾號【編程珠璣】:專注但不限於分享計算機編程基礎,Linux,C語言,C++,Python,數據庫等編程相關[原創]技術文章,號內包含大量經典電子書和視頻學習資源。歡迎一起交流學習,一起修煉計算機“內功”,知其然,更知其所以然。


免責聲明!

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



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