上篇文章将树和二叉树的基本概念和操作进行了实现,但只单纯的了解这部分对于数据结构来讲是不够的,因此本片文章摄入一些二叉树的进阶习题,都是力扣或者牛客上相对比较经典的习题。
对于二叉树结点的描述依然采用孩子表示法:
class TreeNode{
TreeNode left;
TreeNode right;
int value;
TreeNode(int value){
this.value = value;
}
}
1、检查两棵树是否相同 isSameTree()
判断两颗二叉树相同的条件是:结点值相同以及树的结构相同。假设p树的结点个数为m,q树的结点个数为n,则检查两棵树是否相同的时间复杂度为O(Min(m,n))。
public boolean isSameTree(TreeNode p, TreeNode q) {
if(p == null && q == null) {
return true;
}
if(p == null && q != null || p !=null && q == null) {
return false;
}
if(p.value != q.value) {
return false;
}
return isSameTree(p.left,q.left) && isSameTree(p.right,q.right);
}
2、判断一棵树是否为另一棵树的子树 isSubtree()
(1)首先判断两棵树是否相同;
(2)判断subRoot是否与root的左子树或者右子树相同。
由上可知判断是否是子树。需要调用判断两个树是否相同的方法isSameTree(),isSubtree()时间复杂度为O(m*n)。
public boolean isSubtree(TreeNode root, TreeNode subRoot) {
if(root == null && subRoot == null) {
return true;
}
if(root == null || subRoot == null) {
return false;
}
if(isSameTree(root,subRoot)) {
return true;
}
//判断子树与subRoot
if(isSubtree(root.left,subRoot)) {
return true;
}
if(isSubtree(root.right,subRoot)) {
return true;
}
return false;
}
3、二叉树最大深度 maxDepth()
public int maxDepth(TreeNode root) {
if(root == null) {
return 0;
}
int left = maxDepth(root.left);
int right = maxDepth(root.right);//最好将计算结果记录下来,否则直接return的话,多次计算迭代的次数就过多
return left > right ? left+1 : right+1;
}
4、判断一棵二叉树是否是平衡二叉树
平衡二叉树:一个二叉树每个结点的左右子树的高度差绝对值不超过1,并且其所有子树都是平衡二叉树。本题有两种解法:
法1:求出左右子树的高度差是否为1,如果为1,再判断左右子树是否为平衡二叉树。
public int Height(TreeNode root) {
if(root == null) {
return 0;
}
int leftHeight = Height(root.left);
int rightHeight = Height(root.right);
return (leftHeight > rightHeight) ? leftHeight+1 : rightHeight+1;
}
public boolean isBalanced(TreeNode root) {
if(root == null) {
return true;
}
int left = Height(root.left);
int right = Height(root.right);
return Math.abs(left-right)<=1 && isBalanced(root.left) && isBalanced(root.right);
}
但是这个方法时间复杂度相对较高。Height()方法的时间复杂度为O(n),多次调用会提高时间复杂度。因此,可以采用在计算高度的同时判断是否为平衡二叉树。
法2:边计算边判断,当左右子树的高度差超过1时就返回-1,当某一个子树的左子树或者右子树返回值为-1时就可以直接判断整个树就不是平衡二叉树。
public int height (TreeNode root) {
if(root == null) {
return 0;
}
int leftHeight = height(root.left);
int rightHeight = height(root.right);
if(leftHeight >= 0 && rightHeight >= 0 && Math.abs(leftHeight-rightHeight) <= 1) {
return Math.max(leftHeight,rightHeight) + 1;
}else {
return -1;
}
}
public boolean isBalanced(TreeNode root) {
if(root == null) {
return true;
}
return height(root) >=0;
}
5、判断一个二叉树是否是对称的
public boolean isSymmetricChild(TreeNode leftTree,TreeNode rightTree) {
if(leftTree != null && rightTree == null || leftTree == null && rightTree != null) {
return false;
}
if(leftTree == null && rightTree == null) {
return true;
}
if(leftTree.value != rightTree.value) {
return false;
}
return isSymmetricChild(leftTree.left,rightTree.right) && isSymmetricChild(leftTree.right,rightTree.left);
}
public boolean isSymmetric(TreeNode root) {
if(root == null) {
return true;
}
return isSymmetricChild(root.left,root.right);
}
6、二叉树的构建
读入用户输入的一串先序遍历字符串,根据此字符串建立一个二叉树(以指针方式存储),其中"#"表示空格,并以中序遍历的方式输出。
public static int i;
public static TreeNode createTree(String str) {
TreeNode root = null;
if(str.charAt(i) != '#') {
//前序的序列 因此创建使用根、左、右
root = new TreeNode(str.charAt(i));
i++;
root.left = createTree(str);
root.right = createTree(str);
}else {
i++;
}
return root;
}
//中序遍历
public static void inorder(TreeNode root) {
if(root == null) {
return;
}
inorder(root.left);
System.out.printf(root.val + " ");
inorder(root.right);
}
7、层序遍历
public void levelOrder(TreeNode root) {
if(root == null) {
return;
}
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(root);
while(!queue.isEmpty()) {
TreeNode cur = queue.poll();
System.out.println(cur.value+ " ");
if(cur.left != null) {
queue.offer(cur.left);
}
if(cur.right != null) {
queue.offer(cur.right);
}
}
}
8、给定一个二叉树,找到该树中两个指定节点的最近公共祖先(极其重要)
本题也是两种解法:从两个方面入手。第一种是以树为排序二叉树的情况进行延伸,第二种是用栈存储从根到两个结点的路径,再出栈求交点。
法1:当二叉树为排序二叉树时那么其中序遍历是有序的,根左边的所有结点都比根的值小,根右边所有的结点都比根的值大,因此查找两个结点的最近的公共结点,可以根据数值大小的关系在左子树或者右子树寻找公共结点。
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
if(root == null) {
return null;
}
if(root == p || root == q) {
return root;
}
TreeNode leftT = lowestCommonAncestor(root.left,p,q);
TreeNode rightT = lowestCommonAncestor(root.right,p,q);
if(leftT != null && rightT != null) {
return root;
}else if(leftT != null) {
return leftT;
}else {
return rightT;
}
}
法2:用栈存储从根到两个节点的路径,再使用两个栈求交点的方式求出最近公共祖先。
//查找并存储从根节点到指定节点的路径
public boolean getPath(TreeNode root,TreeNode node,Stack<TreeNode> stack) {
if(root == null || node == null) {
return false;
}
stack.push(root);
if(root == node) {
return true;
}
boolean flg = getPath(root.left,node,stack);
if(flg == true) {
return true;
}
flg = getPath(root.right,node,stack);
if(flg == true) {
return true;
}
stack.pop();
return false;
}
//两个栈求交点(前后指针)
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
if(root == null) {
return null;
}
Stack<TreeNode> stack1 = new Stack<>();
getPath(root,p,stack1);
Stack<TreeNode> stack2 = new Stack<>();
getPath(root,q,stack2);
int size1 = stack1.size();
int size2 = stack2.size();
if(size1 > size2) {
int size = size1 - size2;
while(size != 0) {
stack1.pop();
size--;
}
while(!stack1.isEmpty() && !stack2.isEmpty()) {
if(stack1.peek() == stack2.peek()) {
return stack1.pop();
}else {
stack1.pop();
stack2.pop();
}
}
}else {
int size = size2 - size1;
while(size != 0) {
stack2.pop();
size--;
}
while(!stack1.isEmpty() && !stack2.isEmpty()) {
if(stack1.peek() == stack2.peek()) {
return stack1.pop();
}else {
stack1.pop();
stack2.pop();
}
}
}
return null;
}
9、二叉搜索树转换成排序双向链表
要得到一个排序的二叉树首先是要对二叉树进行中序遍历的,在中序遍历的过程中将二叉树转换成双向链表。
TreeNode prev = null;
public void inorder(TreeNode pCur) {
if(pCur == null) {
return;
}
inorder(pCur.left);
pCur.left = prev;
if(prev != null) {
prev.right = pCur;
}
prev = pCur;
inorder(pCur.right);
}
public TreeNode Convert(TreeNode pRootOfTree) {
if(pRootOfTree == null) {
return null;
}
inorder(pRootOfTree);
TreeNode head = pRootOfTree;
while(head.left != null) {
head = head.left;
}
return head;
}
10、根据一棵树的前序遍历与中序遍历构造二叉树
树的前序遍历加中序遍历以及后序遍历加中序遍历可以构造出一个完整的二叉树,知道前序遍历和后续遍历是不能得到完整二叉树的。
public int preIndex = 0;
public TreeNode createTreeByPandI(int[] preorder,int[] inorder,int inbegin,int inend) {
//preorder 前序遍历 inorder 是中序遍历 preIndex 是前序遍历数组的引用,inbegin是中序遍历的前引用,inend是中序遍历的后引用。
if(inbegin > inend) {
return null;
}
TreeNode root = new TreeNode(preorder[preIndex]);
int rootIndex = findIndexOfI(inorder,inbegin,inend,preorder[preIndex]);
if(rootIndex == -1) {
return null;
}
preIndex++;
root.left = createTreeByPandI(preorder,inorder,inbegin,rootIndex-1);
root.right = createTreeByPandI(preorder,inorder,rootIndex+1,inend);
return root;
}
private int findIndexOfI(int[] inorder,int inbegin,int inend,int key) {
for(int i = inbegin; i<= inend;i++) {
if(inorder[i] == key) {
return i;
}
}
return -1;
}
public TreeNode buildTree(int[] preorder, int[] inorder) {
if(preorder == null || inorder == null) {
return null;
}
return createTreeByPandI(preorder,inorder,0,inorder.length-1);
}
11、根据一棵树的中序遍历与后序遍历构造二叉树
中序遍历加后序遍历需要注意的是后续遍历需要从后往前访问,中序遍历需要先右后左的访问。
public int postIndex;
public TreeNode createTreeByPandI(int[] inorder,int[] postorder,int inbegin,int inend) {
//preorder 前序遍历 inorder 是中序遍历 preIndex 是前序遍历数组的引用,inbegin是中序遍历的前引用,inend是中序遍历的后引用。
if(inbegin > inend) {
return null;
}
TreeNode root = new TreeNode(postorder[postIndex]);
int rootIndex = findIndexOfI(inorder,inbegin,inend,postorder[postIndex]);
if(rootIndex == -1) {
return null;
}
postIndex--;
//先 右子树 后 左子树
root.right = createTreeByPandI(inorder,postorder,rootIndex+1,inend);
root.left = createTreeByPandI(inorder,postorder,inbegin,rootIndex-1);
return root;
}
private int findIndexOfI(int[] inorder,int inbegin,int inend,int key) {
for(int i = inbegin; i<= inend;i++) {
if(inorder[i] == key) {
return i;
}
}
return -1;
}
public TreeNode buildTree(int[] inorder, int[] postorder) {
if(inorder == null || postorder == null) {
return null;
}
postIndex = inorder.length - 1;
return createTreeByPandI(inorder,postorder,0,inorder.length-1);
}
12、二叉树创建字符串
采用前序遍历的方式,将一个二叉树转化成一个由括号和数字组成的字符串。
public void treeToString(TreeNode t,StringBuilder sb) {
if(t == null) {
return;
}
sb.append(t.val);
if(t.left != null) {
sb.append("(");
treeToString(t.left,sb);
sb.append(")");
}else {
if(t.right == null) {
return;
}else {
sb.append("()");
}
}
if(t.right == null) {
return;
}else {
sb.append("(");
treeToString(t.right,sb);
sb.append(")");
}
}
public String tree2str(TreeNode root) {
if(root == null) {
return null;
}
StringBuilder sb = new StringBuilder();
treeToString(root,sb);
return sb.toString();
}
13、二叉树前序非递归遍历实现
要使用栈来辅助实现
void preOrderNor(TreeNode root){
Stack<TreeNode> stack = new Stack<>();
TreeNode cur = root;
while(cur != null || !stack.isEmpty()) {
while(cur != null) {
stack.push(cur);
System.out.print(cur.value + " ");
cur = cur.left;
}
TreeNode top = stack.pop();
cur = top.right;
}
}
14、二叉树的中序非递归遍历实现
void inOrderNor(TreeNode root){
Stack<TreeNode> stack = new Stack<>();
TreeNode cur = root;
while(cur != null || !stack.isEmpty()) {
while(cur != null) {
stack.push(cur);
cur = cur.left;
}
TreeNode top = stack.pop();
System.out.print(top.value + " ");
cur = top.right;
}
}
15、后续遍历的非递归遍历实现
void postOrderNor(TreeNode root){
Stack<TreeNode> stack = new Stack<>();
TreeNode cur = root;
TreeNode prev = null;
while(cur != null || !stack.isEmpty()) {
while(cur != null) {
stack.push(cur);
cur = cur.left;
}
TreeNode top = stack.peek();
//如果当前节点的右子树被打印过或者遍历过就直接进行弹出就可以了
if(top.right == null || top.right == prev) {
stack.pop();
System.out.print(top.value+ " ");
prev = top;//记录最近一次打印的节点,防止重复打印,陷入死循环。
}else {
cur = top.right;
}
}
}
以上即为本文的全部内容,如有更加优化的思想,欢迎一起探讨!