基于二叉树的先序,中序,后序,层序遍历以及非递归遍历算法
关于二叉树的先序,中序,后序,层序遍历以及非递归遍历
基于对二叉树的学习,自己手写了一个小demo,以此来加深对二叉树的印象!
手动创建二叉树
由于并没有对二叉树进行什么有序的建立,所以自己手动创建二叉树。
//创建一个二叉树
Node node5=new Node(5,null,null);
Node node4=new Node(4,null,node5);
Node node7=new Node(7,null,null);
Node node3=new Node(3,null,null);
Node node6=new Node(6,null,node7);
Node node2=new Node(2,node3,node6);
Node node1=new Node(1,node4,node2);
BinaryTree btree =new LinkedBinaryTree(node1);
建立如图所示的二叉树:
先序遍历
在先序遍历中,访问结点的顺序是根结点,左孩子,右孩子,即根左右,由于访问的每一个孩子结点都又可以看作是一个根节点,因此树可以通过递归来定义,所以树的常见操作用递归实现是非常方便的。
递归实现的代码如下:
//判断根节点是否为空
if(root != null) {
//先输出根结点的值
System.out.print(root.getValue()+" ");
//2.对左子树进行先序遍历
//构建一个二叉树,根是左子树的根
BinaryTree leftTree=new LinkedBinaryTree(root.getLeftChild());
leftTree.preOrderTraverse();
//3.对右子树进行先序遍历
//构建一个二叉树,根是右子树的根
BinaryTree rightTree=new LinkedBinaryTree(root.getRightChild());
rightTree.preOrderTraverse();
}
中序遍历
中序遍历的遍历思路与先序遍历非常相似,主要不同点在于访问结点的顺序不同:中序遍历顺序是先访问左孩子,再访问根结点,最后才访问右孩子,即左根右
实现代码如下:
@Override
public void inOrderTraverse() {
if(root != null) {
System.out.print("中序遍历:");
//调用方法进行遍历
this.inOrderTraverse(root);
System.out.println();
}
}
//由于中序遍历是左根右,先遍历左子树
private void inOrderTraverse(Node root) {
if(root != null) {
//遍历左子树
this.inOrderTraverse(root.getLeftChild());
//输出根的值
System.out.print(root.getValue()+" ");
//遍历右子树
this.inOrderTraverse(root.getRightChild());
}
}
后序遍历
后序遍历也是相似的,其访问顺序为先访问左孩子,在访问右孩子,最后访问根结点,即为左右根,
实现代码如下:
@Override
public void postOrderTraverse() {
if(root != null) {
System.out.print("后序遍历:");
this.postOrderTraverse(root);
System.out.println();
}
}
private void postOrderTraverse(Node root) {
if(root != null) {
//遍历左子树
this.postOrderTraverse(root.getLeftChild());
//遍历右子树
this.postOrderTraverse(root.getRightChild());
//输出根的值
System.out.print(root.getValue()+" ");
}
}
层次遍历
层次遍历则跟先序,中序,后序遍历截然不同,而是将二叉树分层,由左到右的顺序读取每一层次的结点
实现代码如下:
@Override
public void levelOrderByStack() {
System.out.print("按照层次遍历二叉树:");
if(root==null) return;
//创建一个队列
Queue<Node> queue =new LinkedList<Node>();
将根结点加入到队列中
queue.add(root);
//当队列不为空时,进入循环
while(queue.size() != 0) {
int len =queue.size();
//循环遍历队列中的每一个结点
for(int i=0;i<len;i++) {
Node temp=queue.poll();
System.out.print(temp.getValue()+" ");
if(temp.getLeftChild() != null) queue.add(temp.getLeftChild());
if(temp.getRightChild() != null) queue.add(temp.getRightChild());
}
}
System.out.println();
}
层次遍历运用了队列
- 当根节点不为空时,进入队列,以保证队列不为空进入for循环
- 进入for循环后根节点出队,获取其左孩子右孩子入队,并结束循环(以队列的长度为循环条件)
- 当根结点的左右孩子结点入队时队列不为空,并且队列长度为2,再次进入for循环,并循环两次
- 进入for循环后由代码可知根结点的左孩子先入队所以根节点的左孩子先出队,在获取其左孩子的左右孩子结点入队,再遍历根结点的右孩子,以此循环!
中序非递归遍历
中序遍历非递归操作
- 对于任意节点current,若该结点不为空则将该结点压栈,并将左子树结点设置为Current,重复此操作,直到左子树为空;
- 若左子树为空,栈顶结点出栈,访问结点后将该结点的右子树设置为Current;
- 重复1,2步操作,直到current为空且栈内结点为空
@Override
public void inOrderByStack() {
System.out.print("中序非递归遍历:");
Deque<Node> stack =new LinkedList<Node>();
Node current =root;
//当current不为空或者栈不为空时
while(current !=null || !stack.isEmpty()) {
while(current !=null) {
stack.push(current);
current=current.getLeftChild();
}
if(!stack.isEmpty()) {
current =stack.pop();
System.out.print(current.getValue()+" ");
current=current.getRightChild();
}
}
System.out.println();
}
后序非递归遍历
后续非递归遍历我觉得应该是二叉树遍历中最难的了吧,在后续非递归遍历中需要给一些可能再次遍历到的结点做上标记,比如当一个左结点已经没有了左孩子,但是还拥有右孩子时,此时这个结点是不能弹栈,必须将右孩子也遍历完才能将左结点弹栈,这就可能再次遍历到这个结点,所以需要将此结点做上标记。
后序遍历非递归操作
- 一直取得二叉树的左孩子,入栈,直到取出最左边的孩子加入到栈顶;
- 当取得最左边的结点时此时还不能弹栈,要查看此结点是否还拥有右孩子,当右孩子为空时才能将此节点弹栈,否则获取其右孩子继续遍历,并给此节点做上标记加入list集合中。
- 当再次遍历到最左边的结点时,将会再次判断其右孩子是否为空,不为空则可能继续获取其右孩子遍历进入死循环,这是标记则就起到了很大的作用,当此结点拥有右孩子时,并且list集合中并未对此结点标记过,才对其右孩子进行遍历,否则直接将此结点弹出。
实现代码如下:
//后序非递归遍历
@Override
public void postOrderByStack() {
System.out.println("后续非递归遍历:");
Deque<Node> stack =new LinkedList<Node>();
ArrayList<Node> list = new ArrayList<>();
Node current =root;
while(current !=null || !stack.isEmpty()) {
while(current != null) {
//一直取得二叉树的左孩子,直到左孩子不存在 取最左存入栈顶
stack.push(current);
current=current.getLeftChild();
}
if(!stack.isEmpty()) {
Node peek= stack.peek();
//peek方法取出栈顶结点但不弹栈,取出最左结点,判断是否有右孩子,没有则直接弹栈
if(peek.getRightChild()!=null) {
//给结点做一个标记,当出栈再次弹出这个结点时直接弹栈
boolean opinion=list.contains(peek);
//以做上标记直接弹栈
if(opinion==true) {
Node pop=stack.pop();
System.out.print(pop.getValue()+" ");
}else {
//否则取出其右孩子接着遍历
current=peek.getRightChild();
list.add(peek);//给此节点做上标记
}
}else {
Node pop=stack.pop();
System.out.print(pop.getValue()+" ");
}
}
}
}
完整代码
定义结点:
package cn.jxau.btree;
public class Node {
private Object value; //结点值
private Node leftChild;//左孩子
private Node rightChild;//右孩子
public Node() {
super();
}
public Node(Object value, Node leftChild, Node rightChild) {
super();
this.value = value;
this.leftChild = leftChild;
this.rightChild = rightChild;
}
public Object getValue() {
return value;
}
public void setValue(Object value) {
this.value = value;
}
public Node getLeftChild() {
return leftChild;
}
public void setLeftChild(Node leftChild) {
this.leftChild = leftChild;
}
public Node getRightChild() {
return rightChild;
}
public void setRightChild(Node rightChild) {
this.rightChild = rightChild;
}
@Override
public String toString() {
return "Node [value=" + value + ", leftChild=" + leftChild + ", rightChild=" + rightChild + "]";
}
}
定义树接口:
package cn.jxau.btree;
public interface BinaryTree {
/**
* 判断是否为空树
* @return
*/
public boolean isEmpty();
/**
* 树节点的数量
* @return
*/
public int size();
/**
* 获取二叉树的高度
* @return
*/
public int getHeight();
/**
* 查询指定值的结点
* @param value 指定值
* @return
*/
public Node findKey(int value);
/**
* 前序递归遍历
*/
public void preOrderTraverse();
/**
* 中序递归遍历
*/
public void inOrderTraverse();
/**
* 后序递归遍历
*/
public void postOrderTraverse();
/**
* 中序遍历非递归操作
*/
public void inOrderByStack();
/**
* 后序遍历非递归操作
*/
public void postOrderByStack();
/**
* 按照层次遍历二叉树
*/
public void levelOrderByStack();
}
树实现代码:
package cn.jxau.btree;
import java.util.Deque;
import java.util.LinkedList;
import java.util.Queue;
public class LinkedBinaryTree implements BinaryTree {
private Node root;//根结点
public LinkedBinaryTree() {
super();
}
public LinkedBinaryTree(Node root) {
super();
this.root = root;
}
//判断树是否为空
@Override
public boolean isEmpty() {
return root==null;
}
//获取树总的结点
@Override
public int size() {
System.out.println("二叉树结点个数:");
return this.size(root);
}
private int size(Node root) {
if(root ==null) {
return 0;
}else {
int nl=this.size(root.getLeftChild());
int nr=this.size(root.getRightChild());
return nl+nr+1;
}
}
//获取树的高度
@Override
public int getHeight() {
System.out.println("二叉树的高度是:");
return this.getHeight(root);
}
private int getHeight(Node root) {
if(root ==null) {
return 0;
}else {
int nl=this.getHeight(root.getLeftChild());
int nr=this.getHeight(root.getRightChild());
return nl>nr?nl+1:nr+1;
}
}
//根据指定值查询树是否含有该结点
@Override
public Node findKey(int value) {
return this.findKey(value,root);
}
private Node findKey(Object value, Node root) {
if(root==null) {
return null;
}else if(root!=null && root.getValue()==value) {
return root;
}else {
Node node1=this.findKey(value,root.getLeftChild());
Node node2=this.findKey(value,root.getRightChild());
if(node1 != null && node1.getValue()==value) {
return node1;
}else if(node2 !=null && node2.getValue()==value) {
return node2;
}else {
return null;
}
}
}
//先序遍历
@Override
public void preOrderTraverse() {
if(root != null) {
//输出根结点的值
System.out.print(root.getValue()+" ");
//2.对左子树进行先序遍历
//构建一个二叉树,根是左子树的根
BinaryTree leftTree=new LinkedBinaryTree(root.getLeftChild());
leftTree.preOrderTraverse();
//3.对右子树进行先序遍历
//构建一个二叉树,根是右子树的根
BinaryTree rightTree=new LinkedBinaryTree(root.getRightChild());
rightTree.preOrderTraverse();
}
}
//中序遍历
@Override
public void inOrderTraverse() {
if(root != null) {
System.out.print("中序遍历:");
this.inOrderTraverse(root);
System.out.println();
}
}
private void inOrderTraverse(Node root) {
if(root != null) {
//遍历左子树
this.inOrderTraverse(root.getLeftChild());
//输出根的值
System.out.print(root.getValue()+" ");
//遍历右子树
this.inOrderTraverse(root.getRightChild());
}
}
//后序遍历
@Override
public void postOrderTraverse() {
if(root != null) {
System.out.print("后序遍历:");
this.postOrderTraverse(root);
System.out.println();
}
}
private void postOrderTraverse(Node root) {
if(root != null) {
//遍历左子树
this.postOrderTraverse(root.getLeftChild());
//遍历右子树
this.postOrderTraverse(root.getRightChild());
//输出根的值
System.out.print(root.getValue()+" ");
}
}
//中序非递归遍历
@Override
public void inOrderByStack() {
System.out.print("中序非递归遍历:");
Deque<Node> stack =new LinkedList<Node>();
Node current =root;
while(current !=null || !stack.isEmpty()) {
while(current !=null) {
stack.push(current);
current=current.getLeftChild();
}
if(!stack.isEmpty()) {
current =stack.pop();
System.out.print(current.getValue()+" ");
current=current.getRightChild();
}
}
System.out.println();
}
//后序非递归遍历
@Override
public void postOrderByStack() {
System.out.println("后续非递归遍历:");
Deque<Node> stack =new LinkedList<Node>();
ArrayList<Node> list = new ArrayList<>();
Node current =root;
while(current !=null || !stack.isEmpty()) {
while(current != null) {
//一直取得二叉树的左孩子,直到左孩子不存在 取最左存入栈顶
stack.push(current);
current=current.getLeftChild();
}
if(!stack.isEmpty()) {
Node peek= stack.peek();
//peek方法取出栈顶结点但不弹栈,取出最左结点,判断是否有右孩子,没有则直接弹栈
if(peek.getRightChild()!=null) {
//给结点做一个标记,当出栈再次弹出这个结点时直接弹栈
boolean opinion=list.contains(peek);
if(opinion==true) {
Node pop=stack.pop();
System.out.print(pop.getValue()+" ");
}else {
current=peek.getRightChild();
list.add(peek);//做上标记
}
}else {
Node pop=stack.pop();
System.out.print(pop.getValue()+" ");
}
}
}
}
//层次遍历
@Override
public void levelOrderByStack() {
System.out.print("按照层次遍历二叉树:");
if(root==null) return;
Queue<Node> queue =new LinkedList<Node>();
queue.add(root);
while(queue.size() != 0) {
int len =queue.size();
for(int i=0;i<len;i++) {
Node temp=queue.poll();
System.out.print(temp.getValue()+" ");
if(temp.getLeftChild() != null) queue.add(temp.getLeftChild());
if(temp.getRightChild() != null) queue.add(temp.getRightChild());
}
}
System.out.println();
}
}