三種遍歷的基本思想
先序遍歷:根節點 -> 左子樹 -> 右子樹
中序遍歷:左子樹 -> 根節點 -> 右子樹
后序遍歷:左子樹 -> 右子樹 -> 根節點
如,以下二叉樹遍歷:
先序遍歷結果:1 2 4 5 7 8 3 6
中序遍歷結果:4 2 7 5 8 1 3 6
后序遍歷結果:4 7 8 5 2 6 3 1
二叉樹基本結構
1 function TreeNode(val) { 2 3 this.val = val; 4 5 this.left = this.right = null; 6 7 }
一、先序遍歷
- 遞歸實現:先處理當前節點,然后遞歸處理左子節點,最后遞歸處理右子節點
1 function preVisitRecursive(root) { 2 if(root) { 3 console.log(root.val); //處理當前節點 4 preVisit(root.left); //遞歸處理左子節點 5 preVisit(root.right); //遞歸處理右子節點 6 } 7 }
- 非遞歸實現:使用棧(后進先出)來保存返回需要處理的節點
1 function preVisit(root) { 2 let curNode = root, treeNode = []; 3 while(curNode !== null || treeNode.length > 0) { 4 if(curNode!== null) { 5 console.log(curNode.val); //處理當前節點 6 treeNode.push(curNode); 7 curNode = curNode.left; //指向左子節點做同樣處理 8 } else { 9 curNode = treeNode.pop(); //棧頂元素出棧 10 curNode = curNode.right; //指向右子節點 11 } 12 } 13 }
二、中序遍歷
- 遞歸實現:先遞歸處理左子節點,再處理當前節點,最后遞歸處理右子節點
1 function midVisitRecursive(root) { 2 if(root) { 3 preVisit(root.left); //遞歸處理左子節點 4 console.log(root.val); //處理當前節點 5 preVisit(root.right); //遞歸處理右子節點 6 } 7 }
- 非遞歸實現:使用棧(后進先出)來保存返回需要處理的節點
1 function midVisit(root) { 2 let curNode = root, treeNode = []; 3 while(curNode !== null || treeNode.length > 0) { 4 if(curNode!== null) { 5 treeNode.push(curNode); 6 curNode = curNode.left; //指向左子節點做同樣處理 7 } else { 8 curNode = treeNode.pop(); //棧頂元素出棧 9 console.log(curNode.val); //處理當前節點 10 curNode = curNode.right; //指向右子節點 11 } 12 } 13 }
三、后序遍歷
- 遞歸實現:先遞歸處理左子節點,然后遞歸處理右子節點,最后處理當前節點
1 function postVisitRecursive(root) { 2 if(root) { 3 preVisit(root.left); //遞歸處理左子節點 4 preVisit(root.right); //遞歸處理右子節點 5 console.log(root.val); //處理當前節點 6 } 7 }
- 非遞歸實現:需要保證左子節點和右子節點都被訪問 且 右子節點在左子節點訪問之后 才能訪問根節點
1)對於任一結點P,將其入棧,然后沿其左子樹一直往下搜索,直到搜索到沒有左孩子的結點,此時該結點出現在棧頂,但是此時不能將其出棧並訪問,因為其右孩子還未被訪問。
所以接下來按照相同的規則對其右子樹進行相同的處理,當訪問完其右孩子時,該結點又出現在棧頂,此時可以將其出棧並訪問。這樣就保證了正確的訪問順序。
可以看出,在這個過程中,每個結點都兩次出現在棧頂,只有在第二次出現在棧頂時,才能訪問它。因此需要多設置一個變量標識該結點是否是第一次出現在棧頂。

1 function postVisit(root) { 2 let curNode = root, treeNode = []; 3 while(curNode !== null || treeNode.length > 0) { 4 { 5 if(curNode !== null) 6 { 7 curNode.isFirst = true; 8 treeNode.push(curNode); //當前節點入棧 9 curNode = curNode.left; //指向left,進行步驟1處理 10 } else { 11 curNode = treeNode.pop(); //指向棧頂元素 12 if(curNode.isFirst) { //表示第一次出棧頂 13 curNode.isFirst = false; 14 treeNode.push(curNode); 15 curNode =curNode.right 16 } else { //第二次出棧頂 17 console.log(curNode.val); 18 curNode = null; //進行步驟2 19 } 20 } 21 } 22 } 23 }
覺得不錯的另外的實現
2)要保證左子節點和右子節點都被訪問之后才能訪問根節點。對於任一節點P,先將其入棧。如果其不存在左子節點和右子節點,可直接訪問;亦或其存在左子節點或右子節點,且其左子節點和右子節點已被訪問過,則可直接訪問該節點;其他情況下,則將其右子節點和左子節點依次入棧,這樣保證了出棧時左子節點在右子節點之前被訪問,根節點在子節點之后被訪問

1 function postVisit(root) { 2 let treeNode = [], 3 curNode, //當前節點 4 pre = null; //上一次訪問的節點 5 treeNode.push(root); 6 while(treeNode.length > 0) { 7 curNode = treeNode[treeNode.length - 1]; 8 // 若當前節點沒有左子節點和右子節點或者孩子節點均被訪問過 9 if((curNode.left === null && curNode.right === null) || (pre && (pre === curNode.left || pre === curNode.right))) { 10 console.log(curNode.val); 11 treeNode.pop(); 12 pre = curNode; 13 } else { 14 if(curNode.right) { 15 treeNode.push(curNode.right); 16 } 17 if(curNode.left) { 18 treeNode.push(curNode.left); 19 } 20 } 21 } 22 }
四、二叉樹與數組相結合
從二叉搜索樹(BST)中找出是否存在兩個節點的和等於某個值k
1 /** 2 * @param {TreeNode} root 3 * @param {number} k 4 * @return {boolean} 5 */ 6 var findTarget = function(root, k) { 7 let treeNode = [], numsArr = [], curNode = root; 8 while(curNode !== null || treeNode.length > 0) { 9 if(curNode !== null) { 10 treeNode.push(curNode); 11 curNode = curNode.left; 12 } else { 13 curNode = treeNode.pop(); 14 numsArr.push(curNode.val); 15 curNode = curNode.right; 16 } 17 } 18 let m = 0, n = numsArr.length - 1, sum = numsArr[m] + numsArr[n]; 19 while(m < n) { 20 if(sum === k) return true; 21 sum < k ? m++ : n--; 22 sum = numsArr[m] + numsArr[n]; 23 } 24 return false 25 };