树及相关常见问题(一)

本文详细介绍了二叉树的基本概念、遍历方法(前序、中序、后序、层次遍历)及其非递归实现,包括查找最大元素、判断元素存在、插入节点、计算节点数、删除树、逆序层次遍历等常见问题。此外,还探讨了二叉树的高度、镜像和直径计算。接着转向N叉树,讲解了其最大深度、前序、后序遍历的实现。这些内容涵盖了树结构的基础知识和重要操作。
摘要由CSDN通过智能技术生成

1. 树的定义和术语

定义:
在这里插入图片描述

  • 普通节点:包含子节点的节点
  • 叶子节点:没有子节点的节点
  • 根节点:没有父节点的节点

在这里插入图片描述

  • 节点的度:节点拥有的子树的个数
  • 树的度:所有节点中度的最大值
  • 节点的层次:根节点的层次为1,往下节点的层次值为父节点的层次值加一
  • 树的高度(深度):节点层次值的最大值
  • 森林:2棵或2棵以上的互不相交的树的集合,删除一棵树的根,就得到一个森林

树的两种表示方法:
在这里插入图片描述

1. 父节点表示法:每个节点都记录它的父节点
在这里插入图片描述

在这里插入图片描述

定义树节点时增加了一个parent域,该parent域用于保存该节点的父节点在数组中的索引,添加节点时只需要将新节点的parent域的值设为其父节点在数组中的索引即可
特点:找某个节点的父节点容易,但是找子节点麻烦,需要遍历整个数组

2. 子节点链表示法: 父节点记住子节点
在这里插入图片描述

在这里插入图片描述

树节点中增加了一个first域,该域用于保存对该节点的子节点的引用,添加节点时只需要找到指定父节点的子节点链的最后节点,最后一个节点的first指向新的节点

特点:找子节点容易,找父节点难,需要遍历整个数组

2. 二叉树的相关概念

二叉树每个节点最多只有两棵子树,左边的称为左子树,右边的称为右子树,二者不能互换,因此二叉树是有序树

二叉树与树的区别:

  • 树中节点的度没有限制,二叉树中的节点的度最大为2
  • 无序树的节点无左右之分,二叉树的节点有左右之分

满二叉树:高度为k. 有 2 k − 1 2^k-1 2k1个节点
在这里插入图片描述

完全二叉树:除最后一层外节点没有满,并且最后一层却是的节点都在右边
在这里插入图片描述

二叉树的性质:

  1. 第i层的节点数目最多为 2 i − 1 2^i-1 2i1 i从1开始
  2. 深度为k的二叉树最多有 2 k − 1 2^k-1 2k1个节点----满二叉树
  3. n 0 = n 2 + 1 n_0=n_2+1 n0=n2+1: n 0 n_0 n0表示度为0的节点数目, n 2 n_2 n2为度为2的数目,简单证明如下: 节 点 数 目 之 和 : n 0 + n 1 + n 2 度 数 之 和 : 0 ∗ n 0 + 1 ∗ n 1 + 2 ∗ n 2 节 点 数 = 度 数 之 和 + 1 n 0 + n 1 + n 2 = 0 ∗ n 0 + 1 ∗ n 1 + 2 ∗ n 2 因 此 n 0 = n 2 + 1 节点数目之和:n_0+n_1+n_2 \\ 度数之和:0*n_0+1*n_1+2*n_2 \\ 节点数=度数之和+1 \\ n_0+n_1+n_2=0*n_0+1*n_1+2*n_2 \\ 因此 n_0=n_2+1 n0+n1+n20n0+1n1+2n2=+1n0+n1+n2=0n0+1n1+2n2n0=n2+1
  4. 具有n个节点的完全二叉树的高度为 l o g 2 n + 1 log_2n+1 log2n+1
  5. 如果将一棵二叉树从左到右从上到下按层次进行不会1-n, 则i=1表示根节点,对于i>1, 如果2i<=n,则节点i的左子节点编号为2i,否则不存在左子节点,如果2i+1<=n,则节点i的右子节点编号为2i+1,否则不存在右子节点,父节点编号为i/2

