算法02-二叉树
前言
开始接触树结构的时候心里面的有点复杂的,因为知道树是比较复杂的,还有就是树结构的强大性,树的应用也是比较广泛的。
下面是有关树的一些算法题以及概念
一、树的基本概念
- 结点:一个数据元素以及其若干指向子树的分支;
- 结点的度:
树的度:结点所拥有的子树的棵树称为结点的度,树的节点度的最大值称为树的度。
- 叶子节点与非叶子节点:
树中度为0的结点称为叶子节点(或者终端节点)。相对应的度不为0的结点称为非叶子节点(或者非终端节点或分支节点)。
- 树的深度
树中节点的最大层次值,又称为树的高度。
二、二叉树
- 二叉树的定义:
二叉树是n(n>=0)个节点的有限集合,若n=0时称为空树
1、有且只有一个特殊的称为树的根节点(root)
2、若n>1时,其余的结点被分成两个互不相交的子集,分别称为左子树和右子树,并且都是二叉树。
- 二叉树的遍历方式
1、前序遍历:根左右
2、中序遍历:左根右
3、后序遍历:左右根
- 上面是三种遍历方式的口诀:下面是自己的理解
实际上的遍历与上面的都是一样的,在我自己开始看的时候也是有一点的小懵逼,但是这个二叉树有种理解为,可以将他当做递归的最好实现。那么我们在想象一下递归的操作,一个大问题转化为一个个的相同的小问题。那么在二叉树中就是这样的,一个根节点下面有两个子节点,在这两个子节点下面可能还有这两个子节点的子节点,那么我们这个时候就可以将这两个根节点下的子节点当做是两个根节点,这个时候对于两个子节点来说下面的他们的分别的节点一样可以当做是一个相同的问题。
- 画画水平有限–下面是前序遍历的丑图(中序遍历和后续遍历差不多就不画了)
下面是关于二叉树的一些算法题
三、力扣算法题
3.1、二叉树的前序遍历-力扣144题
对于一个大问题,其中有很多的小问题,这个时候就适合使用递归的思想。在二叉树的前序遍历中就会使用这样的思想,我们遍历一个二叉树的时候会一个个的将树节点存到一个集合中用作后面的输出,那么这里就可以知道,我们的存储方式是一个个的按照二叉树的前序遍历的方式进行相关的一步步的存储,在方法的调用的时候先进行根节点的调用存储,在进行左边叶子节点,当这个节点是叶子节点(后面没有其他的叶子节点)的时候就返回,如果是叶子节点,就继续进行相同的操作。话不多说看代码
/**
* 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> res;
public List<Integer> preorderTraversal(TreeNode root) {
res = new ArrayList<>();
dfs(root);
return res;
}
public void dfs(TreeNode root){
if(root==null) return;
res.add(root.val);
dfs(root.left);
dfs(root.right);
}
}
3.2、多叉树的前序遍历–力扣589题
树的分支会有许多,在对这样的树进行前序遍历的时候就与二叉树的前序遍历大同小异,这样的树的叶子节点的数量是不确定的,存储在一个集合里面,那么我们在对这样的树结构遍历时,类似于左右叶子节点的遍历。将每个节点直接遍历进行存储
/*
// 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 {
List<Integer> res = null;
public List<Integer> preorder(Node root) {
res = new ArrayList<>();
dfs(root);
return res;
}
public void dfs(Node root){
if(root == null) return;
res.add(root.val);
List<Node> children = root.children;
for(Node chi:children){
dfs(chi);
}
}
}
3.3、反转二叉树–力扣226题
这个题是将二叉树的左右节点进行反转,在这个过程中,我们可以将一个节点下的左右节点看成一个小问题,在外面定义一个反转的方法,进行反转,那么在这个节点下可能还会有其他的叶子节点,这个时候将这个叶子节点下的左右节点又看成一个小问题,再进行反转,依次再看右边的节点(当然,没有的话就直接返回根节点就可以了)…这个地方很容易就会想象到递归的方式,根节点的左右叶子节点使用的是和根节点相同的方式,那么直接传入这个根节点就可以了。看如下代码:
/**
* 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 {
//将左右节点进行交换,当走到叶子节点时停止
public TreeNode invertTree(TreeNode root) {
if(root == null) return root;
swap(root);
invertTree(root.left);
invertTree(root.right);
return root;
}
//自己定义的节点交换方法
public void swap(TreeNode root){
TreeNode temp = root.left;
root.left = root.right;
root.right = temp;
}
}
3.4、剑指offer32-从上到下打印二叉树
这个题目的要求是自上而下的输出这个树,并且是分层的,也就是说是一层一层的节点进行输出存储到一个集合中,这个时候一个集合对应的就是一层节点。这里我们使用的还是递归,不同的是每一层的递归丢携带一个对应的层数用作判断是不是一层,是同一层的节点数据则进行存储返回,具体看如下代码:(还有一种使用队列的方法)
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
List<List<Integer>> res;
public List<List<Integer>> levelOrder(TreeNode root) {
res = new ArrayList<>();
dfs(root,0);
return res;
}
public void dfs(TreeNode root,int k){
if(root == null) return;
if(res.size() == k) res.add(new ArrayList<>());
res.get(k).add(root.val);
dfs(root.left,k+1);
dfs(root.right,k+1);
}
}
3.5、二叉树的层序遍历–力扣107题
方法同上,最后得到的结果使用 Collections.reverse(res);//系统自带的反转集合即可。代码如下
/**
* 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<List<Integer>> res;
public List<List<Integer>> levelOrderBottom(TreeNode root) {
res = new ArrayList<>();
dfs(root,0);
Collections.reverse(res);//系统自带的反转集合
return res;
}
public void dfs(TreeNode root,int k){
if(root == null) return;
if(res.size() == k) res.add(new ArrayList<>());
res.get(k).add(root.val);
dfs(root.left,k+1);
dfs(root.right,k+1);
}
}
3.6、二叉树的锯齿形层序遍历–力扣103题
与上面的层序遍历类似,重点在于后面对于输出的数组集合的处理,可以知道这是锯齿状的,那么就是相隔一个集合对下一个集合进行反转,这里就可以得到下标为奇数时进行反转,为偶数时不处理,这时只需要改动最后的返回数据的位置就可以了。代码如下:
/**
* 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<List<Integer>> res;
public List<List<Integer>> zigzagLevelOrder(TreeNode root){
res = new ArrayList<>();
dfs(root,0);
for(int i = 0;i<res.size();i++){
if(i%2!=0){
Collections.reverse(res.get(i));//系统自带的反转集合
}
}
return res;
}
public void dfs(TreeNode root,int k){
if(root == null) return;
if(res.size() == k) res.add(new ArrayList<>());
res.get(k).add(root.val);
dfs(root.left,k+1);
dfs(root.right,k+1);
}
}
3.7、路径总和-力扣112题
- 给你二叉树的根节点 root 和一个表示目标和的整数 targetSum ,判断该树中是否存在 根节点到叶子节点 的路径,这条路径上所有节点值相加等于目标和 targetSum 。
解题思想:这个题是寻找这个二叉树中有没有一个根节点到叶子节点的路径正好是我们输入的一个数字,那么我们选择一个个的遍历然后相减获取每一次的值与targetSum进行对比,相同返回true,不同返回false。通过这样的方式每个节点的左右叶子节点都可以是一个根节点。通过递归的方式,当满足或者不满足时返回。
/**
* 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 {
public boolean hasPathSum(TreeNode root, int targetSum) {
if(root == null) return false;
if(root.left == null && root.right==null && root.val==targetSum) return true;
targetSum -=root.val;
return hasPathSum(root.left,targetSum)|| hasPathSum(root.right,targetSum);
}
}
3.8、从前序和中序遍历序列构造二叉树-力扣105题
- 给定一棵树的前序遍历 preorder 与中序遍历 inorder。请构造二叉树并返回其根节点。
题解思路:前序遍历的要点是根左右,中序遍历的要点是左根右,那么这个可以得知,在前序遍历中的第一个节点是整个二叉树的根节点,那么在中序遍历中找到这个根节点得到的由这个根节点分割的两块地方,前面一块是整个二叉树的左节点,右边一块是整个二叉树的右节点。好,这个时候就可以知道了整个二叉树的左边和右边的节点了,那么再对两边的节点进行相同的操作就可以得到一个完整的二叉树了,每次存储根节点,递归实现这样的相同操作实现并返回就可以了,详细看代码:
/**
* 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 {
//定义一个map集合用来存储中序遍历的数值以及下标
Map<Integer,Integer> map;
public TreeNode buildTree(int[] preorder, int[] inorder) {
map = new HashMap<>();
for(int i = 0;i<inorder.length;i++){
map.put(inorder[i],i);
}
return helper(preorder,inorder,0,preorder.length-1,0,inorder.length-1);
}
public TreeNode helper(int[] preorder,int[] inorder,int p_start,int p_end,int i_start,int i_end){
if(p_start>p_end) return null;
int rootVal = preorder[p_start];//获取前序遍历的根节点
TreeNode root = new TreeNode(rootVal);//定义一个树节点将根节点存进去
int mid = map.get(rootVal);//获取中序遍历中存储的根节点的下标
int leftNum = mid - i_start;//获取新一段的长度 (节点的数组的长度)
root.left = helper(preorder,inorder,p_start+1,p_start+leftNum,i_start,mid-1);
root.right = helper(preorder,inorder,p_start+leftNum+1,p_end,mid+1,i_end);
return root;
}
}
3.9、剑指offer 54 二叉搜索树的第k大节点
二叉搜索数:左节点的值小于根节点的值小于右节点的值—左<根<右–直接中序遍历就可以将整个二叉树按从小到大的顺序输出出来。但是在这个题中,我们将二叉树的右节点树的节点数与输入的第K做比较,如果得到的右边的节点总数大于K,那么则可以说明搜索到的最大的第k个节点就存在于右边的子节点中,再在这个节点下进行相同的操作。在这其中需要注意的是,如果右边子树的数量cnt大于K那么这个数在右边,如果cnt小于k那么在左边,这个时候要将K做变化-减去整个右边的树的数量。如果等于cnt+1那么返回这个节点的值即可。这里同样是运用递归的方式每次都将下面的子树转化为这样的问题即可。
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
//二叉搜索数:左节点的值小于根节点的值小于右节点的值---左<根<右--直接中序遍历就可以将整个二叉树按从小到大的顺序输出出来
public int kthLargest(TreeNode root, int k) {
int cnt = getCount(root.right);
if(cnt>=k) return kthLargest(root.right,k);
if(cnt+1==k) return root.val;
return kthLargest(root.left,k-cnt-1);
}
public int getCount(TreeNode root){
if(root == null) return 0;
return 1+getCount(root.left)+getCount(root.right);
}
}
3.10、剑指offer 54 二叉搜索树的第k大节点
二叉搜索数:左节点的值小于根节点的值小于右节点的值—左<根<右–直接中序遍历就可以将整个二叉树按从小到大的顺序输出出来。但是在这个题中,我们将二叉树的右节点树的节点数与输入的第K做比较,如果得到的右边的节点总数大于K,那么则可以说明搜索到的最大的第k个节点就存在于右边的子节点中,再在这个节点下进行相同的操作。在这其中需要注意的是,如果右边子树的数量cnt大于K那么这个数在右边,如果cnt小于k那么在左边,这个时候要将K做变化-减去整个右边的树的数量。如果等于cnt+1那么返回这个节点的值即可。这里同样是运用递归的方式每次都将下面的子树转化为这样的问题即可。
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
//二叉搜索数:左节点的值小于根节点的值小于右节点的值---左<根<右--直接中序遍历就可以将整个二叉树按从小到大的顺序输出出来
public int kthLargest(TreeNode root, int k) {
int cnt = getCount(root.right);
if(cnt>=k) return kthLargest(root.right,k);
if(cnt+1==k) return root.val;
return kthLargest(root.left,k-cnt-1);
}
public int getCount(TreeNode root){
if(root == null) return 0;
return 1+getCount(root.left)+getCount(root.right);
}
}
总结
最近算法没有进入状态,希望自己能够慢慢的进入,二叉树可以进一步理解递归思想,这里做个小总结随时可以查看。