前端數據結構--樹

前面介紹過的都是線性的數據結構,本文將介紹一種非線性數據結構——樹,它對於存儲需要快速查找的數據非常有用。樹是一種一對多的數據結構,樹這種數據結構在生活中經常看到,如 組織結構圖

 

圖中每個元素我們叫做節點,即樹(Tree)可以理解爲是n(n>=0)個節點的有限集合。當n=0時稱爲空樹。

基本概念

樹這種數據結構跟現實生活中的樹很相似,樹中的元素叫節點,其中連接相鄰節點之間具有層級關係的叫做父子關係。

比如下面這幅圖,A 節點就是 B 節點的父節點,B 節點是 A 節點的子節點。B、C、D 這三個節點的父節點是同一個節點,所以它們之間互稱爲兄弟節點。父節點爲同一層的節點稱爲堂兄弟節點,也就是圖中的B、C、D、K、L,及G、H、I、J。我們把沒有父節點的節點叫做根節點,也就是圖中的節點 E。我們把沒有子節點的節點叫做葉子節點或者葉節點,比如圖中的 G、H、I、J、K、L 都是葉子節點。

樹還有三個比較相似的概念:高度(Height)、深度(Depth)、層(Level)。

節點的高度:節點到葉子節點的最長路徑、邊的個數

節點的深度:跟節點到這個節點的邊的個數

節點的層數:節點的深度 + 1

樹的高度:跟節點的高度

這三個概念的定義比較容易混淆,描述起來也比較空洞。我舉個例子說明一下,你一看應該就能明白。

 

 如圖所示高度是從下往上遞增,深度是上往下遞增,層數是深度加 1。

二叉樹

樹結構多種多樣,不過我們最常用還是二叉樹。

二叉樹,顧名思義,每個節點最多有兩個叉,也就是兩個子節點,分別是左子節點和右子節點。不過,二叉樹並不要求每個節點都有兩個子節點,有的節點只有左子節點,有的節點只有右子節點。我畫的這幾個都是二叉樹。

 

 因爲二叉樹每個節點最多隻有兩個子節點,所以既可以用數組來存儲,也可以用鏈表來存儲。

先來看比較簡單、直觀的鏈式存儲法。從圖中你應該可以很清楚地看到,每個節點有三個字段,其中一個存儲數據,另外兩個是指向左右子節點的指針。我們只要知道根節點,就可以通過左右子節點的指針,把整棵樹都串起來,這種存儲方式我們比較常用。大部分二叉樹代碼都是通過這種結構來實現的。

基於數組的順序存儲法。把根節點存儲在下標 i = 1 的位置,那左子節點存儲在下標 2 * i = 2 的位置,右子節點存儲在 2 * i + 1 = 3 的位置。以此類推,B 節點的左子節點存儲在 2 * i = 2 * 2 = 4 的位置,右子節點存儲在 2 * i + 1 = 2 * 2 + 1 = 5 的位置。

 

 如圖所示,如果節點 X 存儲在數組中下標爲 i 的位置,下標爲 2 * i 的位置存儲的就是左子節點,下標爲 2 * i + 1 的位置存儲的就是右子節點。反過來,下標爲 i/2 的位置存儲就是它的父節點。通過這種方式,我們只要知道根節點存儲的位置(一般情況下,爲了方便計算子節點,根節點會存儲在下標爲 1 的位置),這樣就可以通過下標計算,把整棵樹都串起來。

創建二叉樹

觀察上面的圖我們可以知道,二叉樹實際就是一個遞歸的過程,不斷的左子樹、右子樹,直到該節點沒有左子樹或者右子樹。遞歸需要一個臨界點來結束遞歸,不然會死循環,從圖中可以知道樹終止遞歸其實就是沒有左子樹、右子樹,也就是葉子節點,所以我們需要把葉子節點補上,用 # 來表示 如:

1 const arr = ['A','B','D','#','#','E','#','#','C','F','#', '#', 'G', '#', '#']

步驟:

  1. 先創建跟節點
  2. 遞歸創建左子樹
  3. 遞歸創建右子樹

先序遍歷構建

 1 /*
 2  * @Description: 
 3  * @Version: 1.0
 4  * @Autor: longbs
 5  * 先序構建
 6  */
 7 
 8 class Node {
 9   constructor (data = '#') {
10     this.data = data
11     this.lNode = null
12     this.rNode = null
13   }
14 }
15 
16 class BiTree {
17   root = null
18   nodeList = []
19   constructor (nodeList) {
20     this.root = new Node()
21     this.nodeList = nodeList
22   }
23   createNode (node) {
24     const data = this.nodeList.shift()
25     if (data === '#') return
26     node.data = data
27     // 下一個元素是不是空節點, 如果不是創建左節點
28     if (this.nodeList[0] !== '#') {
29       node.lNode = new Node(data)
30     }
31     this.createNode(node.lNode)
32 
33     // 下一個元素是不是空節點, 如果不是創建右節點
34     if (this.nodeList[0] !== '#') {
35       node.rNode = new Node(data)
36     }
37     this.createNode(node.rNode)
38     
39   }
40 }
41 
42 const arr = ['A','B','D','#','#','E','#','#','C','F','#', '#', 'G', '#', '#']
43 const bi = new BiTree(arr)
44 bi.createNode(bi.root)
45 console.log(bi.root)

層級遍歷構建

還可以一層一層的來構建,如先創建跟節點,在創建下一層的左子樹、右子樹,在繼續創建左子樹的下一層(左右子樹)。

步驟:

  1. 先創建跟節點
  2. 創建左子樹
  3. 創建右子樹
  4. 重複2、3過程

通常這種層次的問題可以使用隊列來解決,先將跟節點入隊,把隊列中的隊首出隊,將這個出隊相關的節點入隊,這樣循環,一直到隊列爲空。

 1 /*
 2  * @Description: 
 3  * @Version: 1.0
 4  * @Autor: longbs
 5  * 層次構建
 6  */
 7   class Node {
 8     constructor (data = '#') {
 9       this.data = data
10       this.lNode = null
11       this.rNode = null
12     }
13   }
14   
15   class BiTreeByLevel {
16     root = null
17     constructor () {
18       this.root = null
19     }
20     createNode (nodeList) {
21       let queue = []
22       if (!this.root) {
23         let nodeValue = nodeList.shift()
24         this.root = new Node(nodeValue)
25         queue.push(this.root)
26       }
27       while (queue.length) {
28         // 將隊列隊首出隊,這個是樹的跟節點或者子樹的跟節點
29         let head= queue.shift()
30         // 找到相關的在入隊
31         let nodeValue = nodeList.shift()
32         // 構建左節點
33         if (nodeValue !== '#') {
34           head.lNode = new Node(nodeValue)
35           queue.push(head.lNode)
36         }
37         // 右節點
38         nodeValue = nodeList.shift()
39         if (nodeValue !== '#') {
40           head.rNode = new Node(nodeValue)
41           queue.push(head.rNode)
42         }
43       }
44     }
45   }
46 
47 let arr = ['A','B','C','D','E','F','G','#','#','#','#','#','#','#','#']
48 let bi = new BiTreeByLevel(arr)
49 bi.createNode(arr)
50 console.log(bi.root)

 今天先到這裏吧,後面把二叉樹先序、中序、後序、層次遍歷,查找二叉樹、紅黑樹補上。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章