前言
这里同样不介绍二叉树的相关概念,毕竟数据结构的基础已经讲过很多了,这里不再赘述。至于一些平衡二叉树,完全二叉树,红黑树,B+树等相关结构,这个已经有很多博客介绍了,这里只是介绍一下二叉树的一些基础操作。
定义
这个应该见过多次
/**
* autor:liman
* createtime:2020/2/6
* comment:二叉树节点的实现
*/
public class TreeNode {
public String value;
public TreeNode left;
public TreeNode right;
public TreeNode(String value) {
this.value = value;
}
}
遍历的非递归实现
递归的实现非常简单,估计很多人都会,这里就介绍非递归的遍历实现
基本的遍历操作如下:这里示意的输出一下表示遍历操作
/**
* 做遍历的操作。
*
* @param node
*/
public static void doTraverse(TreeNode node) {
System.out.print(node.value + " ");
}
先序遍历
/**
* 非递归实现的先序遍历,这里用到了栈的操作
*
* @param root
*/
public static void preOrderTraverse(TreeNode root) {
if (root != null) {//如果根节点不为空
Stack<TreeNode> stack = new Stack<>();//非递归要用到栈
stack.push(root);
while (!stack.isEmpty()) {
TreeNode node = stack.pop();
doTraverse(node);
//先判断右子树,再判断左子树
if (node.right != null) {//判断右子树,如果右子树不为空,入栈
stack.push(node.right);
}
if (node.left != null) {//判断左子树,如果左子树不空,则入栈
stack.push(node.left);
}
}
}
}
中序遍历
/**
* 非递归实现中序遍历
*
* @param root
*/
public static void innerOrderTraverse(TreeNode root) {
if (root != null) {
Stack<TreeNode> stack = new Stack<>();
//不断的入栈左子树,找到整个树中最左端的一个节点(中序遍历的头结点)
while (!stack.isEmpty() || root != null) {
if (root != null) {
stack.push(root);
root = root.left;
} else {//如果节点的左子树为空了,则直接弹出,遍历,然后指向右子树。
root = stack.pop();
doTraverse(root);
root = root.right;
}
}
}
}
后续遍历
/**
* 非递归实现后序遍历,一个栈比较麻烦,两个栈相对简单
*
* @param root
*/
public static void postOrderTraverse(TreeNode root) {
if (root != null) {
Stack<TreeNode> stack1 = new Stack<>();
Stack<TreeNode> stack2 = new Stack<>();//用于存放待遍历的元素
stack1.push(root);
while (!stack1.isEmpty()) {
TreeNode pop = stack1.pop();
stack2.push(pop);
if (pop.left != null) {
stack1.push(pop.left);
}
if (pop.right != null) {
stack1.push(pop.right);
}
}
while (!stack2.isEmpty()) {
doTraverse(stack2.pop());
}
}
}
层次遍历
/**
* 层次遍历,非递归实现
* 利用队列
* @param root
*/
public static void levelOrderTraverse(TreeNode root) {
if (root != null) {
Queue<TreeNode> queue = new ArrayDeque<>();
queue.offer(root);//root入队列
while (!queue.isEmpty()) {
//获取当前层次的节点个数
int levelNum = queue.size();
for (int i = 0; i < levelNum; i++) {
TreeNode node = queue.poll();
doTraverse(node);
if (node.left != null) {//入队左子树
queue.offer(node.left);
}
if (node.right != null) {//入队右子树
queue.offer(node.right);
}
}
}
}
}
计算二叉树的深度(最大深度和最小深度)
最大深度
递归实现
递归实现最大深度,代码是最为简单的
/**
* 递归计算最大深度
*
* @param root
* @return
*/
public int maxDepth(TreeNode root) {
if (root == null) {
return 0;
}
int left = maxDepth(root.left);
int right = maxDepth(root.right);
return Math.max(left, right) + 1;//毕竟根节点还是要算一层高度的。
}
非递归实现
/**
* 非递归实现最大深度,要用到层序遍历
*
* @param root
* @return
*/
public int maxDepthLevel(TreeNode root) {
if (root == null) {
return 0;
}
Queue<TreeNode> queue = new ArrayDeque<>();
queue.offer(root);
int level = 0;
while (!queue.isEmpty()) {
level++; //这里统计层高
int levelNum = queue.size();
for (int i = 0; i < levelNum; i++) {
TreeNode node = queue.poll();
if (node.left != null) {
queue.offer(node.left);
}
if (node.right != null) {
queue.offer(node.right);
}
}
}
return level;
}
最小深度
最小深度相比最大深度来说,我们要判断的条件会比较多,如果树为空则直接返回0,如果只有一个根节点则直接返回1,如果左子树为空右子树不为空,则需要计算右子树的最小深度。如果右子树为空左子树不为空,则需要计算左子树的最小深度。
递归实现
/**
* 递归求树的最小深度,这里需要考虑的条件更多
*
* @param root
* @return
*/
public int mixDepth(TreeNode root) {
if (root == null) {//树为空
return 0;
}
if (root.left == null && root.right == null) {//只有一个根节点
return 1;
}
if (root.left == null && root.right != null) {//左子树为空,右子树不为空
return mixDepth(root.right) + 1;
}
if (root.right == null && root.left != null) {//右子树为空,左子树不为空
return mixDepth(root.left) + 1;
}
//这个就是左子树和右子树都不为空的情况
int left = mixDepth(root.left);
int right = mixDepth(root.right);
return Math.min(left,right)+1;
}
非递归实现
层序遍历的时候,遇到的第一个左右子树均为空的节点的层高,必是最小深度
public int mixDepthLevel(TreeNode root){
if (root == null) {
return 0;
}
Queue<TreeNode> queue = new ArrayDeque<>();
queue.offer(root);
int level = 0;
while (!queue.isEmpty()) {
level++;
int levelNum = queue.size();
for (int i = 0; i < levelNum; i++) {
TreeNode node = queue.poll();
//第一个左右子树均为空的节点的层高,必是最小深度
if(node.left== null && node.right == null){
return level;
}
if (node.left != null) {
queue.offer(node.left);
}
if (node.right != null) {
queue.offer(node.right);
}
}
}
return 0;
}
计算两个节点的最近的祖先
/**
* 递归实现两个节点最近的祖先
* @param root
* @param node01
* @param node02
* @return
*/
public TreeNode lowestCommoAncestor(TreeNode root, TreeNode node01, TreeNode node02) {
//最近的公共祖先是root节点
if (root == null || root == node01 || root == node02) {
return root;
}
TreeNode left = lowestCommoAncestor(root.left, node01, node02);
TreeNode right = lowestCommoAncestor(root.right, node01, node02);
if (left != null && right != null) {//如果左右都不空,说明两边均能找到node01和node02,则公共节点只能root了
return root;
}
//如果只有一个为空,则那个公共的祖先就是这个节点。
return left != null ? left : right;
}
根据中序和先序序列构建二叉树
这个其实数据结构中接触过相关问题,无非就是从先序遍历序列中找到根节点,然后这个根节点在中序序列中的左边均为该节点的左子树,中序序列右边的节点均为该节点的右子树。
public TreeNode buildTree(int[] preOrder,int[] inOrder){
if(preOrder == null || inOrder == null){
return null;
}
HashMap<Integer,Integer> map = new HashMap<>();
for(int i=0;i<inOrder.length;i++){
map.put(inOrder[i],i);
}
return buildTree(preOrder,0,preOrder.length-1,inOrder,0,inOrder.length-1,map);
}
/**
* 根据中序和先序遍历的数组构建一个二叉树
* @param preOrder 先序序列数组
* @param pStart 先序序列的起始位置
* @param pEnd 先序序列的结束位置
* @param inOrder 中序序列数组
* @param iStart 中序序列的起始位置
* @param iEnd 中序序列的结束位置
* @param map 中序序列元素与索引位置的map,仅仅只是为了搜索方便
* @return
*/
public TreeNode buildTree(int[] preOrder,int pStart,int pEnd,
int[] inOrder,int iStart,int iEnd,
HashMap<Integer,Integer> map){
if(pStart>pEnd || iStart>iEnd){
return null;
}
TreeNode head = new TreeNode(preOrder[pStart]);
int index = map.get(preOrder[pStart]);//找到根节点在中序遍历中的位置
head.left = buildTree(preOrder,pStart+1,pStart+index-iStart,inOrder,iStart,index-1,map);
head.right = buildTree(preOrder,pStart+index-iStart+1,pEnd,inOrder,index+1,iEnd,map);
return head;
}
这里有很多变量,关于索引的,这里直接用一张图表达(至于这个完整的树结构,各位看官可以自行转换一下,这个不是难事):
总结
一些基础的操作都总结了,其实之前用C实现过,但是都不太系统。
来源:CSDN
作者:谜一样的Coder
链接:https://blog.csdn.net/liman65727/article/details/104222410