1. 程式人生 > >[珠璣之櫝]位向量/點陣圖的定義和應用

[珠璣之櫝]位向量/點陣圖的定義和應用

  位向量/點陣圖是一個很有用的資料結構,在充分利用小空間儲存大量資料方面非常具有優勢,Linux核心中很多地方都是用了點陣圖。同時,它不但基礎,而且用到了很多程式語言的知識,以及對細節的把握,常常作為面試題出現。這裡將要介紹它的實現、操作、應用。

  與點陣圖(bitmap)比,我更傾向於用位向量(bit vector),前者比較容易與圖形學裡的名詞混淆,其實提到點陣圖,多指的是“是使用畫素陣列來表示的影象”(維基百科),為了避免這一點,下文中使用位向量。先來看看產生位向量的需求:

  一般地,對於多個物件和一個性質,這些物件可能滿足(true)也可能不滿足(false)這條性質。那麼,為了表示所有物件對這個性質的滿足情況,最容易想到的方式是分配一個int型變數(這裡不討論布林型,C++沒有對布林型佔用空間有明確規定;本文主要討論C)作為標誌,用1表示滿足條件,用0表示不滿足條件。同時為了方便查詢,把這些物件的標誌整合成一個數組。顯然,使用int來表示是0還是1有些太浪費空間了,即使把int改為char,浪費的情況也只是減輕了一部分,仍有很大的空間被浪費。考慮到計算機中最小的資料單位是非0即1的二進位制位,對於一個物件,使用一個二進位制位就足夠了。很多語言都具有位運算,利用位運算是可以完成本段開始處提出的要求的。當然,因為不能用一個變數名直接表示一個位,那麼可以將多個位組合成一個基本資料型別,通過對這個基本資料型別進行操作,達到使用位的方法。同時,為了方便,延續使用int陣列的做法,把這些由位組合成的基本資料型別也組成陣列。

  經過這樣分析,位向量的實現方法大體是:多個位組成一個基本資料型別,基本資料型別組合成陣列。根據這個思路就可以寫出位向量的表示了。在閱讀下面程式碼前,建議讀者嘗試自己獨立完成,這是一些提示:簡單起見,使用int作為位組成的基本資料型別,且int使用32位表示;int陣列中元素的個數如何計算?

#define N           10000000 //number of elements
#define BITPERWORD  32 //bits of int depends on machine
int a[(N-1)/BITPERWORD + 1]; //allocate space for bitmap 
//《程式設計珠璣》原書中是N/BITPERWORD + 1 //如果N恰為BITPERWORD的倍數,那麼又要浪費一個int的空間了 //N取0時,仍會浪費一個int的空間 //N非0非BITPERWORD的倍數時,最多是最後一個int不完全利用而已
位向量的表示

  寫完了表示,就需要為這個資料結構增加對應的操作了。對於每個位的操作,有三種:設定為0、設定為1、讀取當前值。根據上文位向量的表示,實現這三種操作。同樣建議讀者先嚐試獨立完成。以下程式碼參考自《程式設計珠璣》習題1.2。

#define SHIFT       5    //32 = 2^5
#define MASK        0x1F //
vaule 11111 in binary //i代表需要進行操作的第i個物件 //i>>SHIFT相當於i/32,將i定位到具體是哪個int中,即a[i>>SHIFT] //i&MASK相當於i%32 只保留i的0至4位,即i在int中的第幾位,然後把1左移這麼多位 //將a[i>>SHIFT]和(1<<(i&MASK))視需要進行操作 //同1做或運算或即為位設定 //同1取反再做與運算即為位清除 //同1做與,結果為0則原位為0,為1則原位為1 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));}
位向量操作

  使用位向量前不要忘記對所有位進行初始化:

for(i=0;i<N;i++)
    clr(i);

  當然,你也可以用這個或許更快的初始化方式:

int temp = N/BITPERWORD+1;
for (i=0; i< temp;i++) 
    a[i] = 0;

補充討論:

Q:為什麼要用看上去並不那麼直接的位運算而不是i/32和i%32?

A:為了速度而不是易讀性。你所寫下的最初版本如果使用的是/和%,當然沒有錯;當你著手於提高效率的時候,把它們改成移位運算就勢在必行了。雖然編譯器可能會對/和%在除數為2的冪時進行優化,寫成移位運算,你當然可以自己來完成這件事來保證確實優化了。

Q:是否可以將存放位向量的陣列作為引數?

A:當然可以,上文程式碼只是為了敘述方便。

Q:是否可以把這三個函式寫成巨集?

A:其實我曾經被面試過一道題,就是要求寫一個巨集,完成上面clr(i)的任務。寫成巨集當然沒問題,注意加好括號就行,具體的用法讓呼叫的人去擔心吧(笑)。

Q:位向量和位欄位/位域有什麼區別和聯絡?可以用位欄位來實現位向量嗎?

