初識BitMap

故事從這里開始~~~

先看一個簡單的問題:有n個不小于0的整數(shù),現(xiàn)在要設(shè)計一個類腿椎,用數(shù)組存儲數(shù)據(jù)优烧,并提供兩個方法add和isExist,用來添加數(shù)據(jù)和判斷數(shù)據(jù)是否存在

相信我們不用思考就寫出來了

function Test () {
  const data = [];

  this.add = (num) => {
    data.push(num);
  }

  this.isExist = (num) => {
    return data.indexOf(num) >= 0;
  }
}

這個實現(xiàn)雖然滿足要求庸娱,但是速度慢啊着绊,時間復(fù)雜度是O(n), 隨著加入數(shù)據(jù)的增多,查找時間也會越來越長熟尉。

有什么辦法可以降低查找時間的呢归露?我們是判斷這個數(shù)據(jù)存不存在框仔,如果把加入的數(shù)據(jù)作為數(shù)組下標(biāo)岩齿,對應(yīng)的值設(shè)置為1,這樣查找的時候
只需要判斷data[num]是不是等于1就行了,這樣的話不管有多少數(shù)據(jù)诚欠,只需一次就可以判斷出該數(shù)據(jù)是否存在了磷雇。

function Test (size) {
  const data = [];

  for(let i = 0; i < size; i++) {
    data[i] = 0;
  }

  this.add = (num) => {
    data[num] = 1;
  }

  this.isExist = (num) => {
    return data[num] === 1;
  }
}

上面這個方法雖然查找時間快了雳窟,但是如果有上億個數(shù)據(jù)表窘,那得占多少內(nèi)存啊。js中一個Number類型的整數(shù)占8個字節(jié)堕油,1024字節(jié)是1kb潘飘,
1024kb是1M, 假設(shè)一億個整數(shù)就占762M的內(nèi)存空間。沒有這么多內(nèi)存咋辦呢掉缺,bitmap就登場了~

bitmap是一種數(shù)據(jù)壓縮的算法卜录,用二進(jìn)制位來存儲數(shù)據(jù)

在正式進(jìn)入bitmap之前先看幾個位運算

位運算

按位與 &

參加運算的兩個運算數(shù)按二進(jìn)制位進(jìn)行“與”運算攀圈。如果相同二進(jìn)制位的數(shù)字都是1暴凑,則結(jié)果為1, 否則是0.

二進(jìn)制   整數(shù)
010      2
011      3

010      2

即2 & 3 = 2
按位或 |

參加運算的兩個運算數(shù)按二進(jìn)制位進(jìn)行“或”運算赘来。如果相同二進(jìn)制位的數(shù)字有一個是1现喳,則結(jié)果為1,否則是0.

二進(jìn)制   整數(shù)
010      2
011      3

011      3

即2 | 3 = 3
左移 <<

將一個運算對象的二進(jìn)制向左移動n位犬辰,左邊的二進(jìn)制位丟棄嗦篱,右邊補(bǔ)0

2<<2:
二進(jìn)制   整數(shù)
10      2
1000    8
異或 ^

參加運算的兩個運算數(shù),按二進(jìn)制進(jìn)行“異或”運算幌缝。相應(yīng)二進(jìn)制位不同灸促,結(jié)果為1,否則為0.

0^0=0; 0^1=1; 1^0=1; 1^1=0;

二進(jìn)制   整數(shù)
010      2
011      3

001      1

即2^3 = 1;

BitMap

現(xiàn)在我們以一種新的方式實現(xiàn)前面的功能涵卵。

前面我們用數(shù)據(jù)做下標(biāo)浴栽,用0和1來區(qū)分該值是否存在。0和1正好是二進(jìn)制數(shù)轿偎,現(xiàn)在我們?nèi)杂?和1來表示數(shù)據(jù)存在與否典鸡,但是存入的數(shù)據(jù)不作為數(shù)組的下標(biāo)了,
而是二進(jìn)制數(shù)的第幾位坏晦。

比如我們新加了個0萝玷,就把二進(jìn)制的第一位設(shè)置為1:

00000000 00000001

再次添加了3,就把二進(jìn)制的第三位設(shè)置為1:

00000000 00000101

又添加了8昆婿,就把二進(jìn)制的第八位設(shè)置為1:

00000000 10000101

判斷該值是否存在的時候球碉,就判斷該二進(jìn)制位上是不是1.

由于JS中一個Number類型的數(shù)是64bit,那么
data[0] 可以表示0~63是否存在
data[1] 可以表示64~127是否存在
...

這樣的話占用空間將大大降低仓蛆。

首先我們先明確兩個值:

