【資料結構與演算法】bitmap

徘徊彼岸花發表於2024-07-18

bitmap


點陣圖法,用每個bit位儲存狀態(如0/1),用於判斷某個資料是否存在。適用於資料量很大,但狀態不多的情況。

STL中的bitset就是點陣圖法。

(1)bitmap原理及實現

#pragma warning(disable : 4996 4800)
#include <stdlib.h>
#include <stdio.h>
#include <memory.h>
#include "math.h"
class Bitmap { //點陣圖bitmap類
private:
	char* M; int N; //位元圖所存放的空間M[],容量為N*sizeof(char)*8位元
protected:
	void init(int n)   //初始化點陣圖空間
	{
		M = new char[N = (n + 7) / 8];   //申請記憶體
		memset(M, 0, N);    //初始化記憶體塊
	}

public:
	Bitmap(int n = 8)  //按指定或預設規模建立位元圖(為測試暫時選用較小的預設值)
    { 
        init(n); 
    }
	Bitmap(char* file, int n = 8) //按指定或預設規模,從指定檔案中讀取位元圖
	{
	    init(n); 
        FILE* fp = fopen(file, "r"); 
        fread(M, sizeof(char), N, fp); 
        fclose(fp);
	}
	~Bitmap() //析構時釋放位元圖空間
    { 
        delete[] M; M = NULL; 
    } 

    //置位第k個標誌位
	void set(int k)   
	{
		expand(k);   //拓容        
		M[k >> 3] |= (0x80 >> (k & 0x07));  //M[第k個標誌位所在的位元組(k/8 取整)] |= (第k個標誌位在所在位元組中的位數(取餘)) 
	}

    //復位第k個標誌位
	void clear(int k) 
    {
		expand(k);    //拓容 
		M[k >> 3] &= ~(0x80 >> (k & 0x07)); //M[第k個標誌位所在的位元組(k/8 取整)] &= ~(第k個標誌位在所在位元組中的位數(取餘))
	}

    //取出指定位元組中的指定位
	bool test(int k) 
    {
		expand(k);    //拓容 
		return M[k >> 3] & (0x80 >> (k & 0x07));  //M[第k個標誌位所在的位元組(k/8 取整)] &(第k個標誌位在所在位元組中的位的值)
	}

    //將點陣圖整體匯出至指定的檔案,以便對此後的新點陣圖批次初始化
	void dump(char* file) 
	{
		FILE* fp = fopen(file, "w"); fwrite(M, sizeof(char), N, fp); fclose(fp);
	}

    //將前n位轉換為字串
	char* bits2string(int n)
	{ 
		expand(n - 1); //此時可能被訪問的最高位為bitmap[n - 1]
		char* s = new char[n + 1]; s[n] = '\0'; //字串所佔空間,由上層呼叫者負責釋放
		for (int i = 0; i < n; i++) s[i] = test(i) ? '1' : '0';
		return s; //返回字串位置
	}

    //若被訪問的bitmap[k]已出界,則需擴容
	void expand(int k)
	{ 
		if (k < 8 * N) return; //仍在界內,無需擴容
		int oldN = N; char* oldM = M;
		init(2 * k); //與向量類似,加倍策略
		memcpy_s(M, N, oldM, oldN); delete[] oldM; //原資料轉移至新空間
	}

    //逐位列印以檢驗點陣圖內容,非必需介面
	void print(int n) 
	{
		expand(n);
		for (int i = 0; i < n; i++)
			printf(test(i) ? "1" : "0");
	}

    //判斷某個數是否為素數
	static bool isPrime(int n) {  
		if (n <= 3) {
			return n > 1;
		}
		// 不在6的倍數兩側的一定不是質數
		if (n % 6 != 1 && n % 6 != 5) {
			return false;
		}
		int s = (int)sqrt(n);
		for (int i = 5; i <= s; i += 6) {
			if (n % i == 0 || n % (i + 2) == 0) {
				return false;
			}
		}
		return true;
	}
};

