二叉树的建树和七种遍历(含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中依次出栈访问结点。