const arrIndex = Math.floor(num / 64); // 確定num在數(shù)組中的索引
const bitIndex = num % 64; // 確定在哪個二進(jìn)制位上

實現(xiàn)方法:

function BitMap(size) {
  const data = [];
  for(let i = 0; i < size; i++) {
    data[i] = 0;
  }

  this.add = (num) => {
    const arrIndex = Math.floor(num / 64); // 確定num在數(shù)組中的索引
    const bitIndex = num % 64; // 確定在哪個二進(jìn)制位上

    data[arrIndex] = data[arrIndex] | 1<<bitIndex // 把相應(yīng)位置上的二進(jìn)制數(shù)置為1
  }

  this.isExist = (num) => {
    const arrIndex = Math.floor(num / 64); // 確定num在數(shù)組中的索引
    const bitIndex = num % 64; // 確定在哪個二進(jìn)制位上

    return !!(data[arrIndex] & 1<<bitIndex) // 相應(yīng)位置上的二進(jìn)制數(shù)是否為1睁冬,為1則表示該數(shù)存在
  }
}

現(xiàn)在這種數(shù)據(jù)結(jié)構(gòu)基于位做映射,能夠用很少的內(nèi)存存儲數(shù)據(jù)多律,和數(shù)組不同痴突,它只能存儲表示某個數(shù)是否存在搂蜓,可以用于大數(shù)據(jù)去重狼荞、大數(shù)據(jù)排序辽装、兩個集合取交集等。

BitMap在處理大數(shù)據(jù)時才有優(yōu)勢相味,而且要求數(shù)據(jù)集緊湊拾积,如果要處理的數(shù)只有三個,1丰涉,1000拓巧,100000,那空間利用率就太低了一死,最大的值決定了BitMap要用多少內(nèi)存肛度。

練習(xí)題

練習(xí)一 、大數(shù)據(jù)排序

有多達(dá)10億無序整數(shù)投慈,已知最大值為15億承耿,請對這10億個數(shù)進(jìn)行排序

思路分析:

通過兩次遍歷,第一次先把所有數(shù)據(jù)存到BitMap中伪煤,第二次從0到最大值進(jìn)行遍歷加袋,如果在BitMap中,則輸出該值抱既。

為了方便职烧,使用一個小數(shù)組進(jìn)行編碼, 最大值99:

const arr = [0, 6, 88, 7, 73, 34, 10, 99, 22];
const sortArr = [];
const bitmap = new BitMap(2);

for(const num of arr) {
  bitmap.add(num);
}
for(let i = 0; i <= 99; i++) {
  if (bitmap.isExist(i)) {
    sortArr.push(i);
  }
}
console.log(sortArr);
練習(xí)二、 兩個集合取交集

兩個數(shù)組防泵,內(nèi)容分別為[1, 4, 6, 8, 9, 10, 15], [6, 14, 9, 2, 0, 7]蚀之,用BitMap計算他們的交集

思路分析:

先把其中一個數(shù)組存入BitMap中,然后遍歷另一個數(shù)組捷泞,如果存在于BitMap中足删,則說明這個數(shù)是重復(fù)的

const arr1 = [1, 4, 6, 8, 9, 10, 15];
const arr2 = [6, 14, 9, 2, 0, 7];
const intersectionArr = [];

const bitmap = new BitMap(1);

for(const num of arr1) {
  bitmap.add(num);
}
for(const num of arr2) {
  if (bitmap.isExist(num)) {
    intersectionArr.push(num);
  }
}
console.log(intersectionArr);
練習(xí)三、 支持負(fù)數(shù)

前面的BitMap類只能存儲大于等于0的整數(shù)肚邢,現(xiàn)在要改造BitMap類壹堰,不論正數(shù)還是負(fù)數(shù),都可以存儲并判斷是否存在

思路分析:

可以用兩個數(shù)組來存儲骡湖,一個存儲大于等于0的整數(shù)贱纠,一個存儲小于0的整數(shù)

function SuperBitMap(size) {
  const positiveBitMap = new BitMap(size); // 存儲大于等于0的整數(shù)
  const negativeBitMap = new BitMap(size); // 存儲小于0的整數(shù)

  this.add = (num) => {
    if (num >= 0) {
      positiveBitMap.add(num);
    } else {
      negativeBitMap.add(num);
    }
  }

  this.isExist = (num) => {
    if (num >= 0) {
      return positiveBitMap.isExist(num);
    }
    return negativeBitMap.isExist(num);
  }
}
練習(xí)四、 查找不重復(fù)的數(shù)

