二叉樹的建樹和七種遍歷(含Java代碼)
詳細代碼請訪問我的github:二叉樹操作
二叉樹結點定義
一個樹結點最基本的字段就是三個字段,分別代表該結點的值和它的左右孩子結點。
public class TreeNode {
int val;//該結點的值
TreeNode left;//結點的左孩子結點
TreeNode right;//結點的右孩子結點
TreeNode(int x) {//構造方法
val = x;
}
}
創建二叉樹
將下圖的二叉樹從上至下、從左至右存放在一個一維數組中,對應的一維數組爲[1,2,3,4,5,6,7,8,9,10]。
可以明顯看出在數組中下標爲i的元素所對應的結點的左右結點在數組中的下標爲2i+1和2i+2。因此,給定一個數組,我們可以從第一個元素開始遞歸創建二叉樹。
public TreeNode buildTree(int[] nums, int i){
if(nums.length==0)
return null;
if(i>=nums.length)
return null;
TreeNode root = new TreeNode(nums[i]);
root.left = buildTree(nums,2*i+1);
root.right = buildTree(nums,2*i+2);
return root;
}
其中nums是給定的數組,i初始下標從0開始,返回值是一個樹結點。首先創建值爲nums[i]的結點,也是buildTree返回的樹結點。從上圖的結論易知其左右孩子結點的位置分別爲2i+1=1和2i+2=2,因此使其左右結點分別指向buildTree(nums,2i+1)和root.right = buildTree(nums,2i+2)返回的樹結點,通過遞歸創建二叉樹。
二叉樹的層序遍歷
所謂層序遍歷:即從上到下、從左至右依次遍歷二叉樹。
可以藉助一個隊列,通過隊列先進先出的特點,首先將根結點放入隊列,只要隊列不空,就從隊列中取出一個結點,再依次將其左右結點放入隊列。由於每次都是父結點出隊列後,子結點才進隊列,而且是保持先左後右的順序,所以能保證對樹的遍歷是從上到下、從左至右的依次遍歷。
public void levelOrder(TreeNode root){
if(root==null)
return;
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(root);
while(!queue.isEmpty()){
TreeNode node = queue.poll();
System.out.print(node.val+ " ");
if(node.left!=null)
queue.offer(node.left);
if(node.right!=null)
queue.offer(node.right);
}
}
結合創建二叉樹和層序遍歷二叉樹,在主類中測試:
int[] nums = {1,2,3,4,5,6,7,8,9,10};
BinaryTree tree = new BinaryTreeImpl();
//創建二叉樹
TreeNode root = tree.buildTree(nums,0);
System.out.println("二叉樹的層序遍歷");
tree.levelOrder(root);//結果輸出爲:1 2 3 4 5 6 7 8 9 10
二叉樹的先序遍歷
先序遍歷是指:先訪問根結點,再依次訪問其左右結點。可以使用兩種方式實現二叉樹的先序遍歷:遞歸遍歷和非遞歸遍歷。
遞歸遍歷
遞歸方法的參數是一個樹結點,開始時是樹的根結點。如果該結點爲null,則回到上一層結點,否則輸出該結點的值,然後先遍歷其左子樹,再遍歷其右子樹。
public void preOrder(TreeNode root){
if(root==null)
return;
System.out.print(root.val+" ");
preOrder(root.left);
preOrder(root.right);
}
非遞歸遍歷
在計算機中,對遞歸方法的調用是通過棧實現的。如果不使用遞歸方式,由程序員自己創建一個棧就能通過非遞歸方式實現先序遍歷。
public void preNonrecursion(TreeNode root) {
if(root!=null){
Stack<TreeNode> stack = new Stack<>();
stack.push(root);
while(!stack.empty()){
TreeNode t = stack.pop();
System.out.print(t.val+" ");
if(t.right!=null)
stack.push(t.right);
if(t.left!=null){
stack.push(t.left);
}
}
}
}
由於遞歸是先進後出,所以進棧時右結點先進棧,然後左節點再進棧。
二叉樹的中序遍歷
中序遍歷是指:先訪問左子樹(結點),再訪問根結點,最後再訪問右子樹(結點)。
public void inOrder(TreeNode root){
if(root==null)
return;
inOrder(root.left);
System.out.print(root.val + " ");
inOrder(root.right);
}
類似的,根據先序遍歷的非遞歸實現,我們也可以實現中序遍歷的非遞歸實現。
public void inNonrecursion(TreeNode root) {
if(root!=null){
Stack<TreeNode> stack = new Stack<>();
TreeNode q = root;
while(!stack.empty()||q!=null){
while(q!=null){
stack.push(q);
q=q.left;
}
if(!stack.empty()){
q=stack.pop();
System.out.print(q.val+" ");
q=q.right;
}
}
}
}
從代碼中可以看出每一次根結點都在左節點進棧前進棧,等輸出該結點值之後,其右節點才進棧,因此每一個結點的上一個輸出結點要麼是其左節點,要麼爲null不輸出值。
二叉樹的後序遍歷
後序遍歷是指:先依次訪問左右結點,最後訪問根結點。
類似於先序和中序遍歷,我們可以寫出後序遍歷的遞歸和非遞歸方法。
遞歸方法
public void postOrder(TreeNode root){
if(root==null)
return;
postOrder(root.left);
postOrder(root.right);
System.out.print(root.val + " ");
}
非遞歸方法
與先序遍歷和中序遍歷的非遞歸操作不同,因爲在後序遍歷中,要保證左孩子和右孩子都已被訪問並且左孩子在右孩子前訪問才能訪問根結點,這裏我藉助了兩個棧。
public void postNonrecursion(TreeNode root) {
if(root!=null){
Stack<TreeNode> stack1 = new Stack<>();
Stack<TreeNode> stack2 = new Stack<>();
TreeNode q = null;
stack1.push(root);
while(!stack1.empty()){
q=stack1.pop();
stack2.push(q);
if(q.left!=null)
stack1.push(q.left);
if(q.right!=null)
stack1.push(q.right);
}
while(!stack2.empty()){
q = stack2.pop();
System.out.print(q.val+" ");
}
}
}
由於根結點是最後訪問的,所以每次從stack1中取出結點後壓入stack2,再將其左右結點依次壓入stack1,等stack1中的元素都壓入stack2之後,從stack2中依次出棧訪問結點。