A:C語言允許將結構的整數成員存入比編譯器通常允許的更小的空間裡。然而它有三個風險:不同計算機中對齊限制不同;位欄位寬度限制不同;將位包裝成字的位元組順序不同。在位向量裡,雖然也有依賴於具體計算機特性的限制(需要預先知道int的位數),但是位的包裝是由程式設計師來控制的,也不必考慮對其限制。移植性略好於將成員宣告為1位整數位欄位的結構。

應用:

1.Linux中分配唯一pid的演算法、記憶體管理的夥伴分配系統等,詳細可以google,關鍵詞:linux+點陣圖。

2.一個最多包含n個正整數的檔案,每個數都小於n,其中n=107,並且沒有重複。最多有1MB記憶體可用。要求用最快方式將它們排序並按升序輸出。(《程式設計珠璣》第一章正文)方法是一次讀入檔案,把出現過的數字對應位置1;讀取完畢後從低位到高位輸出位向量為1的位所代表的數。

3.如果有用時間換空間的必要,可以將尋找1至某個數之間所有質數的埃氏篩法用位向量實現。原始版本的程式碼見於《程式設計珠璣(續)》習題1.2,演算法簡介:一個n位元的陣列,對應1至n-1,初值均為真。從2開始,每發現一個質數,就把這個陣列中所有這個數的倍數設為假。下一個質數就是陣列中下一個為真的位元。迴圈至所有數都被遍歷,即可得到該範圍內所有的質數。

引申和擴充套件:

1.(習題1.6)每個整數最多出現10次而不是1次;如果記憶體限制不變,又應該怎麼做?提示:多趟排序

(更一般的擴充套件:用幾個位表示一個物件的多個性質,也即一個物件不僅僅只佔用一位而是多位,但每個物件佔用的位數是相同的。重寫點陣圖。)

2.(習題1.9)對於稀疏的位向量,初始化很浪費時間。一般地,對於一個向量(不僅限於位向量),怎樣利用輔助資料結構,使得第一次訪問位向量的某一位時才將其初始化為0?

解法:

對於向量int data[N],分配輔助資料結構int from[N]和to[N],以及一個整數top,初始化top=0。

插入data[i]時,from[i]填入top,to[top] = i,top++;

這樣維持了一個性質:top代表下一個被插入的值的次序;from[i]代表了data[i]是第幾個被插入的;to[]中小於top的元素to[j]儲存了第j個被插入對應在data[]的下標。

若data[i]未初始化,則from[i]也未初始化,此時即使from[i]中未初始化的垃圾值<top,to[from[i]]!= i,仍能判斷出data[i]未初始化。

相關推薦