(2)std::vector<bool>

bitset效率極低,做不了bitmap。

vector<bool>在cpp中不是儲存bool的vector,而是被標準庫特化為了位元,極大節省了空間且效率極高。

(3)應用場景

  • 有500萬個數字,數字分佈在範圍1000w~1500w內,要求排序且複雜度為O(N),查詢數字且複雜度為O(1)

    #include <iostream>
    #include <stdlib.h>
    #include <vector>
    #include <random>
    #include <string>
    
    #define MIN 10000000	//資料下限
    #define MAX 14999999	//資料上限(右閉區間)
    #define COUNT 5000000	//資料個數
    
    int main()
    {
        using namespace std;
        std::random_device rd;  //如果可用的話,從一個隨機數發生器上獲得一個真正的隨機數
        std::mt19937 gen(rd()); //gen是一個使用rd()作種子初始化的標準梅森旋轉演算法的隨機數發生器
        std::uniform_int_distribution<> distrib(MIN, MAX);
    
        vector<bool> bitmap(MAX - MIN + 1, false);	//全部置0
        printf("capacity=%d,size=%d\n", bitmap.capacity(), bitmap.size());
    
        for (int i = 0; i < COUNT; i++)
        {
            int index = distrib(gen) - MIN;	//原始資料要減去MIN對映到0~MAX-MIN的區間上
            bitmap[index] = true;	
        }
    
        while (true)
        {
            cout << "input:" << endl;
            int a;
            cin >> a;
            if (a<MIN || a>MAX)
            {
                cout << to_string(a) << " is not existed..\n";
                continue;
            }
    
            if (bitmap[a - MIN])
                cout << to_string(a)<< "is existed..\n";
            else
                cout << to_string(a)<< "is not existed..\n";
        }
    
        /*int count = 0;
        for(auto item:bitmap)
        { 
            if (++count == 100)
            {
                cout << endl;
                count = 0;
            }
    
            if (item)
                printf("1");
            else
                printf("0");
    
        }
        cout << endl;
        printf("capacity=%d,size=%d\n", bitmap.capacity(), bitmap.size());*/
    
        return 0;
    }
    
    #include <iostream>
    #include <stdlib.h>
    #include <vector>
    #include <random>
    #include <string>
    #include "Bitmap.h"
    
    #define MIN 10000000
    #define MAX 14999999
    #define COUNT 5000000
    
    int main()
    {
        using namespace std;
        std::random_device rd;  //如果可用的話,從一個隨機數發生器上獲得一個真正的隨機數
        std::mt19937 gen(rd()); //gen是一個使用rd()作種子初始化的標準梅森旋轉演算法的隨機數發生器
        std::uniform_int_distribution<> distrib(MIN, MAX);
    
        Bitmap bitmap(COUNT);
    
        for (int i = 0; i < COUNT; i++)
        {
            int index = distrib(gen) - MIN;
            bitmap.set(index);
        }
    
    
        for (int i = 0; i < (MAX - MIN); i++)
        {
            if (bitmap.test(i))
            {
                cout << "min=" << i + MIN << endl;
                break;
            }
        }
    
        for (int i = MAX - MIN - 1; i >= 0; i--)
        {
            if (bitmap.test(i))
            {
                cout << "max=" << i + MIN << endl;
                break;
            }
        }
    
        //bitmap.print(COUNT);
    
        while (true)
        {
            cout << "input:" << endl;
            int a;
            cin >> a;
            if (a<MIN || a>MAX)
            {
                cout << to_string(a) << " is not existed..\n";
                continue;
            }
    
            if (bitmap.test(a-MIN))
                cout << to_string(a)<< "is existed..\n";
            else
                cout << to_string(a)<< "is not existed..\n";
        }
    
        return 0;
    }
    
  • 40億個非負整數中算中位數和找出現兩次的數

相關文章