算法总结七:Tree和recursion的相互成全

一。Tree的特点:

每个节点都只有有限个子节点或无子节点;
没有父节点的节点称为根节点;
每一个非根节点有且只有一个父节点;
每个节点可以分出多个不相交的子树;
树里面没有环路(cycle)

二。为什么需要树?

因为它结合了另外两种数据结构的优点: 一种是有序数组,另一种是链表。在树中查找数据项的速度和在有序数组中查找一样快, 并且插入数据项和删除数据项的速度也和链表一样。
from zhihu

这么理解:
查找时,有序数组应用index实现O(1)的查找。链表查找需要O(n)。树介于其中,查找时如果是标准的满二叉树,实现log2n的查找时间。
插入删除时,有序数组需要expand空间,再插入,平均时间O(n),链表O(1)。树介于其中,插入删除时如果是标准的满二叉树,实现log2n的时间,进行查询-再插入删除。

三。Tree的种类:
Ordered Tree vs Unordered Tree
兄弟节点有顺序的树,称为有序树,兄弟节点之间无顺序的树称为无序树。
Tree A中左孩子比右孩子小
Tree B中左孩子比右孩子大
在这里插入图片描述

四。Tree相关概念:
degree节点的:一个节点含有的子树的个数称为该节点的度;
degree树的:一棵树中,最大的节点的度称为树的度;
leaf叶节点终端节点:度为零的节点;
non-leaf非终端节点分支节点:度不为零的节点;
parent父节点:若一个节点含有子节点,则这个节点称为其子节点的父节点;
child子节点:一个节点含有的子树的根节点称为该节点的子节点;
sibling兄弟节点:具有相同父节点的节点互称为兄弟节点;
level节点的层次:从根开始定义起,根为第1层,根的子节点为第2层,以此类推;
depth深度:对于任意节点n,n的深度为从根到n的唯一路径长,根的深度为0;
height高度:对于任意节点n,n的高度为从n到一片树叶的最长路径长,所有树叶的高度为0;
cousin堂兄弟节点:父节点在同一层的节点互为堂兄弟;
ancestor节点的祖先:从根到该节点所经分支上的所有节点;
descendant子孙:以某节点为根的子树中任一节点都称为该节点的子孙。
forest森林:由m(m>=0)棵互不相交的树的集合称为森林; from 维基百科:树(数据结构)

五。常见的binary tree:

binary tree二叉树:每个节点最多含有两个子树的树称为二叉树;   
complete binary tree完全二叉树:对于一颗二叉树,假设其深度为d(d>1)。除了第d层外,其它各层的节点数目均已达最大值,且第d层所有节点从左向右连续地紧密排列,这样的二叉树被称为完全二叉树;    
full binary tree满二叉树:所有叶节点都在最底层的完全二叉树;   
balanced binary tree平衡二叉树(AVL树):当且仅当任何节点的两棵子树的高度差不大于1的二叉树;   
Binary Search Tree排序二叉树(二叉查找树):也称二叉搜索树、有序二叉树;  
Huffman Tree霍夫曼树:带权路径最短的二叉树称为霍夫曼树或最优二叉树;(带权路径长度: WPL)

https://www.youtube.com/watch?v=dM6us854Jk0
构建顺序:s:3 nl:1两个节点作为叶子先集结于一个根节点,根节点记录了两个节点的权重总和为T1:4
T1:4和t:4继续集结于T2节点,权重总和为8
T2:8和a:10继续集结于T3节点,权重总和为18 …
在这里插入图片描述

在这里插入图片描述

https://www.jianshu.com/p/95fba425be44  
可以看出,霍夫曼树对字母或者单词出现的频率进行了统计,保证出现频率最高的在树顶,查询时可以很快找到。减小时间成本。
出现频率低的查找次数少,也能保证在log2n的时间内找到。

B树
一种对读写操作进行优化的自平衡的二叉查找树,能够保持数据有序,拥有多于两个子树。

a B-tree of order m is a tree which satisfies the following properties:
Every node has at most m children.
Every non-leaf node (except root) has at least ⌈m/2⌉ child nodes.
The root has at least two children if it is not a leaf node.
A non-leaf node with k children contains k − 1 keys.
All leaves appear in the same level and carry no information.

bilibili视频讲解 2分钟开始
bilibili视频讲解 10分钟开始 B-树查找删除插入
区别B树和B+树:
B树
每个节点都作为临界点包含具体信息。
100 155 226
下一层就是(0,100)(100,155)(155,226)不包含临界点在这里插入图片描述
实现:

在这里插入代码片

