二叉樹的前序遍歷,中序遍歷,後序遍歷(Java實現)
阿新 • • 發佈:2019-01-30
1.前序遍歷
前序遍歷(DLR,lchild,data,rchild),是二叉樹遍歷的一種,也叫做先根遍歷、先序遍歷、前序周遊,可記做根左右。前序遍歷首先訪問根結點然後遍歷左子樹,最後遍歷右子樹。
前序遍歷首先訪問根結點然後遍歷左子樹,最後遍歷右子樹。在遍歷左、右子樹時,仍然先訪問根結點,然後遍歷左子樹,最後遍歷右子樹。若二叉樹為空則結束返回,否則:(1)訪問根結點。(2)前序遍歷左子樹。(3)前序遍歷右子樹 。前序遍歷需要注意的是:遍歷左右子樹時仍然採用前序遍歷方法。如右圖所示二叉樹前序遍歷結果:ABDECF已知後序遍歷和中序遍歷,就能確定前序遍歷。其實在遍歷二叉樹的時候有三次遍歷, 比如前序遍歷:A->B->D->D(D左子節點並返回到D)->D(D右子節點並返回到D)->B->E->E(左)->E(右)->->B->A->C->F->F(左)->F(右)->C->C(右),所以可以用棧結構,把遍歷到的節點壓進棧,沒子節點時再出棧。也可以用遞迴的方式,遞迴的輸出當前節點,然後遞迴的輸出左子節點,最後遞迴的輸出右子節點。直接看程式碼更能理解:
package test; //前序遍歷的遞迴實現與非遞迴實現 import java.util.Stack; public class Test { public static void main(String[] args) { TreeNode[] node = new TreeNode[10];//以陣列形式生成一棵完全二叉樹 for(int i = 0; i < 10; i++) { node[i] = new TreeNode(i); } for(int i = 0; i < 10; i++) { if(i*2+1 < 10) node[i].left = node[i*2+1]; if(i*2+2 < 10) node[i].right = node[i*2+2]; } preOrderRe(node[0]); } public static void preOrderRe(TreeNode biTree) {//遞迴實現 System.out.println(biTree.value); TreeNode leftTree = biTree.left; if(leftTree != null) { preOrderRe(leftTree); } TreeNode rightTree = biTree.right; if(rightTree != null) { preOrderRe(rightTree); } } public static void preOrder(TreeNode biTree) {//非遞迴實現 Stack<TreeNode> stack = new Stack<TreeNode>(); while(biTree != null || !stack.isEmpty()) { while(biTree != null) { System.out.println(biTree.value); stack.push(biTree); biTree = biTree.left; } if(!stack.isEmpty()) { biTree = stack.pop(); biTree = biTree.right; } } } } class TreeNode//節點結構 { int value; TreeNode left; TreeNode right; TreeNode(int value) { this.value = value; } }
2.中序遍歷
中序遍歷(LDR)是二叉樹遍歷的一種,也叫做中根遍歷、中序周遊。在二叉樹中,先左後根再右。巧記:左根右。中序遍歷首先遍歷左子樹,然後訪問根結點,最後遍歷右子樹若二叉樹為空則結束返回,否則:(1)中序遍歷左子樹(2)訪問根結點(3)中序遍歷右子樹如右圖所示二叉樹中序遍歷結果:DBEAFC
import java.util.Stack; public class Test { public static void main(String[] args) { TreeNode[] node = new TreeNode[10];//以陣列形式生成一棵完全二叉樹 for(int i = 0; i < 10; i++) { node[i] = new TreeNode(i); } for(int i = 0; i < 10; i++) { if(i*2+1 < 10) node[i].left = node[i*2+1]; if(i*2+2 < 10) node[i].right = node[i*2+2]; } midOrderRe(node[0]); System.out.println(); midOrder(node[0]); } public static void midOrderRe(TreeNode biTree) {//中序遍歷遞迴實現 if(biTree == null) return; else { midOrderRe(biTree.left); System.out.println(biTree.value); midOrderRe(biTree.right); } } public static void midOrder(TreeNode biTree) {//中序遍歷費遞迴實現 Stack<TreeNode> stack = new Stack<TreeNode>(); while(biTree != null || !stack.isEmpty()) { while(biTree != null) { stack.push(biTree); biTree = biTree.left; } if(!stack.isEmpty()) { biTree = stack.pop(); System.out.println(biTree.value); biTree = biTree.right; } } } } class TreeNode//節點結構 { int value; TreeNode left; TreeNode right; TreeNode(int value) { this.value = value; } }
3.後序遍歷(難點)
後序遍歷(LRD)是二叉樹遍歷的一種,也叫做後根遍歷、後序周遊,可記做左右根。後序遍歷有遞迴演算法和非遞迴演算法兩種。在二叉樹中,先左後右再根。巧記:左右根。後序遍歷首先遍歷左子樹,然後遍歷右子樹,最後訪問根結點,在遍歷左、右子樹時,仍然先遍歷左子樹,然後遍歷右子樹,最後遍歷根結點。即:若二叉樹為空則結束返回,否則:(1)後序遍歷左子樹(2)後序遍歷右子樹(3)訪問根結點如右圖所示二叉樹後序遍歷結果:DEBFCA已知前序遍歷和中序遍歷,就能確定後序遍歷。演算法核心思想:首先要搞清楚先序、中序、後序的非遞迴演算法共同之處:用棧來儲存先前走過的路徑,以便可以在訪問完子樹後,可以利用棧中的資訊,回退到當前節點的雙親節點,進行下一步操作。
後序遍歷的非遞迴演算法是三種順序中最複雜的,原因在於,後序遍歷是先訪問左、右子樹,再訪問根節點,而在非遞迴演算法中,利用棧回退到時,並不知道是從左子樹回退到根節點,還是從右子樹回退到根節點,如果從左子樹回退到根節點,此時就應該去訪問右子樹,而如果從右子樹回退到根節點,此時就應該訪問根節點。所以相比前序和後序,必須得在壓棧時新增資訊,以便在退棧時可以知道是從左子樹返回,還是從右子樹返回進而決定下一步的操作。
import java.util.Stack;
public class Test
{
public static void main(String[] args)
{
TreeNode[] node = new TreeNode[10];//以陣列形式生成一棵完全二叉樹
for(int i = 0; i < 10; i++)
{
node[i] = new TreeNode(i);
}
for(int i = 0; i < 10; i++)
{
if(i*2+1 < 10)
node[i].left = node[i*2+1];
if(i*2+2 < 10)
node[i].right = node[i*2+2];
}
postOrderRe(node[0]);
System.out.println("***");
postOrder(node[0]);
}
public static void postOrderRe(TreeNode biTree)
{//後序遍歷遞迴實現
if(biTree == null)
return;
else
{
postOrderRe(biTree.left);
postOrderRe(biTree.right);
System.out.println(biTree.value);
}
}
public static void postOrder(TreeNode biTree)
{//後序遍歷非遞迴實現
int left = 1;//在輔助棧裡表示左節點
int right = 2;//在輔助棧裡表示右節點
Stack<TreeNode> stack = new Stack<TreeNode>();
Stack<Integer> stack2 = new Stack<Integer>();//輔助棧,用來判斷子節點返回父節點時處於左節點還是右節點。
while(biTree != null || !stack.empty())
{
while(biTree != null)
{//將節點壓入棧1,並在棧2將節點標記為左節點
stack.push(biTree);
stack2.push(left);
biTree = biTree.left;
}
while(!stack.empty() && stack2.peek() == right)
{//如果是從右子節點返回父節點,則任務完成,將兩個棧的棧頂彈出
stack2.pop();
System.out.println(stack.pop().value);
}
if(!stack.empty() && stack2.peek() == left)
{//如果是從左子節點返回父節點,則將標記改為右子節點
stack2.pop();
stack2.push(right);
biTree = stack.peek().right;
}
}
}
}
class TreeNode//節點結構
{
int value;
TreeNode left;
TreeNode right;
TreeNode(int value)
{
this.value = value;
}
}
4.層次遍歷
與樹的前中後序遍歷的DFS思想不同,層次遍歷用到的是BFS思想。一般DFS用遞迴去實現(也可以用棧實現),BFS需要用佇列去實現。層次遍歷的步驟是:
1.對於不為空的結點,先把該結點加入到佇列中
2.從隊中拿出結點,如果該結點的左右結點不為空,就分別把左右結點加入到佇列中
3.重複以上操作直到佇列為空
public static void levelOrder(TreeNode biTree)
{//層次遍歷
if(biTree == null)
return;
LinkedList<TreeNode> list = new LinkedList<TreeNode>();
list.add(biTree);
TreeNode currentNode;
while(!list.isEmpty())
{
currentNode = list.poll();
System.out.println(currentNode.value);
if(currentNode.left != null)
list.add(currentNode.left);
if(currentNode.right != null)
list.add(currentNode.right);
}
}
先序遍歷特點:第一個值是根節點中序遍歷特點:根節點左邊都是左子樹,右邊都是右子樹