java數據結構和算法08(B樹的簡單原理)

  這一篇首先會說說前面剩餘的一點知識2-3樹,而後簡單說說B樹,不寫代碼,只是簡單看看原理吧!算法

  爲何要說一下2-3樹呢?瞭解2-3樹以後能更快的瞭解B樹;數據庫

 

1.簡單看看2-3樹數組

  其實咱們學過了前面的2-3-4樹以後,再看2-3樹就太容易了,2-3樹中任意一個節點最多隻有三個子節點,並且節點中只有兩個空位置能夠存數據;除了分裂,其餘的都和2-3-4樹同樣的,就很少說了,下面咱們就隨意看看節點分裂吧!緩存

  首先要區分2-3-4樹和2-3樹分裂的的不一樣,對於2-3-4樹來講是插入數據以前首先會把滿的葉節點分裂,把三個數據分配完了以後再插入數據到節點中;而對於2-3樹來講,是在插入期間,什麼是插入期間呢?看看下圖:數據結構

  上圖中的操做的目的就是向2-3樹中插入85,插入的時候會判斷該葉節點是否是滿的,假如是滿的 ,首先就80、90、85進行從小到大排列 爲80、8五、90,而後80不動,中間的數據放進父節點中,最後將90放入新建立的節點當中,就ok了;這裏假如85在進入父節點的時候發現父節點滿了,那麼父節點就會分裂,這裏跟2-3-4樹差很少,重複上述步驟,左邊數據不動,將中間值放入父節點,右邊數據放入新建節點;提及來很繞,請看下圖:學習

  其實沒什麼新的東西,弄懂了2-3-4樹,再看2-3樹幾分鐘就差很少了,這裏也就是隨意看看,有興趣的能夠用代碼實現一下,這裏就是注意一下2-3樹和2-3-4樹分裂過程的不一樣就能夠了;spa

 