有一個數(shù)組响蕴,內(nèi)容為[1, 3, 4, 5, 7, 4, 8, 9, 2, 9]谆焊,有些數(shù)值是重復(fù)出現(xiàn)的,請你改造BitMap類浦夷,增加一個isRepeat(member)方法辖试,判斷member是否重復(fù)出現(xiàn)辜王,并利用該方法找出數(shù)組中不重復(fù)的數(shù)

思路分析:

一個bit位有兩個狀態(tài),表示一個數(shù)是否存在」扌ⅲ現(xiàn)在我們可以用相鄰的兩個bit位來表示一個數(shù)是否存在以及是否重復(fù)呐馆。這樣64個bit位可以表示32個數(shù)的存在以及重復(fù)狀態(tài)。

比如莲兢,一開始添加一個0汹来,把第一個位置置為1,表示0存在了改艇。
00000000 00000001
然后又添加了一個0收班,就把第二個位置置為1,表示0是重復(fù)的谒兄。
00000000 00000011

這樣摔桦,判斷0是否存在的時候,判斷第一個位置是否為1承疲;判斷0是否重復(fù)的時候邻耕,判斷第二個位置是否為1.

function BitMap(size) {
  const data = [];
  for(let i = 0; i < size; i++) {
    data[i] = 0;
  }

  this.add = (num) => {
    const arrIndex = Math.floor(num / 32);
    const bitIndex = num % 32;

    if (!this.isExist(num)) {
      data[arrIndex] = data[arrIndex] | 1 << (bitIndex * 2);
    } else {
      data[arrIndex] = data[arrIndex] | 1 << (bitIndex * 2 + 1);
    }    
  }

  this.isExist = (num) => {
    const arrIndex = Math.floor(num / 32);
    const bitIndex = num % 32;

    return !!(data[arrIndex] & 1 << (bitIndex * 2));
  }

  this.isRepeat = (num) => {
    const arrIndex = Math.floor(num / 32);
    const bitIndex = num % 32;

    return !!(data[arrIndex] & 1 << (bitIndex * 2 + 1));
  }
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市纪隙,隨后出現(xiàn)的幾起案子赊豌,更是在濱河造成了極大的恐慌,老刑警劉巖绵咱,帶你破解...
    沈念sama閱讀 212,454評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件碘饼,死亡現(xiàn)場離奇詭異,居然都是意外死亡悲伶,警方通過查閱死者的電腦和手機(jī)艾恼,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,553評論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來麸锉,“玉大人钠绍,你說我怎么就攤上這事』ǔ粒” “怎么了柳爽?”我有些...
    開封第一講書人閱讀 157,921評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長碱屁。 經(jīng)常有香客問我磷脯,道長,這世上最難降的妖魔是什么娩脾? 我笑而不...
    開封第一講書人閱讀 56,648評論 1 284
  • 正文 為了忘掉前任赵誓,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘俩功。我一直安慰自己幻枉,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 65,770評論 6 386
  • 文/花漫 我一把揭開白布诡蜓。 她就那樣靜靜地躺著熬甫,像睡著了一般。 火紅的嫁衣襯著肌膚如雪万牺。 梳的紋絲不亂的頭發(fā)上罗珍,一...
    開封第一講書人閱讀 49,950評論 1 291
  • 那天洽腺,我揣著相機(jī)與錄音脚粟,去河邊找鬼。 笑死蘸朋,一個胖子當(dāng)著我的面吹牛核无,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播藕坯,決...
    沈念sama閱讀 39,090評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼团南,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了炼彪?” 一聲冷哼從身側(cè)響起吐根,我...
    開封第一講書人閱讀 37,817評論 0 268
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎辐马,沒想到半個月后拷橘,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,275評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡喜爷,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,592評論 2 327
  • 正文 我和宋清朗相戀三年冗疮,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片檩帐。...
    茶點故事閱讀 38,724評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡术幔,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出湃密,到底是詐尸還是另有隱情诅挑,我是刑警寧澤,帶...
    沈念sama閱讀 34,409評論 4 333
  • 正文 年R本政府宣布泛源,位于F島的核電站拔妥,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏俩由。R本人自食惡果不足惜毒嫡,卻給世界環(huán)境...
    茶點故事閱讀 40,052評論 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧兜畸,春花似錦努释、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,815評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至肛鹏,卻和暖如春逸邦,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背在扰。 一陣腳步聲響...
    開封第一講書人閱讀 32,043評論 1 266
  • 我被黑心中介騙來泰國打工缕减, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人芒珠。 一個月前我還...
    沈念sama閱讀 46,503評論 2 361
  • 正文 我出身青樓桥狡,卻偏偏與公主長得像,于是被迫代替她去往敵國和親皱卓。 傳聞我的和親對象是個殘疾皇子裹芝,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,627評論 2 350

推薦閱讀更多精彩內(nèi)容