重拾演算法(1)——優雅地非遞迴遍歷二叉樹及其它
本文中非遞迴遍歷二叉樹的思想和程式碼都來自這裡(http://jianshu.io/p/49c8cfd07410#)。我認為其思想和程式碼都足夠優雅動人了,於是稍作整理,得到如下的程式。
前中後序遍歷二叉樹
1 public class BinaryTreeNode<T> 2 { 3 public T Value { get;set; } 4 public BinaryTreeNode<T> Parent { get;set; } 5 public BinaryTreeNode<T> Left { get;set; } 6 public BinaryTreeNode<T> Right { get;set; } 7 8 public virtual void Traverse(TraverseOrder order, NodeWorker<T> worker) 9 { 10 if (worker == null) { return; } 11 12 var left = this.Left; 13 var right = this.Right; 14 switch (order) 15 { 16 case TraverseOrder.Preorder: 17 /* recursive preorder traverse 18 worker.DoActionOnNode(this); 19 if (left != null) { Left.Traverse(order, worker); } 20 if (right != null) { Right.Traverse(order, worker); } 21 */ 22 PreorderTraverse(worker); 23 break; 24 case TraverseOrder.Inorder: 25 /* recursive inorder traverse 26 if (left != null) { Left.Traverse(order, worker); } 27 worker.DoActionOnNode(this); 28 if (right != null) { Right.Traverse(order, worker); } 29 */ 30 InorderTraverse(worker); 31 break; 32 case TraverseOrder.Postorder: 33 /* recursive postorder traverse 34 if (left != null) { Left.Traverse(order, worker); } 35 if (right != null) { Right.Traverse(order, worker); } 36 worker.DoActionOnNode(this); 37 */ 38 PostorderTraverse(worker); 39 break; 40 default: 41 break; 42 } 43 } 44 45 void PreorderTraverse(NodeWorker<T> worker) 46 { 47 var stack = new Stack<BinaryTreeNode<T>>(); 48 stack.Push(this); 49 50 while (stack.Count > 0) 51 { 52 var node = stack.Pop(); 53 //if (node == null) { continue; } 54 //else 55 { 56 var right = node.Right; 57 var left = node.Left; 58 worker.DoActionOnNode(node); 59 if (right != null) { stack.Push(right); } 60 if (left != null) { stack.Push(left); } 61 } 62 } 63 } 64 /* This works fine and is better for tuition. The code above is an optimized version. 65 void PreorderTraverse(NodeWorker<T> worker) 66 { 67 var stack = new Stack<BinaryTreeNode<T>>(); var stackReady4Visit = new Stack<bool>(); 68 69 stack.Push(this); stackReady4Visit.Push(false); 70 71 while (stack.Count > 0) 72 { 73 var node = stack.Pop(); var ready4Visit = stackReady4Visit.Pop(); 74 //if (node == null) { continue; } 75 if (ready4Visit) 76 { 77 worker.DoActionOnNode(node); 78 } 79 else 80 { 81 var right = node.Right; 82 var left = node.Left; 83 if (right != null) { stack.Push(right); stackReady4Visit.Push(false); } 84 if (left != null) { stack.Push(left); stackReady4Visit.Push(false); } 85 stack.Push(node); stackReady4Visit.Push(true); 86 } 87 } 88 } 89 */ 90 91 void InorderTraverse(NodeWorker<T> worker) 92 { 93 var stack = new Stack<BinaryTreeNode<T>>(); var stackReady4Visit = new Stack<bool>(); 94 95 stack.Push(this); stackReady4Visit.Push(false); 96 97 while (stack.Count > 0) 98 { 99 var node = stack.Pop(); var ready4Visit = stackReady4Visit.Pop(); 100 //if (node == null) { continue; } 101 if (ready4Visit) 102 { 103 worker.DoActionOnNode(node); 104 } 105 else 106 { 107 var right = node.Right; 108 var left = node.Left; 109 if (right != null) { stack.Push(right); stackReady4Visit.Push(false); } 110 stack.Push(node); stackReady4Visit.Push(true); 111 if (left != null) { stack.Push(left); stackReady4Visit.Push(false); } 112 } 113 } 114 } 115 116 void PostorderTraverse(NodeWorker<T> worker) 117 { 118 var stack = new Stack<BinaryTreeNode<T>>(); var stackReady4Visit = new Stack<bool>(); 119 120 stack.Push(this); stackReady4Visit.Push(false); 121 122 while (stack.Count > 0) 123 { 124 var node = stack.Pop(); var ready4Visit = stackReady4Visit.Pop(); 125 //if (node == null) { continue; } 126 if (ready4Visit) 127 { 128 worker.DoActionOnNode(node); 129 } 130 else 131 { 132 var right = node.Right; 133 var left = node.Left; 134 stack.Push(node); stackReady4Visit.Push(true); 135 if (right != null) { stack.Push(right); stackReady4Visit.Push(false); } 136 if (left != null) { stack.Push(left); stackReady4Visit.Push(false); } 137 } 138 } 139 } 140 } 141 public abstract class NodeWorker<T> 142 { 143 public abstract void DoActionOnNode(BinaryTreeNode<T> node); 144 }
以上三種遍歷實現程式碼行數一模一樣,如同遞迴遍歷一樣,只有三行核心程式碼的先後順序有區別。用原作者的話解釋就是:"得以統一三種更簡單的非遞迴遍歷方法的基本思想:有重合元素的區域性有序一定能導致整體有序。基於這種思想,我就構思三種非遞迴遍歷的統一思想:不管是前序,中序,後序,只要我能保證對每個結點而言,該結點,其左子結點,其右子結點都滿足以前序/中序/後序的訪問順序,整個二叉樹的這種三結點區域性有序一定能保證整體以前序/中序/後序訪問,因為相鄰的區域性必有重合的結點,即一個區域性的"根"結點是另外一個區域性的"子"結點。"。
層次遍歷
層次遍歷類似圖的廣度優先搜尋。
1 public class BinaryTree<T> 2 { 3 BinaryTreeNode<T> Node { get;set; } 4 public BinaryTree(BinaryTreeNode<T> node) 5 { 6 this.Node = node; 7 } 8 9 public void Traverse(TraverseOrder order, NodeWorker<T> worker) 10 { 11 if (worker == null) { return; } 12 var node = this.Node; 13 if (node == null) { return; } 14 15 switch (order) 16 { 17 case TraverseOrder.Preorder: 18 node.Traverse(order, worker); 19 break; 20 case TraverseOrder.Inorder: 21 node.Traverse(order, worker); 22 break; 23 case TraverseOrder.Postorder: 24 node.Traverse(order, worker); 25 break; 26 case TraverseOrder.Layer: 27 TraverseLayer(worker); 28 break; 29 default: 30 break; 31 } 32 } 33 34 private void TraverseLayer(NodeWorker<T> worker) 35 { 36 var queue = new Queue<BinaryTreeNode<T>>(); 37 queue.Enqueue(this.Node); 38 while (queue.Count > 0) 39 { 40 var element = queue.Dequeue(); 41 if (element != null) 42 { 43 worker.DoActionOnNode(element); 44 var left = element.Left; 45 var right = element.Right; 46 if (left != null) { queue.Enqueue(left); } 47 if (right != null) { queue.Enqueue(right); } 48 } 49 } 50 } 51 }
借用Traverse方法實現其他功能
1 public class BinaryTree<T> 2 { 3 /* 略 */ 4 5 public int GetNodeCount() 6 { 7 var counter = new NodeCounter<T>(); 8 this.Traverse(TraverseOrder.Preorder, counter); 9 return counter.Count; 10 } 11 12 public int GetLeaveCount() 13 { 14 var counter = new LeaveCounter<T>(); 15 this.Traverse(TraverseOrder.Preorder, counter); 16 return counter.Count; 17 } 18 19 public int GetNonLeaveCount() 20 { 21 var counter = new NonLeaveCounter<T>(); 22 this.Traverse(TraverseOrder.Preorder, counter); 23 return counter.Count; 24 } 25 } 26 27 class NonLeaveCounter<T> : NodeWorker<T> 28 { 29 public int Count { get;set; } 30 public override void DoActionOnNode(BinaryTreeNode<T> node) 31 { 32 if (node != null) 33 { 34 if (node.Left != null || node.Right != null) 35 { 36 this.Count++; 37 } 38 } 39 } 40 } 41 42 class LeaveCounter<T> : NodeWorker<T> 43 { 44 public int Count { get;set; } 45 public override void DoActionOnNode(BinaryTreeNode<T> node) 46 { 47 if (node != null) 48 { 49 if (node.Left == null && node.Right == null) 50 { 51 this.Count++; 52 } 53 } 54 } 55 } 56 57 class NodeCounter<T> : NodeWorker<T> 58 { 59 public int Count { get;set; } 60 public override void DoActionOnNode(BinaryTreeNode<T> node) 61 { 62 if (node != null) 63 { 64 this.Count++; 65 } 66 } 67 }