B树中的插入10分钟开始 B-树查找插入
例子:1,2,6,7,11,4,8,13,10,5,17,9,16,20,3,12,14,18,19,15
树必须是五阶树,由4个数字按顺序分割为5个区间,最少2个数,4个数为满,超过需要relocate出去。
插入的过程就是构建树的过程:

  1. 前四是1,2,6,7
  2. 插入11时发现长度超过4,=5
  3. 1,2,6,7,11中提取index在中间的数6作为父结点,12在左分支711在右分支
  4. 插入4,8,13
  5. 插入10时分支满5个,将index在中间的数10提取到上层,和6同一层,78为同一个分支,11,13为同一个分支
  6. 插入5,17 ,9 ,16
  7. 插入20时11131617分支满5个,将index在中间的数16提取到上层,和10同一层,11,13为同一个分支,17,20为同一个分支
  8. 插入3时12245分支满5个,将index在中间的数3提取到上层,1,2为同一个分支,4,5为同一个分支
  9. ,12 ,14,18,19
  10. 插入15时11121314分支满5个,将index在中间的数13提取到上层, 11,12为同一个分支,1415为同一个分支;36101316层满5个,将index在中间的数10提取到上层,36为同一个分支,13,16为同一个分支

B树中的删除15分钟开始 B-树查找删除
例子:上面的树中删除8,16,15,4
删除8时,是叶子结点,并且看到分支中节点为3个,满足[2-4]个的要求。直接删除8;
删除16时,非叶子结点,删除后需要提取左分支中的最大值15or右分支中的最小值17来替代。考虑15所处的分支只有两个结点,那么提取右分支中的最小值17上来,删除16。
删除15时,是叶子结点,并且看到分支中节点为2个,和兄弟借一个结点。左兄弟也只有11、12两个结点,向2右兄弟借,提取右分支中的最小值18上来,挪17下来,删除15.

B+树
除了叶子节点,其他节点只是索引,不包含具体信息
root节点相当于:(0, 34](34,52]
在这里插入图片描述
Mysql索引使用B+树,数据都在叶节点上,每次查询都需要访问到叶节点。【慢】
MongoDB索引使用B-树,所有节点都有Data域,只要找到指定索引就可以进行访问,无疑单次查询平均快于Mysql。

实现:

在这里插入代码片

六。Tree和recursion的结合
对于递归的问题,我们一般都是从上往下递归的,直到递归到最底,再一层一层着把值返回。
帅地博客讲解recursion
1、第一递归函数功能
2、找出递归结束的条件
3、找出函数的等价关系式

fibo(n):
1、第一递归函数功能
假设 f(n) 的功能是求第 n 项的值,代码如下:

int f(int n){
   
}

2、找出递归结束的条件
显然,当 n = 1 或者 n = 2 ,我们可以轻易着知道结果 f(1) = f(2) = 1。所以递归结束条件可以为 n <= 2。代码如下:

int f(int n){
   
    if(n <= 2){
   
        return n;
    }
}

3、找出函数的等价关系式

int f(int n){
   
    // 1.先写递归结束条件
    if(n <= 2){
   
        return n;
    }
    // 2.接着写等价关系式
    return f(n-1) + f(n - 2);
}

==============
Tree和recursion的结合我们关心三个问题:
1.子问题是什么?
2.当前做点什么?
3.返回给上一层什么?

实际解题中,会出现问题2和问题3一致,或者不一致。不一致的情况需要多思考。
return给上一层的是什么?
问题真正求解的答案是什么?

6.1 问题2和问题3一致 的情况
例如:
222. Count Complete Tree Nodes

class Solution {
   
    public int countNodes(TreeNode root) {
   
        if(root==null) return 0;
        int leftRes=countNodes(root.left);
        int rightRes=countNodes(root.right);
        //if(rightRes==0) return leftRes+1;  
        //simplify: even when rightRes=0, we can use the following:
        return rightRes+leftRes+1;
    }
}

6.2 问题2和问题3不一致 的情况
Tree的recursion中,经常出现当前层结果和返回给上一层的内容不一致的情况。

例如:543. Diameter of Binary Tree
dfs中,cur level执行的是更新结果:

res[0]=Math.max(res[0],leftlen+rightlen);

return to upper level的却是:

return Math.max(leftlen,rightlen);

怎么算长度?count nodes first, then -1 to get lenghs of edges
method1:

class Solution {
   
//int res
     public int diameterOfBinaryTree(TreeNode root) {
   
        int[] res={
   1};
        dfs(root,res);
        return res[0]-1;//count nodes first, then -1 to get lenghs of edges
    }
    private int dfs(TreeNode root, int[] res) {
   
        if(root==null) return 0;
        
        //lchild rchild
        int leftlen= dfs(root.left,res);
        int rightlen= dfs(root.right,res);
        
        //cur level
        res[0]=Math.max(res[0],leftlen+rightlen+1);
        //return to upper level
        return Math.max(leftlen,rightlen)+1;
    }
}

怎么算长度?
method2:
先算边,最后加上和父节点的一条边

class Solution {
   
