在計算機科學裏,樹的遍歷是指經過一種方法按照必定的順序訪問一顆樹的過程。html
對於二叉樹,樹的遍歷一般有四種:先序遍歷、中序遍歷、後序遍歷、廣度優先遍歷。(前三種亦統稱深度優先遍歷)對於多叉樹,樹的遍歷一般有兩種:深度優先遍歷、廣度優先遍歷。java
在學習前面三種深度優先遍歷以前,頗有必要了解它們之間究竟是怎麼遍歷的,要本身去親自去遍歷,不要只看文字node
先序遍歷: 節點 - 左孩子 - 右孩子c++
中序遍歷: 左孩子 - 根結點 - 右孩子算法
後序遍歷 : 左孩子 - 右孩子 - 根結點數組
看下面的圖(本身去寫出幾種遍歷的狀況):ide
按照上面的三種方法去遍歷,結果是學習
前序遍歷:- + a * b – c d / e f
中序遍歷:a + b * c – d – e / f
後序遍歷:a b c d – * + e f / -測試
廣度優先遍歷,又叫層次遍歷:從二叉樹的第一層(根結點)開始,自上至下逐層遍歷;在同一層中,按照從左到右的順序對結點逐一訪問。spa
在這幅圖裏所的結果是:- + / a * c f b – c d
下面給出java實現上面幾種遍歷的算法以及解釋:
三種遞歸的算法不說了,直接說非遞歸算法:
節點 - 左孩子 - 右孩子
1)訪問結點P,並將結點P入棧;
2)判斷結點P的左孩子是否爲空,若爲空,則取棧頂結點並進行出棧操做,並將棧頂結點的右孩子置爲當前的結點P,循環至1);若不爲空,則將P的左孩子置爲當前的結點P;
3)直到P爲NULL而且棧爲空,則遍歷結束。
有兩種實現方法:
public static void preOrder1_1(BinTree t){ Stack<BinTree> s = new Stack<BinTree>(); while(t != null || !s.empty()){ //只要知足節點非空或則棧爲非空,就要不斷遍歷 while(t !=null ){ //壓入全部左孩子節點,壓入前輸出 System.out.print(t.date); s.push(t); t = t.lchild; } if(!s.empty()){ //左孩子都搞定了,從棧頂取出一個節點,指向它的右孩子,繼續前面while循環 t = s.pop(); t = t.rchild; } } System.out.println(); } public static void preOrder1_2(BinTree t){ Stack<BinTree> stack = new Stack<BinTree>(); if(t!=null) { stack.push(t); while(!stack.empty()) { t = stack.pop(); System.out.print(t.date); if(t.rchild!=null) //先把右孩子壓入棧,利用下壓棧的特性 stack.push(t.rchild); if(t.lchild!=null) stack.push(t.lchild); } } }
中序非遞歸算法:
左孩子 - 根結點 - 右孩子
*訪問任意一節點p,若其左孩子非空,p入棧,且p的左孩子做爲當前節點,而後在對其進行一樣的處理
*若其左孩子爲空,則輸出棧頂點元素並進行出棧操做,訪問該棧頂的節點的右孩子
*直到p爲null而且棧爲空則遍歷結束
(和前序的非遞歸算法1_1有類似之處)
public static void InOrder2_1(BinTree t){ Stack<BinTree> s = new Stack<BinTree>(); while(t != null || !s.empty()){ while(t !=null){ //和前序的非遞歸有點像 s.push(t); t = t.lchild; //一路指向左孩子 } if(! s.empty()){ t = s.pop(); //取棧頂元素並輸出,並指向右孩子 System.out.print(t.date); t = t.rchild; } } System.out.println(); }
後序非遞歸算法
左孩子 - 右孩子 - 根結點
這個有難度 由於要保證左孩子和右孩子都已經被訪問過了才訪問根節點,這裏就要思考一下如何標記一下右孩子是否已經被標記了。有兩種方法:
雙棧法(這個比較容易理解)
* 對於跟節點t,先入棧,而後沿着其左子樹往下收索,直到沒有左子樹的節點,此時該節點入棧
* 但此時不能進行出棧訪問,要檢查右孩子,因此接下來一相同的規則進行處理,當訪問其右孩子時,
* 該節點又出如今棧頂,此時就能夠出棧訪問,這樣就保證了正確的訪問順序,這個過程當中,
* 每一個節點都兩次出如今棧頂,只有在第二次出如今棧頂的時候才能訪問它, 所以有必要設置兩個棧
public static void PostOrder3_1(BinTree t){ Stack<BinTree> s = new Stack<BinTree>(); Stack<Integer> s2 = new Stack<Integer>(); Integer i = new Integer(1); //第二個棧有0 1 倆元素,1表示第二次訪問,0,表示第一次訪問 while(t!=null || !s.empty()){ while(t!=null){ //一路壓入左孩子 s.push(t); s2.push(new Integer(0)); t = t.lchild; } //peek查看堆棧頂部的對象,但不從堆棧中移除它。 while(!s.empty() && s2.peek().equals(i)){ //有兩次訪問的記錄了,就出棧且輸出啦,倆棧是同步pop的 s2.pop(); System.out.print(s.pop().date); } if(!s.empty()){ //第二次訪問了,棧2的元素置爲1,這回要指向右孩子 s2.pop(); s2.push(new Integer(1)); t = s.peek(); t = t.rchild; } } }
單棧法:
* 先掃描跟節點的全部左節點併入棧,接着將棧頂元素出棧,
* 再掃描該節點的右孩子節點併入棧,掃描右孩子的全部左節點併入棧,當一個節點的左右孩子均被訪問後在訪問該節點,
* 這裏用一個初始值爲null的節點表示右子樹剛剛被訪問的
* 一直到棧爲空,這裏的難點是如何判斷右孩子已經被訪問過了
這個構造真的是太讚了!!
public static void PostOrder3_2(BinTree t){ BinTree q = null; Stack<BinTree> stack = new Stack<BinTree>(); while(t!=null) { while(t.lchild!=null) //先左子樹入棧,注意:這裏最左節點(葉子)沒有入棧,後面才入棧 { stack.push(t); t = t.lchild; } while(t.rchild==null || t.rchild ==q ) { System.out.print(t.date); //當前節點無右孩子或者右孩子已經輸出 q = t; //用來記錄上一個節點 if(stack.empty()) return ; //棧爲空時,就結束程序 t = stack.pop(); //節點出棧 } stack.push(t); //!一、葉子節點入棧,指向的r.child是null 二、處理右孩子 t = t.rchild; } }
另一個單棧實現,單棧的構造都好神奇!
public static void PostOrder3_3(BinTree t){ BinTree node = t, prev = t; Stack<BinTree> stack = new Stack<BinTree>(); while(node != null || stack.size()>0) { while(node!=null) //壓入左孩子 { stack.push(node); node = node.lchild; } if(!stack.empty()) { BinTree temp = stack.peek().rchild; //記錄當前節點的右孩子 if((temp == null)||temp == prev) { node = stack.pop(); //知足倆條件就出棧輸出 System.out.print(node.date); prev = node; //標記已經被訪問 node = null; //把這個沒用的節點置爲null.,防止執行上面的while循環 } else{ node = temp; } } } }
若是用數組去實現stack的話,能夠用這個,這個是在求解兩節點個最近公共祖先時用到的(LCA算法),改寫自考研書上
//棧是用數組實現的 後序非遞歸算法,數組裏含有全部的元素 public static void PostOrder3_4(BinTree t) { BinTree st[] = new BinTree[1000]; //用數組實現順序棧 int flag,top=-1; //棧指針初始化 BinTree p =t,q; do { while(p!=null) //將p的全部左節點入棧 { top++; st[top] = p; p = p.lchild; } q = null; //q指向棧頂節點的前一個已經訪問的節點 flag = 1; //設置flag = 1表示處理棧頂節點 while(top !=-1 && flag ==1) { p = st[top]; //取出棧頂的元素 if(p.rchild==q) //右孩子不存在或者右孩子已經被訪問,訪問之 { System.out.print(p.date); top--; q=p; //q指向剛剛被訪問的節點 } else { p = p.rchild; //p指向右孩子節點 flag =0; //flag=0表示棧頂節點處理完畢 } } }while(top!=-1); //棧不爲空時循環 System.out.println(); }
下面是所有的代碼外加一個測試實例:
/*************************************** * 時間:2013年12月2日 * author:lm * 內容:二叉樹前中後遞歸與非遞歸遍歷,以及廣度優先遍歷 ***************************************/ import java.util.ArrayDeque; import java.util.Queue; import java.util.Stack; public class BinTree { private char date; private BinTree lchild; //左孩子 private BinTree rchild; //右孩子 private BinTree(char c ){ date = c; } //先序遍歷 遞歸實現 節點 - 左孩子 - 右孩子 public static void preOrder1(BinTree t){ if(t != null){ System.out.print(t.date); preOrder1(t.lchild); preOrder1(t.rchild); } } //中序遍歷 遞歸實現 左孩子 - 根結點 - 右孩子 public static void InOrder2(BinTree t){ if(t != null){ InOrder2(t.lchild); System.out.print(t.date); InOrder2(t.rchild); } } //後序遍歷 遞歸實現 左孩子 - 右孩子 - 根結點 public static void PostOrder3(BinTree t){ if(t!=null) { PostOrder3(t.lchild); PostOrder3(t.rchild); System.out.print(t.date); } } /*前序非遞歸實現 節點 - 左孩子 - 右孩子 1)訪問結點P,並將結點P入棧; 2)判斷結點P的左孩子是否爲空,若爲空,則取棧頂結點並進行出棧操做,並將棧頂結點的右孩子置爲當前的結點P, 循環至1);若不爲空,則將P的左孩子置爲當前的結點P; 3)直到P爲NULL而且棧爲空,則遍歷結束。 */ public static void preOrder1_1(BinTree t){ Stack<BinTree> s = new Stack<BinTree>(); while(t != null || !s.empty()){ //只要知足節點非空或則棧爲非空,就要不斷遍歷 while(t !=null ){ //壓入全部左孩子節點,壓入前輸出 System.out.print(t.date); s.push(t); t = t.lchild; } if(!s.empty()){ //左孩子都搞定了,從棧頂取出一個節點,指向它的右孩子,繼續前面while循環 t = s.pop(); t = t.rchild; } } System.out.println(); } public static void preOrder1_2(BinTree t){ Stack<BinTree> stack = new Stack<BinTree>(); if(t!=null) { stack.push(t); while(!stack.empty()) { t = stack.pop(); System.out.print(t.date); if(t.rchild!=null) //先把右孩子壓入棧,利用下壓棧的特性 stack.push(t.rchild); if(t.lchild!=null) stack.push(t.lchild); } } } /*中序非遞歸的實現 左孩子 - 根結點 - 右孩子 * 訪問任意一節點p,若其左孩子非空,p入棧,且p的左孩子做爲當前節點,而後在對其進行一樣的處理 * 若其左孩子爲空,則輸出棧頂點元素並進行出棧操做,訪問該棧頂的節點的右孩子 * 直到p爲null而且棧爲空則遍歷結束 */ public static void InOrder2_1(BinTree t){ Stack<BinTree> s = new Stack<BinTree>(); while(t != null || !s.empty()){ while(t !=null){ //和前序的非遞歸有點像 s.push(t); t = t.lchild; //一路指向左孩子 } if(! s.empty()){ t = s.pop(); //取棧頂元素並輸出,並指向右孩子 System.out.print(t.date); t = t.rchild; } } System.out.println(); } /*後序非遞歸實現 這個有難度 由於要保證左孩子和右孩子都已經被訪問過了才訪問根節點 * 對於任一節點p,先入棧,而後沿着其左子樹往下收索,直到沒有左子樹的節點,此時該節點入棧 * 但此時不能進行出棧訪問,要檢查右孩子,因此接下來一相同的規則進行處理,當訪問其右孩子時, * 該節點又出如今棧頂,此時就能夠出棧訪問,這樣就保證了正確的訪問順序,這個過程當中, * 每一個節點都兩次出如今棧頂,只有在第二次出如今棧頂的時候才能訪問它, 所以有必要設置兩個棧 */ public static void PostOrder3_1(BinTree t){ Stack<BinTree> s = new Stack<BinTree>(); Stack<Integer> s2 = new Stack<Integer>(); Integer i = new Integer(1); //第二個棧有0 1 倆元素,1表示第二次訪問,0,表示第一次訪問 while(t!=null || !s.empty()){ while(t!=null){ //壓入左孩子 s.push(t); s2.push(new Integer(0)); t = t.lchild; } //peek查看堆棧頂部的對象,但不從堆棧中移除它。 while(!s.empty() && s2.peek().equals(i)){ //有兩次訪問的記錄了,就出棧且輸出啦,倆棧是同步pop的 s2.pop(); System.out.print(s.pop().date); } if(!s.empty()){ //第二次訪問了,棧2的元素置爲1,這回要指向右孩子 s2.pop(); s2.push(new Integer(1)); t = s.peek(); t = t.rchild; } } } /* * 先掃描跟節點的全部左節點併入棧,接着將棧頂元素出棧, * 再掃描該節點的右孩子節點併入棧,掃描右孩子的全部左節點併入棧,當一個節點的左右孩子均被訪問後在訪問該節點, * 這裏用一個初始值爲null的節點表示右子樹剛剛被訪問的 * 一直到棧爲空,這裏的難點是如何判斷右孩子已經被訪問過了 * 我的以爲這個構造真的是太讚了!! */ public static void PostOrder3_2(BinTree t){ BinTree q = null; Stack<BinTree> stack = new Stack<BinTree>(); while(t!=null) { while(t.lchild!=null) //先左子樹入棧,注意:這裏最左節點(葉子)沒有入棧,後面才入棧 { stack.push(t); t = t.lchild; } while(t.rchild==null || t.rchild ==q ) { System.out.print(t.date); //當前節點無右孩子或者右孩子已經輸出 q = t; //用來記錄上一個節點 if(stack.empty()) return ; //棧爲空時,就結束程序 t = stack.pop(); //節點出棧 } stack.push(t); //!奇葩 一、葉子節點入棧,指向的r.child是null 二、不知足while條件時,處理右孩子 t = t.rchild; } } public static void PostOrder3_3(BinTree t){ BinTree node = t, prev = t; Stack<BinTree> stack = new Stack<BinTree>(); while(node != null || stack.size()>0) { while(node!=null) //壓入左孩子 { stack.push(node); node = node.lchild; } if(!stack.empty()) { BinTree temp = stack.peek().rchild; //記錄當前節點的右孩子 if((temp == null)||temp == prev) { node = stack.pop(); //知足倆條件就出棧輸出 System.out.print(node.date); prev = node; //標記已經被訪問 node = null; //把這個沒用的節點置爲null.,防止執行上面的while循環 } else{ node = temp; } } } } //棧是用數組實現的後序非遞歸算法,數組裏含有全部的元素 public static void PostOrder3_4(BinTree t) { BinTree st[] = new BinTree[1000]; //用數組實現順序棧 int flag,top=-1; //棧指針初始化 BinTree p =t,q; do { while(p!=null) //將p的全部左節點入棧 { top++; st[top] = p; p = p.lchild; } q = null; //q指向棧頂節點的前一個已經訪問的節點 flag = 1; //設置flag = 1表示處理棧頂節點 while(top !=-1 && flag ==1) { p = st[top]; //取出棧頂的元素 if(p.rchild==q) //右孩子不存在或者右孩子已經被訪問,訪問之 { System.out.print(p.date); top--; q=p; //q指向剛剛被訪問的節點 } else { p = p.rchild; //p指向右孩子節點 flag =0; //flag=0表示棧頂節點處理完畢 } } }while(top!=-1); //棧不爲空時循環 System.out.println(); } /*廣度優先遍歷樹,又叫層次遍歷 * 1.首先將根節點放入隊列中。 2.當隊列爲非空時,循環執行步驟3到步驟5,不然執行6; 3.出隊列取得一個結點,訪問該結點; 4.若該結點的左子樹爲非空,則將該結點的左子樹入隊列; 5.若該結點的右子樹爲非空,則將該結點的右子樹入隊列; 6.結束。 */ /* * 補充:隊列是什麼,提到隊列,就要想到先進先進先出,即爲先進先出隊列,先進來的人先服務,咱們平常生活 * 排隊時不是先到的人先接受服務麼? */ public static void BFSOrder(BinTree t) { if(t==null) return ; Queue<BinTree> queue = new ArrayDeque<BinTree>(); //隊列小知識:使用offer和poll優於add和remove之處在於它們返回值能夠判斷成功與否,而不拋出異常 queue.offer(t); //進入隊列 while(!queue.isEmpty()) { t=queue.poll(); //當前節點出隊列 System.out.print(t.date); if(t.lchild!=null) //當前節點左孩子去排隊,在前面哦 queue.offer(t.lchild); if(t.rchild!=null) //右孩子排第二 queue.offer(t.rchild); } } public static void main(String[] args) { BinTree b1 = new BinTree('a'); BinTree b2 = new BinTree('b'); BinTree b3 = new BinTree('c'); BinTree b4 = new BinTree('d'); BinTree b5 = new BinTree('e'); BinTree b6 = new BinTree('f'); BinTree b7 = new BinTree('g'); /** * a * / \ * b c * / \ / \ * d e f g */ b1.lchild = b2; b1.rchild = b3; b2.lchild = b4; b2.rchild = b5; b3.lchild = b6; b3.rchild = b7; BinTree.preOrder1(b1); System.out.println(); BinTree.preOrder1_1(b1); System.out.println(); BinTree.InOrder2(b1); System.out.println(); BinTree.InOrder2_1(b1); System.out.println(); BinTree.PostOrder3(b1); System.out.println(); BinTree.PostOrder3_1(b1); System.out.println(); BinTree.PostOrder3_2(b1); System.out.println(); BinTree.PostOrder3_3(b1); System.out.println(); BinTree.PostOrder3_4(b1); System.out.println(); BinTree.BFSOrder(b1); System.out.println(); } }
網上有好的很好的文章都有寫道這幾個搜索算法,這個博客是用c++實現的
http://www.blogjava.net/fancydeepin/archive/2013/02/03/cpp_binarytreesearch.html