前言:
現在安卓面試,對於資料結構的問題也越來越多了,也經常看到別人發的面試題都是問什麼紅黑樹,二叉樹查詢等,所以我們雖然不會馬上就會各種難的面試題,但起碼樹的基礎知識還是要會的,這樣才能去進一步學。
貼上最近看到的一個介紹圖片:
![Android技能樹 — 樹基礎知識小結(一)](https://i.iter01.com/images/7ac1b937f5174f46cedba67931e5853b946f177920894291d26e4728aa76bfdb.png)
Android技能書系列:
Android基礎知識
Android技能樹 — Android儲存路徑及IO操作小結
資料結構基礎知識
演算法基礎知識
本文主要講關於樹的基礎知識。
![Android技能樹 — 樹基礎知識小結(一)](https://i.iter01.com/images/af792b558cd1eab282ac4c7f22f8d74fced2c29b2650aefa2798901e2c4c4bb7.png)
樹(Tree)是n(n>=0)個結點的有限集。n=0時稱為空樹。在任意一棵非空樹中:(1)有且僅有一個特定的稱為根(Root)的結點;(2)當n>1時,其餘結點可分為m(m>O)個互不相交的有限集T1、T2、……、 Tm,其中每一個集合本身又是一棵樹,並且稱為根的子樹(SubTree)
![Android技能樹 — 樹基礎知識小結(一)](https://i.iter01.com/images/246583d492fa44777876389013186f498e397b8aaa97c8a0c7e1d47cb7f1c9d4.png)
基礎知識
結點
![Android技能樹 — 樹基礎知識小結(一)](https://i.iter01.com/images/3c9007ade6802f5213108f9fe37277ee53351e34278beb7f3d5111bebaa4f2a2.png)
根據上面的基礎知識我畫了一個歸總的圖(這樣我就不需要寫文字介紹了,啊哈哈):
![Android技能樹 — 樹基礎知識小結(一)](https://i.iter01.com/images/12386baf11b32a594b112434d8fac3769c1213499c20ab4e410c2b2e78c89a69.png)
樹結構特點
![Android技能樹 — 樹基礎知識小結(一)](https://i.iter01.com/images/c598175c5aab18ed205eb73c598e9aa106a1718a390127d4f54b3ea37bf9245d.png)
還是用自己畫的圖來說明:
![Android技能樹 — 樹基礎知識小結(一)](https://i.iter01.com/images/e93d13d33267d2426e090119562f7f581fda223bd3b262f43e3d3c849020c6a2.png)
儲存結構
在Android技能樹 — 陣列,連結串列,雜湊表基礎小結中,我們介紹了線性儲存,鏈式儲存,我們的樹可以充分用二者來結合表示。
![Android技能樹 — 樹基礎知識小結(一)](https://i.iter01.com/images/42aa446551b104edbbdf29e99d99530d7487b11f8cc59966bda680b9834cc89f.png)
我們統一來用上面各種方式來表示下面這個樹的儲存結構:
![Android技能樹 — 樹基礎知識小結(一)](https://i.iter01.com/images/515e51529d3684f040fa528b22c8ac7e5511e5d36e4b944fdf2e53164075c643.png)
雙親表示法:
在每個結點中,附設一個指示器指示其雙親結點在陣列中的位置。
![Android技能樹 — 樹基礎知識小結(一)](https://i.iter01.com/images/c8b71fde39ac8bef8432dd2422e2b9eea10942380452a95211ac3f7d5183186f.png)
因為有十一個結點,所以我們的index為0-10,然後index位置中儲存(data + parent的index值),結果如下圖所示:
![Android技能樹 — 樹基礎知識小結(一)](https://i.iter01.com/images/9f75b1eb3a4f656c953d2edb7721b3c68d22ffa0b99c42c905cf71c55f78a90b.png)
這裡我們發現根據index裡面的parent指標,很容易知道父結點,但是比如我問知道了E結點,想知道它的子結點是哪幾個,就不知道了,只能通過整個結構的遍歷。
當然我們可以變相的 多加一個指標:
![Android技能樹 — 樹基礎知識小結(一)](https://i.iter01.com/images/a493392ea5dfa9ff61ac4da65272c4c418228826809c0bd5c86144cc3b8212f5.png)
如果我們又比較關注兄弟結點之間的關係,可以增加一個右兄弟域來體現兄弟關係:
![Android技能樹 — 樹基礎知識小結(一)](https://i.iter01.com/images/3e0e53a7a5620313faf42db6b3c602700d82fce1182b327a35395a663a99c921.png)
孩子連結串列表示法:
把每個結點的孩子結點排列起來,以單連結串列作儲存結構,則n 個結點有n個孩子連結串列,如果是葉子結點,則此單連結串列為空。然後n個頭指標又組成一個線性表,採用順序儲存結構,存放進一個一維陣列中。
用孩子表示法表示我們上面的樹,結構如下:
![Android技能樹 — 樹基礎知識小結(一)](https://i.iter01.com/images/b995cc1e2ef58e31362774195abbfda3375a203699ddb18a9e5859a50b8e4bc2.png)
所以我們的結點結構有二種: 1.表頭陣列的表頭結點:
![Android技能樹 — 樹基礎知識小結(一)](https://i.iter01.com/images/8f44d8d43ec3b328844c262b9857e0423646bcbd2303d85ee926c492c7f53eaa.png)
![Android技能樹 — 樹基礎知識小結(一)](https://i.iter01.com/images/0ec7189acb46685c9008c51cd7c43b82c697d6c99a92e4c17baa3dbd5cf721c8.png)
- 孩子連結串列的孩子結點:
但是這樣子對於查詢某個結點的孩子,或者找某個結點的兄弟都方便,但似乎如果要找某個結點的雙親結點就麻煩了。所以我們可以結合上面講過的《雙親表示法》
雙親孩子表示法:
![Android技能樹 — 樹基礎知識小結(一)](https://i.iter01.com/images/b69a177c8ecff5bcab688a3d776731a5f2a8f4cce9484dc3c1665ed2583a670b.png)
把上面二個方法結合就可以了。
孩子兄弟表示法:
任意一棵樹,它的結點的第一個孩子如果存在就是唯一的,它的右兄弟如果存在也是唯一的。 所以設定二個指標,分別指向該結點的第一個孩子和此結點的右兄弟
所以和上面類似,只是我們存了不同的二個指標(從方法名字就知道,<孩子><兄弟>法,一個孩子,一個兄弟,二個指標)。
![Android技能樹 — 樹基礎知識小結(一)](https://i.iter01.com/images/44f4f8ed9db4deeda5f939d0ac4f1d144ce393897e5cb330d9c736efa6e3d13c.png)
我們把上面的樹按照這種方式實現:
![Android技能樹 — 樹基礎知識小結(一)](https://i.iter01.com/images/0eec375b21ec310251c9b9e3b56a049f9eff926ca455739ab82b05e82648d13d.png)
森林:
![Android技能樹 — 樹基礎知識小結(一)](https://i.iter01.com/images/4e65440ede69ae1fd81ece7c860f4026c96588ea87a5191022fd8ef6480fe56c.png)
繼續畫個圖來說明,省得打字了,嘿嘿:
![Android技能樹 — 樹基礎知識小結(一)](https://i.iter01.com/images/9bb95d359ebf1864d3a25f945d7dfc20d8d3afa9d63a9e15856fc25c6ed3baa7.png)
![Android技能樹 — 樹基礎知識小結(一)](https://i.iter01.com/images/13c42966cb97e1b23f4703723248e9e1cd034cd54bc62080cdacfc39e882d40d.png)
分類
樹也是會根據不同條件,做了分類,我們來了解一下。
![Android技能樹 — 樹基礎知識小結(一)](https://i.iter01.com/images/3c954a2b98eceebfd8810c71b0274a3ce11d31444eab51e0f64aa7559bf1fcee.png)
那有序樹和無序數的區別在於哪裡呢?
如果將樹中結點的各子樹看成從左至右是有次序的,不能互換,則成為有序樹,否則就是無序樹
比如我們只是單純的表示一個家族的關係:
![Android技能樹 — 樹基礎知識小結(一)](https://i.iter01.com/images/4f7afea9b7a52361ea7ffc8a32552fe18825b588bf9e4dc6d68c793666660477.png)
比如只是說明A的孩子有B跟C,這時候B和C換了位置葉 沒關係,這時候就是無序樹。
但是如果我們這個家族譜是按照年齡來排序(長子,次子),那這時候B和C就不能換位置了,這時候就是有序樹。
但是我們平常說的樹通常都是指的有序樹,而有序樹有很多分類,比較多的就是二叉樹。
![Android技能樹 — 樹基礎知識小結(一)](https://i.iter01.com/images/311605f1acc88f7c74b6a83e82bd4af3b2c7dbe4fda890da4760c7cd73e0438f.png)
二叉樹:
![Android技能樹 — 樹基礎知識小結(一)](https://i.iter01.com/images/049692d4a89457ad50fe35e3970c3cb48ef8fefc00ae65382d940ea844683f07.png)
基本形態:
![Android技能樹 — 樹基礎知識小結(一)](https://i.iter01.com/images/52be24c4f9c216010927744f52c4da2472114ca20b4b4534479d0cb191cc7648.png)
二叉樹性質:
![Android技能樹 — 樹基礎知識小結(一)](https://i.iter01.com/images/b2f51955aab66bb40b60754266a455a42feae7527dfb9b01fdc6e911cabd6f5b.png)
其實這個類似與我們以前數學課上要背的數學公式,大家可以自己畫個二叉樹,然後試著上面的公式比對下,是不是正確。
遍歷二叉樹:
二叉樹的遍歷是指從根結點出發,按照某種次序依次訪問二叉樹中所有結點,使得每個結點被訪問依次且僅被訪問一次。
前序遍歷:
![Android技能樹 — 樹基礎知識小結(一)](https://i.iter01.com/images/82a4fba6a196ea54b9be29efcd5194d77e28722bd20d146d5049f0da24a46ce1.png)
單單看這個圖,其實換成我,我也看不懂規律,但是我們只需要懂得其中的規則就行。
虛擬碼:
遍歷(結點物件 t){
if( t == null){
return;
}
//第一步,實現某個業務操作,比如我們是列印結點字串。
print(t)
//第二步,遞迴方式繼續呼叫該方法遍歷左孩子
遍歷(t.左孩子)
//第三步,遞迴方式繼續呼叫該方法遍歷右孩子
遍歷(t.右孩子)
}
複製程式碼
我們看到因為print在其他方法的前面,所以叫前序遍歷。我們現在傳入根結點到這個方法,然後依次列印並且遞迴,就會發現,就是圖上的順序。
中序遍歷:
![Android技能樹 — 樹基礎知識小結(一)](https://i.iter01.com/images/cd422db876159155d2a0cda5c0e66c7acfda52fd3e1004a5fe5e50361da646d6.png)
虛擬碼:
遍歷(結點物件 t){
if( t == null){
return;
}
//第一步,遞迴方式繼續呼叫該方法遍歷左孩子
遍歷(t.左孩子)
//第二步,實現某個業務操作,比如我們是列印結點字串。
print(t)
//第三步,遞迴方式繼續呼叫該方法遍歷右孩子
遍歷(t.右孩子)
}
複製程式碼
我們發現只要把我們的列印語句放在中間,就是中序遍歷了。
後序遍歷:
![Android技能樹 — 樹基礎知識小結(一)](https://i.iter01.com/images/c7353acef5632b896ccb65295fe5fbdd30418c3d72e5e59c4c06b0447eb690d0.png)
虛擬碼:
遍歷(結點物件 t){
if( t == null){
return;
}
//第一步,遞迴方式繼續呼叫該方法遍歷左孩子
遍歷(t.左孩子)
//第二步,遞迴方式繼續呼叫該方法遍歷右孩子
遍歷(t.右孩子)
//第三步,實現某個業務操作,比如我們是列印結點字串。
print(t)
}
複製程式碼
我們發現只要把我們的列印語句放在最後,就是後序遍歷了。
二叉樹分類:
![Android技能樹 — 樹基礎知識小結(一)](https://i.iter01.com/images/fbfd847b467579b36dec8169e0d56d45cb48000b89395efc58ae07da7e09fa20.png)
斜樹:
![Android技能樹 — 樹基礎知識小結(一)](https://i.iter01.com/images/eca98b0a74e6608e799a9628d2638e2dc045440a452f0744dc48f0fb51aea7dd.png)
完全二叉樹與滿二叉樹:
一棵深度為k,且有 2^(k+1) - 1 個節點的二叉樹稱為滿二叉樹,這種樹的特點是每一層上的節點數都是最大節點數。
而在一棵二叉樹中,除最後一層外,若其餘層都是滿的,並且最後一層或者是滿的,或者是在右邊缺少連續若干節點,則此二叉樹為完全二叉樹。
![滿二叉樹](https://i.iter01.com/images/32749fba93c1994e859a9d7829b3145a4f4aeef3d9ab86fd869ff65394f68fd7.png)
![完全二叉樹](https://i.iter01.com/images/28b00d262c2580a233d8ceb9d0ed68c6f3d7fbf31ff90159bdd90867be3b3504.png)
平衡二叉樹:
這塊知識很多,後期補上。
排序二叉樹:
這塊知識很多,後期補上。
線索二叉樹:
n個結點的二叉連結串列中含有n+1(2n-(n-1)=n+1)個空指標域。利用二叉連結串列中的空指標域,存放指向結點在某種遍歷次序下的前驅和後繼結點的指標(這種附加的指標稱為"線索")。
這裡一定要說明一個知識點:什麼是前驅和後繼。
網上很多人都是對這個解釋太過於簡單以至於很多人理解錯誤,比如:
![Android技能樹 — 樹基礎知識小結(一)](https://i.iter01.com/images/51c932c47ecbfb5361d7d88f749250d17a42f9f86e1d4e2da93c682056a833ea.png)
假設我們現在有這個一個二叉樹:
![Android技能樹 — 樹基礎知識小結(一)](https://i.iter01.com/images/da1e3b09960f2f8f279ca539ca5c2a896ac630481447da1c15792a1a4d232da5.png)
![Android技能樹 — 樹基礎知識小結(一)](https://i.iter01.com/images/a9e07dbb088e9a36a24d2b68f3328eb10303afd0a2ee9bec0fc8d26db37e099c.png)
比如雙向連結串列就是有前驅和後繼。那我們單純看這棵樹是看不出來的,我們要先把樹按照某個遍歷方式的時候,把它用這種連結串列形式擺列,然後才能知道某個結點的前驅和後繼是什麼,比如上面的圖我們用中序遍歷,我們得到的是:HDIBJEAFCG,這時候 I 的前繼是D,後繼是B。
我們在二叉樹儲存結構中,有二個指標指向它的二個子結點。
![Android技能樹 — 樹基礎知識小結(一)](https://i.iter01.com/images/d17137c901c34d71e55127558f88eb082b39a266d48c3cd07a6a2e73227c96cb.png)
但是可能只有一個子結點,或者沒有子結點,這樣這個空的指標儲存就浪費了,我們可以在這個指標裡面存這個結點的前驅或者後繼結點的指標。
但是這時候又有一個問題,就是我們不知道這個點目前到底放的是前驅的還是左子結點的指標,所以我們還需要一個引數來說明當前這個位置放的是哪個的指標。
![Android技能樹 — 樹基礎知識小結(一)](https://i.iter01.com/images/af7ee5bd9808f09088ab3666f341cc083e2ff7bbb5b252908649acf3db7c4f75.png)
- 當ltag為0的時候,說明lchild是該結點的左孩子的指標,為1的時候說明lchild是該結點的前驅。
- 當rtag為0的時候,說明rchild是該結點的右孩子的指標,為1的時候說明rchild是該結點的後繼。
儲存結構:
![Android技能樹 — 樹基礎知識小結(一)](https://i.iter01.com/images/0b5a3efba1766b1aa3851aa2d11dffbe6d61096d64c87371a206952b522501ef.png)
二叉樹順序儲存結構:
我們把二叉樹補充為一個滿二叉樹,然後相應的填入一個一維陣列即可。
![Android技能樹 — 樹基礎知識小結(一)](https://i.iter01.com/images/2844c99ea6fa8d22616f90d82d909e40f2ac07372c92a4efb8ee235095cb735f.png)
二叉連結串列:
二叉樹每個結點最多又二個孩子,所以為它設計一個資料域和二個指標域。
![Android技能樹 — 樹基礎知識小結(一)](https://i.iter01.com/images/9e26d70c5de359a789416ca02268d59c382420cb5ac03dde3b1ed9d1aa86e91d.jpg)
三叉連結串列:
改進於二叉連結串列,增加父節點的指引,能更好地實現節點間的訪問
![Android技能樹 — 樹基礎知識小結(一)](https://i.iter01.com/images/de845e68abf37d3ae75670ea3ab7c27e5af8e1c338dd048f2d0722da8c2074a2.jpg)
結語:
本文並沒有寫完,內容太多,後面再陸續補上去。哪裡寫錯了,歡迎指出。。。謝謝。
參考:
《大話資料結構》
《維基百科》