[珠璣]向量/點陣定義應用

  位向量/點陣圖是一個很有用的資料結構,在充分利用小空間儲存大量資料方面非常具有優勢,Linux核心中很多地方都是用了點陣圖。同時,它不但基礎,而且用到了很多程式語言的知識,以及對細節的把握,常常作為面試題出現。這裡將要介紹它的實現、操作、應用。   與點陣圖(bitmap)比,我更傾向於用位向量(bit

【數字影象】C++824BMP點陣的平滑、銳化、二值化處理,以及24真彩圖的灰度化

BMP標頭檔案: #ifndef BMP_H//前處理器 #define BMP_H typedef unsigned char BYTE; typedef unsigned short WORD; typedef unsigned int DWORD; typedef

珠璣”系列簡介與索引

系列博文主要目的:   收集《程式設計珠璣》和《程式設計珠璣(續)》(以下簡稱《續》)上的演算法和思想,幷包括了一些自己的思考和對相關問題的引申,以備複習和查用。 內容提要:   主要是演算法收集,結合了《程式設計實踐》 (Practise of Programming)、《程式設計精粹:編寫高質量C語

[珠璣]隨機數函式取樣與概率

  本節主要受到《程式設計珠璣》第12章隨機取樣問題的啟發,但不僅僅限於隨機取樣問題,進一步地,研究討論了一些在筆試面試中常見的和隨機函式以及概率相關的問題。   閱讀本文所需的知識:     1.對C語言中或其他語言中等價的rand()、srand()有所瞭解。本文不討論種子的設定和偽隨機數的問題;

[珠璣]字串序列:左移、雜湊、最長重複子序列的字尾陣列解法、最大連續子序列

  字串和陣列在儲存上是類似的,把它們歸為同一主題之下。本文主要介紹三大類問題和它們衍生的問題,以及相應演算法。   本文主要介紹和討論的問題和介紹的演算法(點選跳轉): 字串迴圈移位(左旋轉)問題 問題敘述:   將一個n元一維向量向左旋轉i個位置。例如,當n=8且i=3時,"abcde

[珠璣]二分思想與分治法、排序思想

#include <stdio.h> #include <assert.h> int BitCheck(int total,int n,int last) { FILE *input,*output0,*output1; char filename[10

[珠璣]淺談程式碼正確性:迴圈不變式、斷言、debug

  這個主題和程式碼的實際寫作有關,而且內容和用法相互交織,以下只是對於其內容的一個劃分。《程式設計珠璣》上只用了兩個章節20頁左右的篇幅介紹,如果希望能獲得更多的例項和技巧,我比較推崇《程式設計實踐》 (Practise of Programming)、《程式設計精粹:編寫高質量C語言程式碼》(Writin

[珠璣]估算的應用與Little定律

  估算的資料主要依賴於所能獲得的資料和常識,有時還包括實踐而不僅僅是理論。它常常作為一個大問題中的子問題,恰當地估算可以省去精確計算的時間和開銷。在計算機領域,所謂常識的內容很寬泛,比如硬碟的傳輸速度、CPU每秒能執行多少指令、各種資料結構的大小甚至每分鐘錄入的單詞數。有些資料是能夠從各種資料中查得的,但僅

建立WINCE下16格式點陣實現快速BitBlt

在WINCE下,如果建立的點陣圖與螢幕裝置格式不一樣,將會導致BitBlt函式極為緩慢,因此需要建立一幅16位色565格式點陣圖,用此方法創建出的點陣圖可以提供1ms的快速BitBlt繪製。當然,如果裝置不是565顏色格式的,更改程式碼中的顏色掩碼位bmiColors就可以了

Bitmap點陣取樣記憶體計算詳解

原文首發於微信公眾號:躬行之(jzman-blog) Android 開發中經常考慮的一個問題就是 OOM(Out Of Memory),也就是記憶體溢位,一方面大量載入圖片時有可能出現 OOM, 通過取樣壓縮圖片可避免 OOM,另一方面,如一張 1024 x 768 畫素的影象被縮略顯示在 128 x

Halcon學習二:攝像頭獲取相關參數

digg tail tours mage eight sta vision name pict 1、close_all_framegrabbers ( : : : ) 關閉所有圖像采集設備。 2、close_framegrabber ( : : AcqHand

- 定義術語總結

最終 col 一個 兩個 都是 spa pan 數據 pos 一、圖(Graph)是由頂點的有窮非空集合和頂點之間邊的集合組成,通常表示為:G(V,E),其中,G表示一個圖,V是圖G中頂點的集合,E是圖G中邊的集合。在圖中的數據元素,我們稱之為頂點(Vertex),頂點集合

iOS讓UISearchBar搜索placeholder靠左顯示

nsstring mode ace change 更改 ring val 搜索 class 系統UISearchBar效果圖: 需求效果圖: 兩種方案: 找到UISearchBar上的放大鏡圖標, 修改Frame. 同時判斷在有無文本內容更改placeholder的顏

大量資料去重:Bitmap點陣演算法布隆過濾器(Bloom Filter)

Bitmap演算法 與其說是演算法,不如說是一種緊湊的資料儲存結構。是用記憶體中連續的二進位制位(bit),用於對大量整型資料做去重和查詢。其實如果並非如此大量的資料,有很多排重方案可以使用,典型的就是雜湊表。 實際上,雜湊表為每一個可能出現的數字提供了一個一一對映的關係,每個元素都相當於有

Ruby學習模組(Module)的定義使用

Ruby中的模組(Module)是一種把方法、類和常量組合在一起的方式,它提供了一個名稱空間和避免名字衝突,並且實現了 mixin 裝置。它的名稱空間,相當於一個沙盒,在裡邊我們的方法和常量不會與其他地方的方法常量衝突,它和類很相似,但是它卻不能被例項化,也沒有子類,它只能被另一個模組定義。

sqlite3學習觸發器(Trigger)的定義使用

SQLite 觸發器(Trigger)是資料庫的回撥函式,它會在指定的資料庫事件發生時自動執行/呼叫,我們來看下關於 SQLite 的觸發器(Trigger)的要點: SQLite 的觸發器(Trigger)可以指定在特定的資料庫表發生 DELETE、INSERT 或 UPDATE

Spark元件GraphX學習7--隨機生成reduce最大或最小出度/入度/度

1解釋 通過自定義函式 reduce最大或最小出度/入度/度 2.程式碼: /** * @author xubo * ref http://spark.apache.org/docs/1.5.2/graphx-programming-guide.html *

陣列的定義應用

陣列的定義和應用 對於有些資料,只用簡單的資料型別是不夠的,難以反映出資料的特點,也難以有效地進行處理,因此就需要用到陣列。 1基本概念 陣列:具有一定順序關係的若干相同型別資料的集合,為構造資料型別之一。 陣列名:為該資料集合起的一個名字 。 陣列元素:組成陣列的資料。屬同一資料型別,用陣

Groovy 函式(方法)定義 應用

定義一個函式  def f(){} 最後一行的為返回值  不需要用return  使用this.&函式名,進行函式的引用  def f(){ 77 }  def g = this.&f  assert g() == 77  也可以不使用特別字元 直接使用 

Docker下ELK三部曲三:K8S上的ELK應用日誌上報

本章是《Docker下ELK三部曲》系列的終篇,前面章節已經詳述了ELK環境的搭建以及如何製作自動上報日誌的應用映象,今天我們把ELK和web應用釋出到K8S環境下,模擬多個後臺server同時上報日誌的場景; 前文連結 關於K8S 基礎結