2.硬盤存儲數據操作系統

  咱們前面說的全部數據結構都是存在於內存中的,當電腦一關機內存就會所有釋放,全部的數據結構都會消失;可是有沒有想過硬盤中是怎麼存數據的啊?指針

  因而就有了B樹,屬於一種多叉樹,在外部存儲器存數據的時候起很大的做用,外部存儲暫時就理解爲硬盤便可!話說數據爲何要存到硬盤中呢?最大的有點就是硬盤便宜,並且硬盤空間比內存大得多,能夠存不少不少的數據,並且硬盤最大的優勢就是能夠持久化,就是電腦即便關機了,數據仍是存在硬盤中不會消失;blog

  可是存在硬盤中有個很大的問題,就是從硬盤中讀取數據的時候太慢太慢了,而從內存中讀取數據的速度大概比硬盤讀取快幾萬倍,相差一個數量級;其實對於cpu的運算速度來講從內存中讀數據仍是太慢了,因而就有了緩存,後面有機會再說......

  雖然每一年硬盤技術都在提升,可是內存技術提升的更快,能夠想象內存和硬盤的速度只會愈來愈大!

  2.1找到硬盤中數據的正確位置

  假設咱們要存一個城市的電話記錄,大概50萬條數據,每條數據512個字節,那麼總共應該是50萬x512=2億5千6百萬  字節,差很少就是256M,這個確定不能存在內存中,要想辦法把這256M的數據存到硬盤中還要保證咱們從硬盤中查找,插入和刪除指定記錄的速度要足夠快才行,否則用戶體驗太差了。。。

  咱們知道計算機想要讀取硬盤中的數據是經過驅動(其實就是磁盤驅動器)去對硬盤進行操做,硬盤內部以下圖所示,其實最終就是經過磁頭對盤片進行操做,可是怎麼操做呢?咱們能夠把盤片看做打靶的那個靶子同樣有不少個圈,磁頭首先要找到目標數據所在的圈(也叫作磁道)須要幾毫秒,而後盤片須要旋轉一下磁頭才能在當前磁道中找到正確的位置(平均下來是要旋轉半圈)須要幾毫秒,找到正確的位置後,最後就是實際的讀寫操做了,差很少也須要幾毫秒;假設硬盤這裏的全部操做共須要10毫秒(10-3),而假如從內存中訪問正確的數據則只須要幾微秒(10-9),能夠看到速度相差了好多好多倍;

 

  那確定有人要問了,既然內存這麼快那幹嗎不直接用內存條當硬盤來用呢?emmm....最主要的緣由就是內存條很貴啊,你能夠去淘寶或者京東查查幾個G的內存條多少RMB,至少好幾百,咱們電腦存儲量至少也要五六百個G吧,因而你買內存條就能夠破產了;可是硬盤的話1T也就一兩百塊,價格纔是最主要的。

  2.2.讀取數據塊

  磁頭在硬盤的盤片中找到數據的正確位置了以後,難道要一條一條數據慢慢讀麼?固然不會用這麼愚蠢的方法,咱們能夠把盤片中的數據分紅一塊一塊的,須要的時候直接讀取一塊數據到內存中的緩存區,一般塊的大小和操做系統和磁盤驅動器的容量相關,並且必須是2的倍數,假設這裏咱們把塊的大小設置爲8192字節(213),那麼上面那兩億多個字節的數據就變成31250塊;

  塊分完以後,假設咱們要讀取100字節的數據,那麼磁盤驅動器首先直接讀取一塊數據,而後將這塊數據前100字節留下其餘的都扔了;假如是讀取8292字節,那麼就會讀取兩塊數據,而後將第二塊數據留下100字節就將第二塊數據其餘的都扔了;

  順便一說,上面說了一條數據512字節,一塊數據是8192字節,能夠知道一塊數據其實就保存有16條記錄,因此咱們一次讀取16條數據效率是最高的,不須要對數據進行丟棄操做;

  2.3.硬盤中數據有序

  咱們存到硬盤中的數據,能夠是有序和無序的;

  假如硬盤中的數據是無序的,那麼插入確定是很快的,可是查詢就比較坑了,由於硬盤中這麼多數據要慢慢的進行遍歷,那就只能慢慢等等了。。。

  假如硬盤中的數據是有序排列的,那麼咱們去查找一條記錄的時候就會很快,能夠用二分法查找,到底有多快呢?假如你要從50萬條數據中查找某條數據,最多須要查找19次,若是一次10微秒,那總共190微秒,比咱們眨一下眼睛的時間還短;

  二分法其實很容易的一個東西,舉個例子一個數組中有順序的數據0、一、二、三、四、五、六、七、八、九、10,咱們要查找9所在的位置!假如咱們用遍歷那就須要10次;若是用二分法,首先9和中間的5比較,比5大,那就再和右半部分中間的8比較,比8大,繼續在右邊查找,能夠找到9,只須要三次操做,數據量越大二分法的效果越明顯;可是二分法也有缺陷就是必需要讓數據有序,這就致使插入(或刪除)的時候比較坑爹,就相似有序數組的插入,插入一個數據以後就要將這個數據後面的全部都日後移動一個位置,並且數據越多插入的效率越糟糕;

  回到硬盤中數據的存儲,假如咱們將硬盤中的數據弄成有序的,分塊完了以後(後面操做是以塊爲單位),查找能夠用二分查找先找到某塊數據,讀取到磁盤驅動器的緩存中,而後裏面就16條數據很快就能夠找到;可是插入數據的話平均要移動一半的塊,每移動一個塊都須要一次硬盤的讀寫操做(就是都要通過2.1到2.2這個步驟而後把數據寫入硬盤,賊坑!),下面就隨意說說一次讀寫究竟是怎麼作的;

  假如咱們如今要插入一條記錄A,第一步:首先會通過2.1和2.2讀取一塊數據並存在緩存中,將這塊數據最後一條記錄保存下來,而後判斷記錄A能夠放在這塊數據的哪裏,適當移動這塊數據中記錄的位置並插入數據A,而後就把緩存區中的這塊數據寫入磁盤中;第二步:讀取下一塊數據,也是保存這塊數據的最後一條記錄,將這塊數據中的記錄都日後移動一個空位置,讓上一塊數據中保存的最後的一條記錄插入到這塊的最開始的位置,而後將本塊數據寫入磁盤;這個第二步會一直重複,直到將全部記錄都從新寫入。。。

  咱們上面數據分塊是31250塊,假如每次讀寫都要10毫秒,那麼咱們插入一條記錄差很少須要5分鐘,真是足夠坑爹!

 

