一。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,2,6,7
- 插入11时发现长度超过4,=5
- 1,2,6,7,11中提取index在中间的数6作为父结点,12在左分支711在右分支
- 插入4,8,13
- 插入10时分支满5个,将index在中间的数10提取到上层,和6同一层,78为同一个分支,11,13为同一个分支
- 插入5,17 ,9 ,16
- 插入20时11131617分支满5个,将index在中间的数16提取到上层,和10同一层,11,13为同一个分支,17,20为同一个分支
- 插入3时12245分支满5个,将index在中间的数3提取到上层,1,2为同一个分支,4,5为同一个分支
- ,12 ,14,18,19
- 插入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