3. 二叉树的遍历

在这里插入图片描述

3.1 前序遍历

1 2 4 5 3 6 7

//递归
public class BinaryTreeAccessDemo {
	public static void preOrderRecur(Node root) {
		if(root!=null) {
			System.out.print(root.val+" ");
			preOrderRecur(root.left);
			preOrderRecur(root.right);
		}
	}
//迭代
/*
	 * 在遍历左子树之前,将当前节点保存到栈中,当遍历完左子树之后,将该元素出栈,然后找到右子树进行遍历
	 */
	public static void preOrderNoRecur(Node root) {
		if(root!=null) {
			Stack<Node> st=new Stack<>();
			while(!st.isEmpty()||root!=null) {
				while(root!=null) {
					System.out.print(root.val+" ");
					st.push(root);
					root=root.left;
				}
				if(!st.isEmpty()) {
					root=st.pop();
					root=root.right;
				}
			}
		}
		
	}
class Solution {
    List<Integer> ans=new ArrayList<>();
    public List<Integer> preorderTraversal(TreeNode root) {
         if (root != null) {
			LinkedList<TreeNode> st = new LinkedList<>();
			st.push(root);
			while (!st.isEmpty()) {
				TreeNode node = st.pop();
                ans.add(node.val);
                if(node.right!=null)
                    st.push(node.right);//由于是栈 所以先压入最右边的节点
                if(node.left!=null)
                    st.push(node.left);
			}
		}
        return ans;
    
    }
}

3.2 中序遍历

4 2 5 1 6 3 7

public static void inOrderRecur(Node root) {
		if(root!=null) {
			inOrderRecur(root.left);
			System.out.print(root.val+" ");
			inOrderRecur(root.right);
		}
	}
/*
	 * 先找最左边的左子节点,完成左子树的遍历后再进行节点的出栈处理
	 * 和非递归形式的前序遍历类似  区别在于输出节点的时机不同
	 */
	public static void inOrderNoRecur(Node root) {
		if (root != null) {
			Stack<Node> st = new Stack<>();
			while (!st.isEmpty() || root != null) {
				while (root != null) {
					st.push(root);
					root = root.left;// 一直沿着左子树走

				}

				if (!st.isEmpty()) {
					root = st.pop();// 拿出最左边的子节点
					System.out.print(root.val + " ");
					root = root.right;
				}
			}
		}
	}

3.3 后序遍历

4 5 2 6 7 3 1

public static void postOrderRecur(Node root) {
		if(root!=null) {
			postOrderRecur(root.left);
			postOrderRecur(root.right);
			System.out.print(root.val+" ");
		}
	}
/*
	 * 前序和中序遍历中 元素出栈之后就不需要再次访问该元素了
	 * 在后续遍历中 每个节点需要访问两次 遍历完左子树需要访问当前节点1次 遍历完右子树需要访问当前节点1次
	 * 第2次访问之后才处理当前节点  如何判断当前节点是第一次被访问还是第2次被访问?
	 * 当出栈一个元素时(pre)  检查这个元素pre与当前栈顶元素top.right是否相同 相同则说明栈顶节点的左右子树
	 * 都已经处理完 将栈顶元素出栈并处理
	 */
	public static void postOrderNoRecur(Node root) {
		if(root!=null) {
			Stack<Node> st = new Stack<>();
			Node pre=null;
			while(!st.isEmpty()||root!=null) {
				while(root!=null) {
					st.push(root);
					root=root.left;
				}
				root=st.pop();
				//当前节点没有右子节点 或者  root.right=pre说明root节点的左右子树都处理
				//完了 开始处理root节点本身
				if(root.right==null||root.right==pre) {
					System.out.print(root.val+" ");
					pre=root;
					root=null;
				}else {
					st.push(root);//再次入栈 开始处理root的右子树
					root=root.right;
				}
			}
		}
	}
/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
 //前序(根右左)的逆序列即为后序列
class Solution {
    List<Integer> ans=new ArrayList<>();
    public List<Integer> postorderTraversal(TreeNode root) {
        preorderTraversal(root);
        Collections.reverse(ans);
        return ans;
    }
    public List<Integer> preorderTraversal(TreeNode root) {
         if (root != null) {
			LinkedList<TreeNode> st = new LinkedList<>();
			st.push(root);
			while (!st.isEmpty()) {
				TreeNode node = st.pop();
                ans.add(node.val);
                //修改后的前序遍历:根右左顺序
                if(node.left!=null)
                    st.push(node.left);
                if(node.right!=null)
                    st.push(node.right);//由于是栈 所以先压入最右边的节点
			}
		}
        return ans;
    
    }
}
class Solution {
    List<Integer> ans=new ArrayList<>();
    public List<Integer> postorderTraversal(TreeNode root) {
       if(root!=null)
        {
           Stack<TreeNode> st=new Stack<>();
           Set<TreeNode> set=new HashSet<>();
           st.push(root);
           while(!st.isEmpty()){
               TreeNode node=st.peek();
               //当前节点是叶子节点或者是第2次访问(后序遍历某个节点会两次进栈出栈)
               if(node.left==null&&node.right==null||set.contains(node)){
                   st.pop();
                   ans.add(node.val);
                   continue;
               }
               if(node.right!=null)
                    st.push(node.right);
               if(node.left!=null)
                    st.push(node.left);
               set.add(node);
           }
        }
        return ans;
    }
    
}

3.4 层次遍历

1 2 3 4 5 6 7

public static void levelOrder(Node root) {
		if(root!=null) {
			Queue<Node> q=new LinkedList<>();
			q.offer(root);
			while(!q.isEmpty()) {
				int sz=q.size();
				for(int i=0;i<sz;i++) {
					Node node=q.poll();
					System.out.print(node.val+" ");
					if(node.left!=null)
						q.offer(node.left);
					if(node.right!=null)
						q.offer(node.right);
				}
				System.out.println();
			}
		}
	}

4. 二叉树的常见问题

4.1 查找给定二叉树中的最大元素

public static int findMax(Node root) {
		int rootVal=Integer.MIN_VALUE;
		int leftVal=Integer.MIN_VALUE;
		int rightVal=Integer.MIN_VALUE;
		int maxVal=Integer.MIN_VALUE;
		if(root!=null) {
			rootVal=root.val;
			leftVal=findMax(root.left);
			rightVal=findMax(root.right);
			maxVal=Math.max(leftVal, maxVal);
			maxVal=Math.max(rightVal, maxVal);
			maxVal=Math.max(rootVal, maxVal);
		}
		return maxVal;
	}
public static int findMax(Node root) {
		int maxVal=Integer.MIN_VALUE;
		if(root!=null) {
			Queue<Node> q=new LinkedList<>();
			q.offer(root);
			while(!q.isEmpty()) {
				int sz=q.size();
				for(int i=0;i<sz;i++) {
					Node node=q.poll();
					maxVal=Math.max(maxVal, node.val);
					if(node.left!=null)
						q.offer(node.left);
					if(node.right!=null)
						q.offer(node.right);
				}
			}
		}
		return maxVal;
	}

4.2 判断某个元素在二叉树中是否存在

public static boolean isEleExists(Node root,int target) {
		if(root!=null) {
			if(root.val==target)
				return true;
			else {
				if(isEleExists(root.left, target))
					return true;
				else if(isEleExists(root.right, target))
					return true;
			}
		}
		return false;
public static boolean isEleExists(Node root,int target) {
		if(root!=null) {
			Queue<Node> q=new LinkedList<>();
			q.offer(root);
			while(!q.isEmpty()) {
				int sz=q.size();
				for(int i=0;i<sz;i++) {
					Node node=q.poll();
					if(node.val==target)
						return true;
					if(node.left!=null)
						q.offer(node.left);
					if(node.right!=null)
						q.offer(node.right);
				}
			}
		}
		return false;
	}

4.3 向二叉树中插入一个节点

/*
	 * 在二叉树中找到左子节点或右子节点为空的节点,然后插入即可
	 */
	public static void insertNode(Node root,int val) {
		if(root==null)
			root=new Node(val);
		else {
			Queue<Node> q=new LinkedList<>();
			q.offer(root);
			while(!q.isEmpty()) {
				int sz=q.size();
				for(int i=0;i<sz;i++) {
					Node node=q.poll();
					if(node.left!=null)
						q.offer(node.left);
					if(node.right!=null)
						q.offer(node.right);
					if(node.left==null) {
						node.left=new Node(val);
						return;
					}
					if(node.right==null) {
						node.right=new Node(val);
						return;
					}	
					
				}
			}
		}
	}

4.4 求二叉树的节点个数

public static int getNodes(Node root) {
		if(root==null)
			return 0;
		return getNodes(root.left)+getNodes(root.right)+1;
	}

当然也可以使用迭代的层次遍历来计数

4.5 删除一棵二叉树

采用后续遍历,先删除子节点,然后再删除父节点

public static void deleteBinaryTree(Node root) {
		if(root==null)
			return;
		deleteBinaryTree(root.left);
		deleteBinaryTree(root.right);
		root.left=null;
		root.right=null;
	}

4.6 逆序逐层输出二叉树的元素

在这里插入图片描述

4 5 6 7 2 3 1

public static void leverlReverse(Node root) {
		if(root!=null) {
			Queue<Node> q=new LinkedList<>();
			Stack<Node> st = new Stack<>();
			q.offer(root);
			while(!q.isEmpty()) {
				
					Node node=q.poll();
					st.push(node);
					if(node.right!=null)//因为后面要入栈 右子节点先入队
						q.offer(node.right);
					if(node.left!=null)
						q.offer(node.left);
			}
			while(!st.isEmpty()) {
				System.out.print(st.pop().val+" ");
			}
		}
	}

4.7 求二叉树的高度

public static int getHeight(Node root) {
		if(root==null)
			return 0;
		return Math.max(getHeight(root.left),getHeight(root.right))+1;
	}
public static int getHeight(Node root) {
		int depth=0;
		if(root!=null) {
			Queue<Node> q=new LinkedList<>();
	
			q.offer(root);
			while(!q.isEmpty()) {
				int sz=q.size();
				for(int i=0;i<sz;i++) {
					Node node=q.poll();
					if(node.left!=null)
						q.offer(node.left);
					if(node.right!=null)
						q.offer(node.right);
				}
				depth++;//每遍历完一层高度加一
			}
		}
		return depth;
		
	}

4.8 判断两棵二叉树是否相同

public static boolean isSameTrees(Node root1,Node root2) {
		if(root1==null&&root2==null)
			return true;
		if(root1==null||root2==null)
			return false;
		if(root1.val!=root2.val)
			return false;
		return isSameTrees(root1.left, root2.left)&&isSameTrees(root1.right, root2.right);
	}

4.9 将一棵树转化成对应的镜像

在这里插入图片描述

public static void changeToMirrorTree(Node root) {
		if(root!=null) {
			changeToMirrorTree(root.left);
			changeToMirrorTree(root.right);
			Node tmp=root.left;
			root.left=root.right;
			root.right=tmp;
		}
	}
public static void changeToMirrorTree(Node root) {
		if(root!=null) {
			Queue<Node> q=new LinkedList<>();
			q.offer(root);
			while(!q.isEmpty()) {
				Node node=q.poll();
				if(node.left!=null)
					q.offer(node.left);
				if(node.right!=null)
					q.offer(node.right);
				Node tmp=node.left;
				node.left=node.right;
				node.right=tmp;
			}
 		}
	}

递归是自底向上交换,迭代是自顶向下交换

4.10 判断两棵树是否互为镜像

public static boolean areMirrors(Node root1,Node root2) {
		if(root1==null&&root2==null)
			return true;
		if(root1==null||root2==null)
			return false;
		if(root1.val!=root2.val)
			return false;
		return areMirrors(root1.left, root2.right)&&areMirrors(root1.right, root2.left);
	}

4.11 计算二叉树的直径

直径:任意两个结点路径长度中的最大值。这条路径可能穿过也可能不穿过根结点
在这里插入图片描述

思路:路径长度=路径经过的节点数-1 可以递归地求出以每个节点为根节点形成的子树的高度,对于某个节点node而言,它所能形成的路径的节点数目最多=左子树高度+右子树高度+1,因此可以复用求二叉树高度的代码,加一点改动,用一个全局变量来更新节点数目

class Solution {
    int d;
    public int diameterOfBinaryTree(TreeNode root) {
        d=1;
        depth(root);
        return d-1;
    }
    public int depth(TreeNode root){
        if(root==null)
            return 0;
        int left=depth(root.left);
        int right=depth(root.right);
        d=Math.max(d,left+right+1);
        return Math.max(left,right)+1;
    }
}
//O(n)
//O(h) h是树的高度

4.12 输出二叉树中所有从根节点到叶子节点的路径

public static void printPath(Node root) {
		int[] path=new int[10];//假设当前最多只有10个节点
		printPath(root, path, 0);
		
	}
	public static void printPath(Node root,int[] path,int depth) {
		if(root==null)
			return;
		path[depth++]=root.val;
		if(root.left==null&&root.right==null) {
			for(int i=0;i<depth;i++)
				System.out.print(path[i]+" ");
			System.out.println();
		}else {
			printPath(root.left, path, depth);
			printPath(root.right, path, depth);
		}
	}

4.13 判断是否存在一条从根节点到叶子节点的路径使得数据和为定值

public static boolean hasPathSum(Node root,int sum) {
		if(root==null)
			return sum==0;
		sum-=root.val;
		System.out.println("sum="+sum);
		return hasPathSum(root.left, sum)||hasPathSum(root.right, sum);
	}

4.14 求出二叉树所有节点数据之和

public static int getSum(Node root) {
		if(root==null)
			return 0;
		return root.val+getSum(root.left)+getSum(root.right);
	}

4.15 打印二叉树中某个节点所有的祖先节点

在这里插入图片描述

public static boolean printAllAncestors(Node root,Node target) {
		if(root==null)
			return false;
		if(root.left==target||root.right==target
				||printAllAncestors(root.left, target)
				||printAllAncestors(root.right, target)) {
			System.out.print(root.val+" ");
			return true;
		}
		return false;
			
	}

4.16 查找二叉树中两个节点的最近公共祖先

public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        if(root==null)
			return root;//以root为根节点没有找到p q
		if(root==p||root==q)//找到p或q则返回
			return root;
		TreeNode left=lowestCommonAncestor(root.left, p, q);//在左子树中查找p q
		TreeNode right=lowestCommonAncestor(root.right, p, q);//在右子树中查找p q
		if(left!=null&&right!=null)
			return root;//p q出现在两个子树在 root就是最近祖先
		return left!=null?left:right;//p q只存在于一棵子树中 返回该子树(也可能left right都为null)
    }

4.17 填充二叉树节点的兄弟节点指针

在这里插入图片描述

class Solution {
    public Node connect(Node root) {
        if(root==null)
            return null;
        if(root.left!=null)
            root.left.next=root.right;
        if(root.right!=null){
            if(root.next!=null)
                root.right.next=root.next.left;
            else
                root.right.next=null;
        }
        connect(root.left);
        connect(root.right);
        return root;
           
    }
}

5. N叉树

表示方法1:

class NTreeNode{
	int val;
	ArrayList<NTreeNode> children;
}
class Node{
	int val;
	Node left;
	Node right;
}

将原先的left和right取消,用一个集合来表示某个节点的所有子节点

表示方法2:

class NTreeNode{
	int val;
	Node first;//指向第一个子节点
	NTreeNode next;
}

next指向root节点第一个子节点,其余的子节点通过next指针连接起来
在这里插入图片描述

5.1 N叉树的最大深度

/*
// Definition for a Node.
class Node {
    public int val;
    public List<Node> children;

    public Node() {}

    public Node(int _val) {
        val = _val;
    }

    public Node(int _val, List<Node> _children) {
        val = _val;
        children = _children;
    }
};
*/

class Solution {
   