    int res;
    public int diameterOfBinaryTree(TreeNode root) {
   
        res=0;
        dfs(root);
        return res;
    }
    int dfs(TreeNode root){
   
         if(root==null) return 0;
        int leftR=dfs(root.left);
        int rightR=dfs(root.right);
        res=Math.max(res,leftR+rightR);// 按节点数相加,5--》2,2--〉1,3--》1
        
        //rememb to add 1 for the path connecting the node and its parent
        return Math.max(leftR,rightR)+1; //比单枝节点数多1  5--》2,2--》1
    }
    
}

563. Binary Tree Tilt
当前层累加差值:

totalTilt[0]+= Math.abs(leftRes-rightRes);

recursively返回给上一层的,是sum

return leftRes+rightRes+root.val;

class Solution {
   
    public int findTilt(TreeNode root) {
   
        int[] totalTilt={
   0};
        getSum(root,totalTilt);//recursion的副产品是totalTilt
        return totalTilt[0];
    }
    protected int getSum(TreeNode root,int[] totalTilt){
   
        if(root==null) return 0; 
        int leftRes=getSum(root.left,totalTilt);
        int rightRes=getSum(root.right,totalTilt);
        totalTilt[0]+= Math.abs(leftRes-rightRes);//当前层累加差值 //副产品是totalTilt
        return leftRes+rightRes+root.val; //recursively返回给上一层的,是sum
    }
}

687. Longest Univalue Path
当前层验证val相同并增1,
recursively返回给上一层的,是larger count to the upper level.

class Solution {
   
    int res;
    public int longestUnivaluePath(TreeNode root) {
   
        res=0;
        dfs(root);
        return res;
    }
     public int dfs(TreeNode root){
   
        if(root==null) return 0;
         
        int leftRes=dfs(root.left);
        int rightRes=dfs(root.right);
         
        int leftCount=0,rightCount=0;
         // counting rules:
        if(root.left!=null && root.val==root.left.val){
   
             leftCount=leftRes+1;
        } 
        if(root.right!=null && root.val==root.right.val) {
   
             rightCount=rightRes+1;
        }
        // get the result for main function
        res=Math.max(res,leftCount+rightCount);
        return Math.max(leftCount,rightCount);//return larger one to the upper level
    }
}

七。树的遍历
树的前序遍历,中序遍历,后序遍历:指的是,什么时候遍历/加入根节点?
前序遍历:,左,右
中序遍历:左,,右
后序遍历:左,右,

recursion写法比较好写
iteration写法需要掌握借用stack FILO特性的方法

preorder
144. Binary Tree Preorder Traversal

class Solution {
   
    List<Integer> res;
    public List<Integer> preorderTraversal(TreeNode root) {
   
        res=new ArrayList<Integer>();
        dfs(root);
        return res;
    }
    void dfs(TreeNode root){
   
        if(root==null) return;
        res.add(root.val);
        dfs(root.left);
        dfs(root.right);
    }
}
//pre order:5(3,1,4)(8,11)
//poll自己,offer右左
public List<Integer> preorderTraversal(TreeNode root) {
   
 List<Integer> res=new ArrayList<Integer> ();
     if(root==null) return res;

    Deque<TreeNode> stack=new ArrayDeque<>();
    //pre order:5(3,1,4)(8,11)
    //poll自己,offer右左
    stack.offerLast(root);
    while(!stack.isEmpty()){
   
      TreeNode cur=stack.pollLast();
      res.add(cur.key);
      if(cur.right!=null)
        stack.offerLast(cur.right);
      if(cur.left!=null)
        stack.offerLast(cur.left);
    }
	return res;
}

inorder
94. Binary Tree Inorder Traversal
一路offer向左
没有左了,就poll当前,走向右边

class Solution {
   
    
    public List<Integer> inorderTraversal(TreeNode root) {
   
        List<Integer> res=new ArrayList<Integer>();
        if(root==null) return res;
        
        Deque<TreeNode> stack=new ArrayDeque<TreeNode>();
        
         while (root!= null || !stack.isEmpty()) {
   
            if(root!=null){
    //while(root!=null){...} ...
                stack.offerFirst(root);
                root=root.left;
            } else {
   
	            root=stack.pollFirst();
	            res.add(root.val);
	            root=root.right;   
            }   
         }
       return res;
    }
}
class Solution {
   
    List<Integer> res;
    public List<Integer> inorderTraversal(TreeNode root) {
   
        res=new ArrayList<Integer>();
        dfs(root);
        return res;
    }
    void dfs(TreeNode root){
   
        if(root==null) return;
        
        dfs(root.left);
        res.add(root.val);
        dfs(root.right);
    }
}

postorder
145. Binary Tree Postorder Traversal

class Solution {
   
    List<Integer> res;
    public List<Integer> postorderTraversal(TreeNode root) {
   
        res=new ArrayList<Integer>();
        dfs(root);
        return res;
    }
    void dfs(TreeNode root){
   
        if(root==null) return;
        
        dfs(root.left);
        dfs(root.right);
        res.add(root.val);
    }
}

将preorder的方法倒成poll自己,offer左右,然后整体reverse一下。

class Solution 
  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值