3.簡單看看B樹

  根據上面咱們能夠知道數據存到硬盤中是分爲兩種狀況的,可是兩種狀況各有優缺點,那有沒有汲兩者優勢的存儲方式呢?聰明的大佬們早就想出來了,這就像數組和鏈表的關係,最終咱們引入了樹的概念完美解決了數組和鏈表的缺陷,相似的如今咱們在硬盤中也要引入一種樹來解決,這種樹就是B樹;

  B樹是一種多叉樹,也有人稱爲B-樹,其實就是一種樹!有點相似2-3-4樹,只是B樹每一個節點都有不少個節點,那到底能夠是多少個呢?咱們慢慢看,補充一點,前面咱們學習過的2-3樹和2-3-4樹都只是B樹的兩種特殊狀況,若是對這兩種樹不熟悉的必定要先去看看;

  根據前面的2-3-4樹能夠知道一個非葉節點的子節點數目 = 節點數據項+1,在B樹中也是這樣;還有,既然都說了2-3樹和2-3-4樹都只是B樹的特殊狀況,那就能夠猜到B樹的節點中的數據估計有不少個,咱們該怎麼選取節點纔是最高效的呢?還記得前面說的分塊嗎,咱們這裏就是將一塊數據做爲一個節點;

  咱們再回顧一下上面說的城市的電話記錄的例子,總共有2億5千6百萬字節,一條記錄512字節,根據每一塊數據8192字節(16條記錄)進行分塊,能夠分爲31250塊,換句話說每塊數據中存有16條數據,這就有點意思了,咱們把每16條記錄看做B樹的一個節點的數據項,那麼就應該有17個子節點纔對;咱們還知道每個節點要保存子節點的引用,怎麼作比較好呢?比較奢侈的作法是:讓每一個節點只保存15條記錄,還有一條記錄大小的空間用於存放子節點和父節點的引用吧!此時只有16個子節點;可是比較高效的作法是:一個節點中最好存偶數個數據,而後適當縮小每條記錄的大小爲507個字節,那麼每一塊中16條數據會佔用8112字節,還剩下80字節,咱們有17個子節點,每一個子節點引用時int類型(一個int數據佔4個字節)的數據,17x4=68 < 80,說明節點空間中即便保存子節點引用仍是夠用,下圖所示:

  下面咱們簡單說說B樹的一些基本操做,不用代碼來表示的,瞭解原理便可;

  3.1 查找

  這個查找的操做和2-3-4樹差很少,就是子節點多了不少而已,很容易!首先將根節點數據讀到內存中,而後根據搜索算法對這個節點進行搜索,從0開始,其實就是比較在哪一個範圍下,而後繼續到對應的子節點那裏去找。。。重複這個步驟就能找到目的數據;

  3.2 插入

  插入這個操做就跟2-3樹差很少了,爲何不用2-3-4樹那樣的插入方式呢?由於咱們能夠知道2-3-4樹中的節點不少都是沒有放滿的,有不少節點只存了一個數據,有太多空位置,假如B樹用這樣的方式硬盤中會很浪費空間,而用相似2-3樹這種方式利用率就比較高,咱們能夠看看B樹中是怎麼分裂的;

·  在插入數據的時候,假設該節點已經滿了,咱們還要向其中插入一個數據下圖所示;

 

  咱們將70和節點中的數據進行從小到大排列,而後以中間數據60爲界限,左邊的數據不動,中間數據60放入父節點(根節點比較特殊,要新建一個父節點),右邊的數據放入新建的節點:

 

  繼續插入1八、30、15,過程以下,比較相似2-3樹的分裂,沒什麼特別很差理解的,只是子節點比較多而已

  

  最後看一個比較複雜的節點分裂,其實跟前面差很少。。。

 

  能夠簡單看到B樹中,除了根節點以外,其餘的節點最低也會用一半的空間,利用率最低也是50%,這就已經很能夠了;

 

4.B樹效率

  B樹的效率如何呢?空間利用率還行,咱們仍是以最開始的那個城市電話記錄爲例子簡單說說,總共有50萬條記錄以B樹的形式存在硬盤中,每一個節點至少也是半滿,咱們就以每一個節點裝滿一半數據計算,能夠算出樹的高度大概爲6,每一個節點有8條記錄(對應9個子節點);

  至於到底怎麼計算的,感受沒什麼好說的,B樹節點存數據的大小(或者說的塊數據)是必定的,跟操做系統等因素有關,咱們這裏爲16,每一個節點存一半就是存8條數據,擁有9個子節點,是在不行用計算器算一下樹的高度最低要爲6才能存滿50萬數據,雖然我是對於這種計算的東西沒多大興趣。。。

  因爲樹只有6層,那麼咱們查找任意一條數據也就須要6次比較而已,假設每次爲10毫秒,那麼最多就花費60毫秒,6/100秒;你想一想從50萬條數據中任意查找一條記錄最可能是6/100秒,這個效率已經很ok了,雖然查找效率已經很不錯了,可是插入和刪除操做才能顯示出B樹的最大優越性;

   就好比說插入,假如插入數據到葉節點中,該葉節點沒有滿,那就不須要進行分裂,也就須要對硬盤7次操做,前六次是比較使得找到正確節點,第7次就是讀取葉節點數據到緩存,插入數據而後寫入硬盤;假如插入數據以後葉節點要進行分裂的話,找到葉節點、移動節點中的數據和建立新的節點等操做合在一塊兒也就須要對硬盤的12步操做,這效率很厲害了,不能再多說了,這篇已經足夠長了。。。

 

5.總結

  B樹其實也就這樣吧,多是沒有仔細實現B樹代碼,可是感受還行,最複雜的仍是分裂這裏,不過也就和2-3-4樹差不了多少!

  其實B樹有不少種,咱們常說的B樹和B-樹是同樣的,還有B+樹、B*樹;其中B+樹是對B-樹的一個改進(多應用於操做系統索引和數據庫索引),B*樹又是對B+樹的一個改進,瞭解了B-樹以後再看後面這兩種樹很容易的,無非是增長几個指針,提升了節點利用率什麼的,後面有時間再說吧!話說還有個R樹,emmm....須要的時候再去看吧!

相關文章
相關標籤/搜索