对二叉树的操作
前言
对于二叉树的操作基本上都是基于对二叉树的7种遍历方法来处理的,所以说学会了这7种遍历方法后,处理起二叉树的问题会非常游刃有余。若想具体了解这7种遍历算法,请查看下面这篇文章:二叉树的7种遍历算法(https://blog.csdn.net/zhang_qing_yun/article/details/108969124)
获取二叉树的最大深度(递归法)(深度优先DFS)
思路:
1.递归获取左子树的最大深度
2.递归获取右子树的最大深度
3.返回左、右子树两者中较大的值并加1,因为当前结点也算一层
4.当目前结点是null时,退出递归,向上回溯
public static int maxDepth(Node T) {
if (T == null) {
return 0;
}
// 递归获取左子树的最大深度
int leftTreeDepth = maxDepth(T.getLeft());
// 递归获取右子树的最大深度
int rightTreeDepth = maxDepth(T.getRight());
// 返回两者中较大的那个+1
return Math.max(leftTreeDepth, rightTreeDepth) + 1;
}
获取该树的最大深度(非递归)(广度优先BFS)
思路:
我们采用层序遍历的方法遍历二叉树,在遍历的过程中顺便记下层数,具体过程请看代码注释。
public static int maxDepth2(Node T) {
if (T == null) {
return 0;
}
Queue<Node> queue = new LinkedList<>();
queue.add(T);
int length = 0;
// 当队列不为空时循环
while (!queue.isEmpty()) {
// 获取该层的个数
int size = queue.size();
// 当该层的结点个数不为0时循环
while (size > 0) {
// 从队列里取出一个结点
Node temp = queue.poll();
// 记录该层个数的变量减1
size--;
if (temp.getLeft() != null) {
// 添加到队列里
queue.offer(temp.getLeft());
}
if (temp.getRight() != null) {
// 添加到队列里
queue.offer(temp.getRight());
}
}
// 记录层数的变量加1
length++;
}
return length;
}
判断是否为完全二叉树
要想判断一个二叉树是否为完全二叉树,就需要判断该二叉树有没有“洞”:即在某个位置左面有结点,右面有结点,但是当前位置时空的。有“洞”的二叉树一定不是完全二叉树,下面我们根据这个想法给出代码,具体的过程请看代码注释。
public static boolean isCompleteBinaryTree(Node T) {
if (T == null) {
throw new RuntimeException("二叉树为空!");
}
Queue<Node> queue = new LinkedList<>();
queue.offer(T);
while (true) {
// 获取每层的个数
int size = queue.size();
while (size > 0) {
Node node = queue.poll();
size--;
// 左右孩子结点都不为空时,将孩子节点排队
if (node.getLeft() != null && node.getRight() != null) {
queue.offer(node.getLeft());
queue.offer(node.getRight());
} else if (node.getLeft() == null && node.getRight() != null) {
return false;
} else {
// 左不空右空
if (node.getLeft() != null) {
queue.offer(node.getLeft());
}
// 如果是完全二叉树,当前应该是最后一层
while (size > 0) {
node = queue.poll();
size--;
if (node.getLeft() != null || node.getRight() != null) {
return false;
}
}
// 只有在当前队列为空时才是完全二叉树
if (queue.size() == 0) {
return true;
} else {
return false;
}
}
}
}
}
判断一棵树是否为满二叉树
判断了完全二叉树后再判断是否为满二叉树就非常容易了,因为思路差不多,都是在当出现孩子节点为空时将该层全部遍历完。
public static boolean isFullBinaryTree(Node T) {
if (T == null) {
throw new RuntimeException("二叉树为空!");
}
Queue<Node> queue = new LinkedList<>();
queue.offer(T);
while (true) {
int size = queue.size();
while (size > 0) {
Node node = queue.poll();
size--;
if (node.getLeft() != null && node.getRight() != null) {
queue.offer(node.getLeft());
queue.offer(node.getRight());
} else if (node.getLeft() == null && node.getRight() == null) {
while (size > 0) {
node = queue.poll();
size--;
if (node.getLeft() != null || node.getRight() != null) {
return false;
}
}
// 保证当前已经把最后一层遍历完了
if (queue.size() == 0) {
return true;
} else {
return false;
}
} else {
return false;
}
}
}
}
获取两个结点最近的公共祖先(递归)
递归遍历树时有一个好处:当到了终止条件向上回溯时,是按根节点到该结点的路径一路向上回溯的!
public static Node lowestCommonNode(Node T, Node p, Node q) {
// 递归的终止条件,同时也是在访问当前结点
// 当走过了叶子结点,或者找到了指定结点时退出
if (T == null || p == T || q == T) {
return T;
}
// 递归左子树
Node leftResult = lowestCommonNode(T.getLeft(), p, q);
// 递归右子树
Node rightResult = lowestCommonNode(T.getRight(), p, q);
if (leftResult != null && rightResult != null) {
return T;
}
if (leftResult != null) {
return leftResult;
}
if (rightResult != null) {
return rightResult;
}
// 没找到p、q
return null;
}
指定结点所在的层数(BFS)
解决方法类似于上面获取该树的最大深度(非递归),只不过的是多了个判断当前遍历到的结点和目的结点是否一样这一步。
public static int floor(Node T, Node node) {
if (T == null) {
throw new RuntimeException("二叉树为空!");
}
if (node == null) {
throw new RuntimeException("该结点为空!");
}
Queue<Node> queue = new LinkedList<>();
int floor = 0;
queue.offer(T);
while (!queue.isEmpty()) {
// 获取该层结点个数
int size = queue.size();
floor++;
while (size > 0) {
Node temp = queue.poll();
size--;
if (temp == node) {
return floor;
}
if (temp.getLeft() != null) {
queue.offer(temp.getLeft());
}
if (temp.getRight() != null) {
queue.offer(temp.getRight());
}
}
}
return 0;
}
指定结点所在的层数(DFS)(先根递归)
依旧是利用递归回溯时走的是根节点到当前结点的路径这一特点,在他回溯时每回溯一次就加一。
public static int floor2(Node T, Node node) {
if (T == null) {
return 0;
}
if (T == node) {
return 1;
}
int left = floor2(T.getLeft(), node);
if (left > 0) {
return left + 1;
}
int right = floor2(T.getRight(), node);
if (right > 0) {
return right + 1;
}
return 0;
}
指定结点的全部祖先结点
还是利用递归回溯时走的是根节点到当前结点的路径这一特点,在它回溯时将回溯路径上的结点压栈。
public static Stack<Node> allAncestor(Node T, Node node, Stack<Node> stack) {
if (T == null) {
return stack;
}
if (T == node || allAncestor(T.getLeft(), node, stack).size() > 0 || allAncestor(T.getRight(), node, stack).size() > 0) {
stack.push(T);
return stack;
}
return stack;
}
统计二叉树中,结点个数:总数、度为1、度为2、度为0
利用层序遍历,很容易就会得出结果,直接给出代码:
public static Map<String, Integer> nodeNumber(Node T) {
if (T == null) {
throw new RuntimeException("二叉树为空!");
}
Queue<Node> queue = new LinkedList<>();
HashMap<String, Integer> map = new HashMap<>();
map.put("all", 0);
map.put("0", 0);
map.put("1", 0);
map.put("2", 0);
queue.offer(T);
while (!queue.isEmpty()) {
Node node = queue.poll();
if (node.getLeft() != null && node.getRight() != null) {
queue.offer(node.getLeft());
queue.offer(node.getRight());
map.put("2", map.get("2") + 1);
map.put("all", map.get("all") + 1);
} else if (node.getLeft() != null) {
queue.offer(node.getLeft());
map.put("1", map.get("1") + 1);
map.put("all", map.get("all") + 1);
} else if (node.getRight() != null) {
queue.offer(node.getRight());
map.put("1", map.get("1") + 1);
map.put("all", map.get("all") + 1);
} else {
map.put("0", map.get("0") + 1);
map.put("all", map.get("all") + 1);
}
}
return map;
}
将二叉链表存储的二叉树转化成类似于存储完全二叉树的数组
这里利用到完全二叉树的一个性质:当前结点的索引在数组中是i,则它的左孩子结点在数组中的索引是2i+1,右孩子结点在数组中的索引是2i+2.
/**
* 将二叉链表存储的二叉树转化成类似于存储完全二叉树的数组
* @param T 二叉树
* @param nodes 存储二叉树的数组
* @param i 当前结点是数组里的哪一个
* @return 存储二叉树的数组
*/
public static Node[] toArray(Node T, Node[] nodes, int i) {
if (T == null) {
nodes[i] = null;
} else {
nodes[i] = T;
toArray(T.getLeft(), nodes, 2*i+1);
toArray(T.getRight(), nodes, 2*i+2);
}
return nodes;
}
更多算法知识请关注微信公众号:青云学斋