前言
本文是我在学习了树后作的总结文章,接上篇文章,本节大致可以总结为:
- 二叉树的遍历与实现(递归和非递归)
- 获取二叉树的高度和度
- 创建一棵二叉树
- 其他应用(层序遍历,复制二叉树,判断二叉树是否相等)
文章传送门:
树的基本概念以及java实现二叉树(一):https://blog.youkuaiyun.com/qingtian_1993/article/details/80637917
树的基本概念以及java实现二叉树(二):https://blog.youkuaiyun.com/qingtian_1993/article/details/80877487
代码传送门,欢迎star:https://github.com/mcrwayfun/java-data-structure
正文
4 二叉树的遍历与实现
二叉树遍历:从树的根节点出发,按照某种次序依次访问二叉树中所有的结点,使得每个结点被访问仅且一次。普遍有三种遍历方式,前序、中序和后序;这里有两个关键词:访问和次序。有一个基本思想要注意下:一个根结点+左右子树均可以看作一棵二叉树
4.1 递归实现二叉树的遍历
4.1.1 前序遍历
基本思想:若二叉树为空,则返回。否则从根结点开始,优先访问根结点,再前序遍历左子树,前序遍历右子树,即根——左——右
图中按照前序遍历的访问结果为:A、B、D、G、H、C、E、I、F
使用代码递归来实现前序遍历,如下所示:
/**
* 前序遍历(中左右)
* output:A、B、D、G、H、C、E、I、F
* @param root
*/
public void preOrder(TreeNode root) {
if (root == null) {
return;
} else {
System.out.println("preOrder data:" + root.getData());
preOrder(root.leftChild);
preOrder(root.rightChild);
}
}
4.1.2 中序遍历
基本思想:若二叉树为空,则返回。否则优先中序遍历左子树,再访问根结点,再后序遍历右子树,即左——根——右
图中按照中序遍历的访问结果为:G、D、H、B、A、E、I、C、F
使用代码递归来实现中序遍历,如下所示:
/**
* 中序遍历(左中右)
* output:G、D、H、B、A、E、I、C、F
* @param root
*/
public void midOrder(TreeNode root) {
if (root == null) {
return;
} else {
midOrder(root.leftChild);
System.out.println("midOrder data:" + root.getData());
midOrder(root.rightChild);
}
}
4.1.3 后序遍历
基本思想:若二叉树为空,则返回。否则优先后序遍历左子树,再后序遍历右子树,最后访问根结点,,即左——右——根
图中按照后序遍历的访问结果为:G、H、D、B、I、E、F、C、A
使用代码递归来实现后序遍历,如下所示:
/**
* 后序遍历(左右中)
* output:G、H、D、B、I、E、F、C、A
* @param root
*/
public void postOrder(TreeNode root){
if (root == null) {
return;
} else {
postOrder(root.leftChild);
postOrder(root.rightChild);
System.out.println("postOrder data:" + root.getData());
}
}
4.2 非递归实现二叉树的遍历
4.2.1 利用栈进行前序遍历
每访问一个结点后,在向左子树遍历下去之前,利用栈来记录该结点的右子女(如果有的话),以便在左子树退回时可以直接从栈顶取得右子树的根结点,继续其右子树的遍历。上图是过程的演示,先将null压入栈中,当栈中无元素时将其推出,表示结束。
/**
* 前序遍历非递归算法
* output:A-B-D-E-C-F
* @param root
*/
public void preOrderNonRecursive(TreeNode root) {
Stack<TreeNode> nodeStack = new Stack<>();
nodeStack.push(null);
while (root != null) {
// 访问根结点
System.out.println("preOrderNonRecursive data:" + root.getData());
// 当前结点右子树不为空则放入栈中
if (root.rightChild != null)
nodeStack.push(root.rightChild);
// 访问左子树
if (root.leftChild != null)
root = root.leftChild;
else root = nodeStack.pop();
}
}
4.2.2 利用栈进行中序遍历
从根结点开始沿着leftChild到最下角的结点,将指针依次压入栈中,直到该结点的leftChild指针为NULL。访问它的数据后,再遍历该结点的右子树。此时该结点为栈中推出的指针。
/**
* 中序遍历非递归算法
* output:D-B-E-A-F-C
* @param root
*/
public void midOrderNonRecursive(TreeNode root) {
Stack<TreeNode> nodeStack = new Stack<>();
do {
while (root != null) {
nodeStack.push(root);
root = root.leftChild;
}
if (!nodeStack.empty()) {
root = nodeStack.pop();
System.out.println("preOrderNonRecursive data:" + root.getData());
root = root.rightChild;
}
} while (root != null || !nodeStack.empty());
}
4.2.3 利用栈进行后序遍历
因为后序遍历的访问顺序为左右根,所以在访问的时候比较麻烦,需要考虑到访问完左结点后,当前结点有无右结点需要访问,若有则需要右进访问右子树,所以要有一个变量来记录当前结点。
- 从根结点开始沿着leftChild到最下角的结点,将指针依次压入栈中,直到该结点的leftChild指针为NULL。
- 判断当前结点有无右子树,若有,则优先访问右子树
- 无右子树货已经访问过右子树则访问当前结点
/**
* 后序遍历非递归算法
* output:D-E-B-F-C-A
*
* @param root
*/
public void postOrderNonRecursive(TreeNode root) {
Stack<TreeNode> nodeStack = new Stack<>();
// 上一个结点
TreeNode prev = root;
do {
while (root != null) {
nodeStack.push(root);
root = root.leftChild;
}
// 访问当前结点的右结点
if (!nodeStack.empty()) {
// 获取右子树,但先不弹出
TreeNode temp = nodeStack.peek().rightChild;
// 不存在右子树或右子树已经访问过,可以访问父结点
if (temp == null || temp == prev) {
root = nodeStack.pop();
System.out.println("postOrderNonRecursive data:" + root.getData());
// 记录访问过的结点
prev = root;
// 当前结点置空
root = null;
} else {
// 存在右子树,需要优先访问右子树
root = temp;
}
}
} while (root != null || !nodeStack.empty());
}
5 获取二叉树的高度和度
5.1 获取二叉树高度
树的性质第12点:叶结点的高度为1,非叶结点的高度等于它子女结点高度的最大值加1。核心思想是递归实现
/**
* 求二叉树的深度(高度)
*
* @return
*/
public int getHeight() {
return getHeight(root);
}
/**
* 求二叉树的深度(高度)
*
* @param root
* @return
*/
private int getHeight(TreeNode root) {
if (root == null) {
return 0;
} else {
int i = getHeight(root.leftChild);
int j = getHeight(root.rightChild);
return i >= j ? i + 1 : j + 1;
}
}
5.2 获取二叉树的度
/**
* 求二叉树的结点数
*
* @return
*/
public int getSize() {
return getSize(root);
}
/**
* 求二叉树的结点数
*
* @param root
* @return
*/
private int getSize(TreeNode root) {
if (root == null) {
return 0;
} else {
return 1 + getSize(root.leftChild) + getSize(root.rightChild);
}
}
6 创建一棵二叉树
完全二叉树和满二叉树是一般二叉树的一种形态,所以只要会创建一般二叉树即会创建所有形态的二叉树。用数组来存储一棵二叉树,对这棵树按照完全二叉树的方式编号,其中结点不存在的使用特殊字符来标明。
现有一棵二叉树如下图所示,则其用数组表示为['A','B','D','#','#','E','#','#','F','#','#','#']
,叶结点需要带子女结点,且子女结点用特殊字符标识。
A
/ \
B C
/ \ /
D E F
- 输入一个arraylist,list中存放的数据是结点数组(通过
list.reove
来获取数据) - 获取list下标为0的元素(总是第一个),如果遇到结束符’#’,则不创建结点,并执行
remove(0)
;否则创建一个结点,判断index = 0
是否成立,若成立则表明根结点不存在,创建根结点,并执行remove(0)
- 循环步骤1,2,递归创建左子树和右子树
/**
* 前序遍历创建二叉树
* ABD##E##CF###
*
* @param data
* @return
*/
public TreeNode createBinaryTree(List<String> data) {
if (data == null || data.isEmpty())
return null;
return createBinaryTreeHelper(data.size(), data);
}
private TreeNode createBinaryTreeHelper(int size, List<String> data) {
TreeNode node;
String d = data.get(0);
int index = size - data.size();
// 遇到结束符#
if (d.equals("#")) {
node = null;
data.remove(0);
return node;
}
node = new TreeNode(index, d);
// 根结点
if (index == 0) {
root = node;
}
data.remove(0);
node.leftChild = createBinaryTreeHelper(size, data);
node.rightChild = createBinaryTreeHelper(size, data);
return node;
}
7 其他应用(层序遍历,复制二叉树,判断二叉树是否相等)
7.1 层序遍历
层序遍历依照从根结点开始,自上而下,从左到右。需要有一个结构来存储当前层的结点,当访问完当前层结点后,将其抛出后继续访问下层结点,队列的先进先出符合这一要求。
- 将根节点压入队列中
- 队列不为空则开始循环,此时只有根结点,则推出根结点并访问。判断根结点有无左右子树,若有则将其压入队列中
- 队列为空,结束循环
/**
* 层序遍历
*
* @param root
*/
public List<List<String>> levelOrder(TreeNode root) {
List<List<String>> reList = new ArrayList<>();
Queue<TreeNode> nodeQueue = new LinkedList<>();
// 压入根结点
nodeQueue.offer(root);
while (!nodeQueue.isEmpty()) {
int levelSize = nodeQueue.size();
List<String> subList = new ArrayList<>();
while (levelSize != 0) {
TreeNode temp = nodeQueue.poll();
subList.add(temp.getData() + "");
if (temp.leftChild != null) nodeQueue.offer(temp.leftChild);
if (temp.rightChild != null) nodeQueue.offer(temp.rightChild);
levelSize--;
}
reList.add(subList);
}
return reList;
}
7.2 通过前序遍历复制一棵二叉树
为了实现二叉树的复制方法,可以利用二叉树的前序遍历算法。若二叉树parent不为空,则首先复制根结点,这相当于二叉树前序遍历中访问根结点的语句;然后分别复制二叉树的左子树和右子树,这相当于二叉树前序遍历算法中的遍历左子树和右子树。整个算法的思想是递归。
/**
* 复制一棵二叉树
* @param parent
* @return
*/
public TreeNode copy(TreeNode parent){
if(parent == null)
return null;
// 构造根结点
TreeNode temp = new TreeNode(parent.getIndex(),parent.getData());
// 递归构造左子树
temp.leftChild = copy(parent.leftChild);
// 递归构造右子树
temp.rightChild = copy(parent.rightChild);
return temp;
}
7.3 判断两棵树是否相等
递归判断每个结点的s.data == t.data
是否成立即可
/**
* 判断两棵树是否相等
* @param s
* @param t
* @return
*/
public boolean euqal(TreeNode s,TreeNode t){
if(s == null && t == null)
return true;
if(s != null && t != null && s.data == t.data &&
euqal(s.leftChild,t.leftChild) &&
euqal(s.rightChild,t.rightChild))
return true;
else
return false;
}
总结
本章节主要学习了
- 二叉树的遍历与实现(递归和非递归)
- 获取二叉树的高度和度
- 创建一棵二叉树
- 其他应用(层序遍历,复制二叉树,判断二叉树是否相等)
学习树最主要的思想就是要了解递归,因为树的定义是一个递归的定义,即树的定义中又用到了树的概念。
附录
本文参考的资料:
- 数据结构(用面向对象方法与C++语言描述)第二版,殷人昆主编
- 图解数据结构——使用java,胡昭民主编
代码传送门,欢迎star:https://github.com/mcrwayfun/java-data-structure