前端數據結構–二叉樹先序、中序、後序 遞歸、非遞歸遍歷
- 2021 年 5 月 10 日
- 筆記
- 二叉樹遍歷, 先序、中序、後序、層次遞歸、非遞歸遍歷, 數據結構
二叉樹遍歷
二叉樹的遍歷是指從根節點出發,按照某種順序依次訪問所有節點,而且只訪問一次,二叉樹的遍歷方式很多,如果限制了從左到右的方式,那麼主要有4種:
- 前序遍歷:根左右
- 中序遍歷:左根右
- 後續遍歷:左右根
- 層序遍歷:按層級、從上到下,在同一層從左到右遍歷
以上一篇的二叉樹為例子,先序遍歷 先訪問根節點,在訪問左節點,在訪問右節點,如圖:
- 先(根)序遍歷(根左右):A、B、D、E、C、F、G
- 中(根)序遍歷(左根右) : D、B、E、A、F、C、G
- 後(根)序遍歷(左右根) : D、E、B、F、G、C、A
- 層序序遍歷(上到下,左到右):A、B、C、D、E、F、G
遞歸實現先序、中序、後序、層序遍歷
二叉樹的創建用上一篇鏈表方法存儲的二叉樹,只不過增加了4個遍歷的方法而已。一顆大的樹都是若干顆小樹(根節點、左節點、右節點)構成,根節點也有可能是其他節點的左子樹(樹的根節點除外),所以遞歸遍歷就是不斷的遞歸子樹的過程。
1 /* 2 * @Description: 3 * @Version: 1.0 4 * @Autor: longbs 5 */ 6 class Node { 7 constructor (data = '#') { 8 this.data = data 9 this.lNode = null 10 this.rNode = null 11 } 12 } 13 14 class BiTree { 15 root = null 16 nodeList = [] 17 constructor (nodeList) { 18 this.root = new Node() 19 this.nodeList = nodeList 20 } 21 createNode (node) { 22 const data = this.nodeList.shift() 23 if (data === '#') return 24 node.data = data 25 // 下一個元素是不是空節點, 如果不是創建左節點 26 if (this.nodeList[0] !== '#') { 27 node.lNode = new Node(data) 28 } 29 this.createNode(node.lNode) 30 31 // 下一個元素是不是空節點, 如果不是創建右節點 32 if (this.nodeList[0] !== '#') { 33 node.rNode = new Node(data) 34 } 35 this.createNode(node.rNode) 36 37 } 38 preorderTraverse (node) { 39 if (node === null) return 40 console.log(node.data) 41 this.preorderTraverse(node.lNode) 42 this.preorderTraverse(node.rNode) 43 } 44 inorderTraverse (node) { 45 if (node === null) return 46 this.inorderTraverse(node.lNode) 47 console.log(node.data) 48 this.inorderTraverse(node.rNode) 49 } 50 postorderTraverse (node) { 51 if (node === null) return 52 this.postorderTraverse(node.lNode) 53 this.postorderTraverse(node.rNode) 54 console.log(node.data) 55 } 56 sequenceTraverse (root) { 57 if (!root) return 58 let queue = [] 59 queue.push(root) 60 while (queue.length) { 61 const node = queue.shift() 62 console.log(node.data) 63 if(node.lNode) { 64 queue.push(node.lNode) 65 } 66 if (node.rNode) { 67 queue.push(node.rNode) 68 } 69 } 70 } 71 } 72 73 let arr = ['A','B','D','#','#','E','#','#','C','F','#', '#', 'G', '#', '#'] 74 let bi = new BiTree(arr) 75 bi.createNode(bi.root) 76 console.log(bi.root) 77 78 console.log('----先序----') 79 console.log(bi.preorderTraverse(bi.root)) 80 81 console.log('----中序----') 82 console.log(bi.inorderTraverse(bi.root)) 83 84 console.log('----後序----') 85 console.log(bi.postorderTraverse(bi.root)) 86 87 console.log('----層序----') 88 console.log(bi.sequenceTraverse(bi.root))
層級遍歷使用了隊列來實現,思路也比較簡單,一開始入隊根節點,出隊最後一個節點,出隊時把相關左節點、右節點入隊,如此循環,隊列為空即遍歷完成。
非遞歸實現先序、中序、後序
二叉樹的遞歸遍歷非常容易理解,但因為是遞歸調用需要在記憶體棧中分配記憶體用來保存參數,層數多了記憶體容易溢出。
非遞歸先序基本思路:使用數組來模擬棧的數據結構,首先根節點入棧,然後出棧,在出棧的時候把相關需要的節點按要求把左右節點入棧,如此循環一直到這個棧為空。
步驟:
- 根節點放入棧
- 取出棧頂的節點,把該節點結果放入數組
- 如果該右節點存在,將該節點右節點放入棧
- 如果該左節點存在,將該節點左節點放入棧
- 重複 2-4
- 棧為空退出循環
1 /* 2 非遞歸:用棧來模擬遞歸 3 */ 4 preorderNonRecursion (root) { 5 if (!root) return '' 6 let stack = [] 7 let result = [] 8 stack.push(root) 9 while (stack.length) { 10 const node = stack.pop() 11 result.push(node.data) 12 13 // 如果存在右節點,先壓入右節點 14 if (node.rNode) { 15 stack.push(node.rNode) 16 } 17 if (node.lNode) { 18 stack.push(node.lNode) 19 } 20 } 21 return result 22 }
中序、後序程式碼基本思路類似程式碼如下:
1 // 非遞歸-中序 2 inorderNonRecursion (root) { 3 if (!root) return '' 4 let stack = [] 5 let result = [] 6 // stack.push(root) 7 while (root !== null || stack.length) { 8 // 找到左節點 9 while (root !== null) { 10 stack.push(root) 11 root = root.lNode 12 } 13 root = stack.pop() 14 result.push(root.data) 15 // 右節點 16 root = root.rNode 17 } 18 return result 19 } 20 // 非遞歸-後序 21 postorderNonRecursion (root) { 22 if (!root) return '' 23 let stack = [] 24 let result = [] 25 stack.push(root) 26 while (stack.length) { 27 const node = stack.pop() 28 result.unshift(node.data) 29 30 if (node.lNode) { 31 stack.push(node.lNode) 32 } 33 if (node.rNode) { 34 stack.push(node.rNode) 35 } 36 } 37 return result 38 }
遞歸遍歷、非遞歸遍歷完整程式碼
/* * @Description: * @Version: 1.0 * @Autor: longbs */ class Node { constructor (data = '#') { this.data = data this.lNode = null this.rNode = null } } class BiTree { root = null nodeList = [] constructor (nodeList) { this.root = new Node() this.nodeList = nodeList } // 創建二叉樹 createNode (node) { const data = this.nodeList.shift() if (data === '#') return node.data = data // 下一個元素是不是空節點, 如果不是創建左節點 if (this.nodeList[0] !== '#') { node.lNode = new Node(data) } this.createNode(node.lNode) // 下一個元素是不是空節點, 如果不是創建右節點 if (this.nodeList[0] !== '#') { node.rNode = new Node(data) } this.createNode(node.rNode) } // 先序 preorderTraverse (node) { if (node === null) return console.log(node.data) this.preorderTraverse(node.lNode) this.preorderTraverse(node.rNode) } // 中序 inorderTraverse (node) { if (node === null) return this.inorderTraverse(node.lNode) console.log(node.data) this.inorderTraverse(node.rNode) } // 後序 postorderTraverse (node) { if (node === null) return this.postorderTraverse(node.lNode) this.postorderTraverse(node.rNode) console.log(node.data) } // 層序 sequenceTraverse (root) { if (!root) return let queue = [] queue.push(root) while (queue.length) { const node = queue.shift() console.log(node.data) if(node.lNode) { queue.push(node.lNode) } if (node.rNode) { queue.push(node.rNode) } } } /* 非遞歸-先序 用棧來模擬遞歸 */ preorderNonRecursion (root) { if (!root) return '' let stack = [] let result = [] stack.push(root) while (stack.length) { const node = stack.pop() result.push(node.data) // 如果存在右節點,先壓入右節點 if (node.rNode) { stack.push(node.rNode) } if (node.lNode) { stack.push(node.lNode) } } return result } // 非遞歸-中序 inorderNonRecursion (root) { if (!root) return '' let stack = [] let result = [] // stack.push(root) while (root !== null || stack.length) { // 找到左節點 while (root !== null) { stack.push(root) root = root.lNode } root = stack.pop() result.push(root.data) // 右節點 root = root.rNode } return result } // 非遞歸-後序 postorderNonRecursion (root) { if (!root) return '' let stack = [] let result = [] stack.push(root) while (stack.length) { const node = stack.pop() result.unshift(node.data) if (node.lNode) { stack.push(node.lNode) } if (node.rNode) { stack.push(node.rNode) } } return result } } let arr = ['A','B','D','#','#','E','#','#','C','F','#', '#', 'G', '#', '#'] let bi = new BiTree(arr) bi.createNode(bi.root) console.log(bi.root) console.log('----遞歸先序----') console.log(bi.preorderTraverse(bi.root)) console.log('----非遞歸先序----') console.log(bi.preorderNonRecursion(bi.root)) console.log('----遞歸中序----') console.log(bi.inorderTraverse(bi.root)) console.log('----非遞歸中序----') console.log(bi.inorderNonRecursion(bi.root)) console.log('----遞歸後序----') console.log(bi.postorderTraverse(bi.root)) console.log('----非遞歸後序----') console.log(bi.postorderNonRecursion(bi.root)) console.log('----層序----') console.log(bi.sequenceTraverse(bi.root))