二叉樹遍歷演算法
摘要:二叉樹主要有3種遍歷演算法,分為為先序、中序、後序。本文對二叉樹的3種遍歷演算法的遍歷規則進行介紹,並給出3種遍歷演算法的遞迴和迭代版本的C++實現。文章發出後,我的好朋友指出還有一個層次遍歷,我將在文章最後介紹。
1. 二叉樹遍歷
如圖1所示,一顆二叉樹T由根節點V、左子樹L和右子樹R構成。

圖1 二叉樹示意圖
則二叉樹T的遍歷指:按照某種次序訪問樹中各節點,每個節點被訪問恰好一次。二叉樹T的先序、中序、後序遍歷的結果如圖2所示。

圖2 二叉樹的先序、中序、後序遍歷
從圖中可以看出,先序、中序、後序的區別在於區域性根節點的訪問時機,而對於左右孩子的訪問,其訪問順序總是滿足先左後右的規則。下面舉例說明二叉樹的遍歷規則,一棵二叉樹的結構如圖3所示,則其遍歷結果如下:
先序遍歷結果:A B D E G H C F
中序遍歷結果:D B G H E A C F
後序遍歷結果:D H F E B G C A

圖3 一顆普通的二叉樹
2. 二叉樹節點
在實現二叉樹的遍歷演算法之前,我們先建立一個二叉樹節點類。二叉樹節點是二叉樹的基本組成單位,節點與節點之間有父子關係、兄弟關係等,由於節點的資料型別不確定,所以採用模板實現二叉樹的節點類。
/* 二叉樹節點 */ #define BiNodePos(T) BiNode<T>* template<typename T> struct BiNode{ BiNodePos(T) parent= nullptr, lChild = nullptr, rChild = nullptr; T data; int height; BiNode(T data, BiNodePos(T) parent){ this->data = data; this->parent = parent; } //子樹規模 int size() { int s = 1;//計入本身 if (lChild) s += lChild.size();//遞迴計入左子樹規模 if (rChild) s += rChild.size();//遞迴計入右子樹規模 return s; } //插入左孩子 BiNodePos(T) insertAsLC(const T &) { return lChild = new BiNode(e, this); } //插入右孩子 BiNodePos(T) insertAsRC(const T &) { return rChild = new BiNode(e, this); } };
3. 二叉樹遍歷演算法的實現
3.1. 遞迴版本
#include <stack> using namespace std; //先序遍歷 template<typename T, typename VST > void preTraverse(BiNodePos(T) x, VST& visit) { if (!x) return; visit(x->data); preTraverse(x->lChild); preTraverse(x->rChild); }//O(n) //中序遍歷 template<typename T, typename VST > void midTraverse(BiNodePos(T) x, VST& visit) { if (!x) return; midTraverse(x->lChild); visit(x->data); midTraverse(x->rChild); }//O(n) //後序遍歷 template<typename T, typename VST > void postTraverse(BiNodePos(T) x, VST& visit) { if (!x) return; postTraverse(x->lChild); postTraverse(x->rChild); visit(x->data); }//O(n)
3.2. 迭代版本
遞迴版本的時間複雜度是O(n),理論上遞迴遍歷演算法已經事件複雜度已經最小了。但實際上,由於遞迴例項必須具有通用的格式,所以在執行棧中,每一個遞迴例項對應的一幀不可能做到足夠的小。而針對於具體的問題,我們完全可以通過精巧的設計使得每一幀做到足夠小的,所以,我們有必要將遞迴版本轉化為迭代版本。
#include <stack> using namespace std; template<typename T, typename VST> static void visitAlongLeftBranch(BiNodePos(T) x, VST& visit, stack<BiNodePos(T)> &s) { while (x!=nullptr) { visit(x->data); s.push(x->rChild); x = x->lChild; } } template<typename T, typename VST > void preIterationTraverse(BiNodePos(T) x, VST& visit) { stack<BiNodePos(T)> s; while (true) { visitAlongLeftBranch(x, visit, s); if (s.empty) break; x = s.top(); s.pop(); } }//O(n) template<typename T> static void goAlongLeftBranch(BiNodePos(T) x,stack<BiNodePos(T)> &s) { while (x!=nullptr) { s.push(x); x = x->lChild; } } template<typename T, typename VST > void inIterationTraverse(BiNodePos(T) x, VST& visit) { stack<BiNodePos(T)> s; while (true) { goAlongLeftBranch(x, s); if (s.empty) break; x = s.top(); s.pop(); visit(x->data); x = x->rChild; } }//O(n) template < typename T> static void gotoLVFL(stack<BiNodePos(T)> s) { BiNodePos(T) node; while (node = s.top()) { if (node->lChild){ if (node->rChild) s->push(node->rChild); s->push(node->lChild); } else s->push(node->rChild); } s.pop(); } template<typename T, typename VST > void postIterationTraverse(BiNodePos(T) x, VST& visit) { stack<BiNodePos(T)> s; if (x!=nullptr) s.push(x); while (!s.empty()) { if (s.top() != x->parent) gotoLVFL<T>(s); x = s.top(); visit(x->data); } }//O(n)
4. 層次遍歷
首先,感謝大佬幫我指正問題。
層次遍歷是指:按層次遍歷樹的所有節點(即逐層地,從左到右訪問所有節點)。下面是層次遍歷的迭代實現。
#include <deque> using namespace std; template<typename T, typename VST > void levelIterationTraverse(BiNodePos(T) x, VST& visit) { deque<BiNodePos(T)> q; q.push_back(x); while (!q.empty()) { x = q.front(); q.pop_front(); visit(x.data); if (x->lChild) q.push_back(x->lChild); if (x->rChild) q.push_back(x->rChild); } }//O(n)