    public int maxDepth(Node root) {
        if(root==null)
            return 0;
       int depth=0;
       LinkedList<Node> q=new LinkedList<>();
       q.offer(root);
       while(!q.isEmpty())
       {
           int sz=q.size();
           for(int i=0;i<sz;i++)
           {
               Node node=q.poll();
           for(Node child:node.children)
           {
               if(child!=null)
                    q.offer(child);
           }
           }
           depth++;
       }
       return depth;
        
    }
}

5.2 N叉树的前序遍历

class Solution {
    List<Integer> ans=new ArrayList<>();
    public List<Integer> preorder(Node root) {
       if (root != null) {
			LinkedList<Node> st = new LinkedList<>();
			st.push(root);
			while (!st.isEmpty()) {
				Node temp = st.pop();
                ans.add(temp.val);
				for(int i=temp.children.size()-1;i>=0;i--)
                    st.push(temp.children.get(i));//由于是栈 所以先压入最右边的节点
			}
		}
        return ans;
    }
  
}

5.3 N叉树的后序遍历

class Solution {
    List<Integer> ans=new ArrayList<>();
    public List<Integer> postorder(Node root) {
        if(root!=null)
        {
           Stack<Node> st=new Stack<>();
           Set<Node> set=new HashSet<>();
           st.push(root);
           while(!st.isEmpty()){
               Node node=st.peek();
               //当前节点是叶子节点或者是第2次访问(后序遍历某个节点会两次进栈出栈)
               if(node.children.size()==0||set.contains(node)){
                   st.pop();
                   ans.add(node.val);
                   continue;
               }
               //从最右边节点开始入栈
               for(int i=node.children.size()-1;i>=0;i--)
                    st.push(node.children.get(i));
                set.add(node);
           }
        }
        return ans;
    }
   
}
//O(n)
//O(n)
//利用前序遍历  使用根右左的顺序访问二叉树得到一个“前序序列”  然后再将该序列反转即可得到后序序列
class Solution {
    List<Integer> ans=new ArrayList<>();
    public List<Integer> postorder(Node root) {
        preorder(root);
        Collections.reverse(ans);
        return ans;
    }
    public List<Integer> preorder(Node root) {
       if (root != null) {
			LinkedList<Node> st = new LinkedList<>();
			st.push(root);
			while (!st.isEmpty()) {
				Node temp = st.pop();
                ans.add(temp.val);
                //修改后的前序遍历:原来是根左右  现在是根右左
				for(int i=0;i<temp.children.size();i++)
                    st.push(temp.children.get(i));//由于是栈 所以先压入最左边的节点
			}
		}
        return ans;
    }
   
}
//O(n)
//O(n)

5.4 N叉树的层次遍历

/*
// Definition for a Node.
class Node {
    public int val;
    public List<Node> children;

    public Node() {}

    public Node(int _val) {
        val = _val;
    }

    public Node(int _val, List<Node> _children) {
        val = _val;
        children = _children;
    }
};
*/

class Solution {
    public List<List<Integer>> levelOrder(Node root) {
        LinkedList<Node> q=new LinkedList<>();
        List<List<Integer>> ans=new ArrayList<>();
        if(root==null)
            return ans;
        q.offer(root);
        while(!q.isEmpty()){
           int sz=q.size();//当前队列中的节点 即上一层的节点个数
           List<Integer> list=new ArrayList<>();
           for(int i=0;i<sz;i++){
               Node node=q.poll();
               list.add(node.val);
               for(Node children:node.children)//加入当前节点node的所有子节点
               {
                   q.offer(children);
                  
               }
           }
           ans.add(list);//将当前层的节点集合加入答案中
        
        }
        return ans;

    }
}
//O(n)
//O(n)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

CodePanda@GPF

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值