文章目录
- 1. 树的定义和术语
- 2. 二叉树的相关概念
- 3. 二叉树的遍历
- 4. 二叉树的常见问题
- 4.1 查找给定二叉树中的最大元素
- 4.2 判断某个元素在二叉树中是否存在
- 4.3 向二叉树中插入一个节点
- 4.4 求二叉树的节点个数
- 4.5 删除一棵二叉树
- 4.6 逆序逐层输出二叉树的元素
- 4.7 求二叉树的高度
- 4.8 判断两棵二叉树是否相同
- 4.9 将一棵树转化成对应的镜像
- 4.10 判断两棵树是否互为镜像
- 4.11 计算二叉树的直径
- 4.12 输出二叉树中所有从根节点到叶子节点的路径
- 4.13 判断是否存在一条从根节点到叶子节点的路径使得数据和为定值
- 4.14 求出二叉树所有节点数据之和
- 4.15 打印二叉树中某个节点所有的祖先节点
- 4.16 查找二叉树中两个节点的最近公共祖先
- 4.17 填充二叉树节点的兄弟节点指针
- 5. N叉树
1. 树的定义和术语
定义:
- 普通节点:包含子节点的节点
- 叶子节点:没有子节点的节点
- 根节点:没有父节点的节点
- 节点的度:节点拥有的子树的个数
- 树的度:所有节点中度的最大值
- 节点的层次:根节点的层次为1,往下节点的层次值为父节点的层次值加一
- 树的高度(深度):节点层次值的最大值
- 森林:2棵或2棵以上的互不相交的树的集合,删除一棵树的根,就得到一个森林
树的两种表示方法:
1. 父节点表示法:每个节点都记录它的父节点
定义树节点时增加了一个parent域,该parent域用于保存该节点的父节点在数组中的索引,添加节点时只需要将新节点的parent域的值设为其父节点在数组中的索引即可
特点:找某个节点的父节点容易,但是找子节点麻烦,需要遍历整个数组
2. 子节点链表示法: 父节点记住子节点
树节点中增加了一个first域,该域用于保存对该节点的子节点的引用,添加节点时只需要找到指定父节点的子节点链的最后节点,最后一个节点的first指向新的节点
特点:找子节点容易,找父节点难,需要遍历整个数组
2. 二叉树的相关概念
二叉树每个节点最多只有两棵子树,左边的称为左子树,右边的称为右子树,二者不能互换,因此二叉树是有序树
二叉树与树的区别:
- 树中节点的度没有限制,二叉树中的节点的度最大为2
- 无序树的节点无左右之分,二叉树的节点有左右之分
满二叉树:高度为k. 有
2
k
−
1
2^k-1
2k−1个节点
完全二叉树:除最后一层外节点没有满,并且最后一层却是的节点都在右边
二叉树的性质:
- 第i层的节点数目最多为 2 i − 1 2^i-1 2i−1 i从1开始
- 深度为k的二叉树最多有 2 k − 1 2^k-1 2k−1个节点----满二叉树
- 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+n2度数之和:0∗n0+1∗n1+2∗n2节点数=度数之和+1n0+n1+n2=0∗n0+1∗n1+2∗n2因此n0=n2+1
- 具有n个节点的完全二叉树的高度为 l o g 2 n + 1 log_2n+1 log2n+1
- 如果将一棵二叉树从左到右从上到下按层次进行不会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)