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、85、90,然后80不動,中間的數據放進父節點中,最后將90放入新創建的節點當中,就ok了;這里假如85在進入父節點的時候發現父節點滿了,那么父節點就會分裂,這里跟2-3-4樹差不多,重復上述步驟,左邊數據不動,將中間值放入父節點,右邊數據放入新建節點;說起來很繞,請看下圖:

  其實沒什么新的東西,弄懂了2-3-4樹,再看2-3樹幾分鍾就差不多了,這里也就是隨意看看,有興趣的可以用代碼實現一下,這里就是注意一下2-3樹和2-3-4樹分裂過程的不同就可以了;

 

2.硬盤存儲數據

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

  於是就有了B樹,屬於一種多叉樹,在外部存儲器存數據的時候起很大的作用,外部存儲暫時就理解為硬盤即可!話說數據為什么要存到硬盤中呢?最大的有點就是硬盤便宜,而且硬盤空間比內存大得多,可以存很多很多的數據,而且硬盤最大的優點就是可以持久化,就是電腦即使關機了,數據還是存在硬盤中不會消失;

  但是存在硬盤中有個很大的問題,就是從硬盤中讀取數據的時候太慢太慢了,而從內存中讀取數據的速度大概比硬盤讀取快幾萬倍,相差一個數量級;其實對於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、1、2、3、4、5、6、7、8、9、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放入父節點(根節點比較特殊,要新建一個父節點),右邊的數據放入新建的節點:

 

  繼續插入18、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....需要的時候再去看吧!


免責聲明!

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



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