树的定义

1、非空树有且只有一个根节点

2、子树互不相交

 

树的节点:包含一个数据元素和若干指向其它子树的分支

节点的度:节点拥有的子树数

叶节点:度为0的节点

树的度:树内节点的度的最大值

 

孩子:节点的子树的根,该节点称为孩子的双亲

节点的祖先:从根节点到该节点所经分支上的所有节点

 

堂兄弟:双亲在同一层的节点

树的深度:树中节点的最大深度

树的存储结构

双亲表示法:每个节点中,设置一个指示器指示双亲在链表中的位置

 

 

二叉树

1、每个节点最多有两颗子树

2、左子树右子树是有顺序的,不能颠倒的

 

二叉树的五种形态:

1、空二叉树

2、只有一个根节点

3、根节点只有左子树

4、根节点只有右子树

5、根节点既有左子树又有右子树

 

特殊树:

1、斜树:所有节点都只有左子树叫做左斜树,都只有右子树叫做右斜树

 

2、满二叉树:所有分支节点都存在左子树和右子树,并且所有叶子都在同一层

 

3、完全二叉树:对于一颗有N个节点的二叉树按层序编号,如果编号为 i (1<= i <= N)的节点与同样深度的满二叉树中编号为 i 的节点在二叉树中的位置完全相同,这种二叉树叫完全二叉树

 

完全二叉树不一定是满二叉树,满二叉树一定是完全二叉树

 

完全二叉树特点:

(1)叶子节点只能出现在最下面两层

(2)最下层的叶子一定集中在左部连续位置

(3)倒数第二层,若有叶子节点,一定集中在右部连续位置

(4)同样节点数的二叉树,完全二叉树深度最小

 

判断是否是完全二叉树的方法:给每个节点按照满二叉树的结构按层编号,如果编号出现空档,就说明不是完全二叉树

 

二叉树的性质:

(1)在二叉树的i层上至多有2^(i - 1)个节点

(2)深度为k的二叉树至多有2^k - 1个节点(1+2+4.。。)

(3)对任何一颗二叉树T,如果其终端节点的数为n0,度为2的节点数为n1,则n0 = n1 + 1;

(4)具有N个节点的完全二叉树的深度为⌊log2N⌋ + 1;(满二叉树  N = 2^K - 1, 完全二叉树  2^(K - 1) - 1 <  N <= 2^K - 1,   2^(K - 1) <= N <2^K)

 

二叉树的存储结构

顺序存储结构:按层序编号,完全二叉树的优势就体现;一般的二叉树,把不存在的节点位置为'^'

缺点:空间浪费,比如深度为K的右斜树,所以顺序存储结构一般用于完全二叉树

二叉树遍历 

前序遍历:先访问根节点,再遍历左子树,最后遍历右子树

中序遍历:先遍历左子树,再访问根节点,最后遍历右子树
应用:对于二叉搜索树,我们可以通过中序遍历得到一个递增的有序序列。

后序遍历:先遍历左子树,再遍历右子树,最后访问根节点
应用:
1、删除节点的时候,按照后序遍历的方式进行,先删除左节点,再删除右节点,最后删除节点本身
2、数学表达式中用后序遍历来解析后缀表达式;中序遍历可以将后缀转中缀

写法:递归、迭代、莫里斯遍历

线索二叉树

原理

1、二叉树许多空指针的存在(n个节点,2n个指针域,(n-1)条分支,所以有n+1个空指针)

2、二叉树可以知道子节点的地址,但是只有经过遍历之后才知道前驱和后继

3、所以可以利用空指针指向某种遍历序列下的前驱和后继节点

概念

线索:指向前驱和后继的指针

线索链表:加上线索的二叉链表

线索二叉树:线索链表相应的二叉树

线索化:对二叉树以某种次序遍历使其变成线索二叉树的过程

 

线索化的实质是将二叉树的空指针改为指向前驱或后继的线索

 

 

leetcode总结 

树的遍历

(1)题目

Binary Tree Inorder Traversal

Binary Tree Preorder Traversal

Binary Tree Postorder Traversal

Binary Tree Level Order Traversal

Binary Tree Level Order Traversal II

Binary Tree Zigzag Level Order Traversal

(2)分类

  • 深度优先遍历
  • 广度优先遍历

树的深度优先遍历

中序, 先序和后序三种方式, 不过结点遍历的方式是相同的, 只是访问的时间点不同而已, 对应于Binary Tree Inorder Traversal, Binary Tree Preorder Traversal和Binary Tree Postorder Traversal这三道题目。

 

(1)实现方式

  • 递归

递归左右结点, 直到结点为空作为结束条件就可以, 哪种序就取决于你访问结点的时间。 

  • 迭代

一个栈手动模拟递归的过程

前序和中序遍历就是不断往左走的过程节点入栈,走到底之后节点出栈,走右边;

后序遍历往左走到底之后不能根节点直接出栈,因为遍历完右子树,还要将根节点添加到vector中

要判断是从左子树回到的根节点还是从右子树回到的根节点(可以用哈希表)

如果是从左子树回到根节点,此时应转到右子树;

如果从右子树回到的根节点,应该将当前节点弹出加入到vector中

 

另一种后序遍历迭代的思路:

类似前序(根-右-左)+反转

(2)时间和空间复杂度

  • 递归/迭代:O(N)/O(logN)
  • 常量空间内解决树的遍历问题呢?

解决方法:Morris遍历方法用了线索二叉树,这个方法不需要为每个节点额外分配指针指向其前驱和后继结点,而是利用叶子节点中的右空指针指向中序遍历下的后继节点就可以了。这样就节省了需要用栈来记录前驱或者后继结点的额外空间, 所以可以达到O(1)的空间复杂度。

缺点:这种方法有一个问题就是会暂时性的改动树的结构

树的广度优先搜索

层序遍历,LeetCode中有三种自顶向下层序, 自底向上层序和锯齿层序遍历, 对应于Binary Tree Level Order Traversal, Binary Tree Level Order Traversal II和Binary Tree Zigzag Level Order Traversal。 

 

其实层序遍历也可以用DFS,只要加上level标记

 

自顶向下层序:思路就是维护一个队列存储上一层的结点, 逐层访问。(可以加上level标记)

自底向上层序:思路之一就是把自顶向下层序遍历得到的层放入数据结构然后reverse过来

//思路1:队列(自顶向下的层序遍历)+反转
//思路2:深度搜索(标记对应层数)+反转
//思路3:队列(自顶向下层序遍历)(但是将结果insert到res.begin())

锯齿形层次遍历:实现逆序(最晚入列最先访问),用双栈(一个栈出栈上一层节点,另一个栈入栈保存该层节点),添加的时候对层数进行奇偶数判断,奇数时,先入右节点,再入左节点;偶数树,先入左节点,再入右节点

树的构造

(1)题目

Convert Sorted Array to Binary Search Tree

Convert Sorted List to Binary Search Tree

Construct Binary Tree from Preorder and Inorder Traversal

Construct Binary Tree from Inorder and Postorder Traversal
 

Convert Sorted Array to Binary Search Tree
 

思路1:分治法
取中间节点为根节点,分左右子树
终止条件:长度为1,则终止

时间O(N):所有节点都遍历一遍
空间O(logN):递归树的高度为logN

Convert Sorted List to Binary Search Tree
 

    //思路1:快慢指针找到链表的中点
    //题目理解:得到的是一个有序链表而不是数组,我们不能直接使用下标来访问元素。我们需要知道链表中的中间元素。

    //思路2:中序遍历
    //根据二叉搜索树性质,二叉搜索树中序遍历得到的是有序数组
    //可以进行中序遍历,在打印节点的地方创建节点
    //时间O(N),链表所有节点遍历一遍
    //空间O(logN),递归树高度为O(logN)

    //步骤:
    //1、统计链表的长度
    //2、找到中点mid,递归左链表,建立当前节点(同时获取链表的下一节点,就像打印节点的时候顺序打印,创建的时候也是顺序创建),递归右链表
    //3、返回根节点

Construct Binary Tree from Inorder and Postorder Traversal

主要问题是如何将节点劈成左右两部分进行递归

    //思路1:分治(递归)
    //两个特性:
    //1、在后序遍历序列中,最后一个元素为树的根节点
    //2、在中序遍历序列中,根节点的左边为左子树,根节点的右边为右子树
    //不断将数组划分为左子树、根、右子树,大小为1的子树即为叶节点
    //中序数组:根节点ri, 左子树数组in_lo~ri-1, 右子树数组,ri+1~in_hi
    //后序数组:根节点post_hi,左子树数组post_lo~post_lo-in_lo+ri-1,右子树数组post_lo-in_lo+ri~post_hi-1

    //Trick1:用哈希表来保存中序遍历序列中,元素和索引的位置关系.因为从后序序列中拿到根节点后,要在中序序列中查找对应的位置,从而将数组分为左子树和右子树

Construct Binary Tree from Preorder and Inorder Traversal

    //思路1:分治(递归)
    //两个特性:
    //1、在前序遍历序列中,第一个元素为树的根节点
    //2、在中序遍历序列中,根节点的左边为左子树,根节点的右边为右子树
    //不断将数组划分为左子树、根、右子树,大小为1的子树即为叶节点
    //中序数组:根节点ri, 左子树数组in_lo~ri-1, 右子树数组,ri+1~in_hi(根据中序数组确定左右子树长度)
    //前序数组:根节点prev_lo,左子树数组prev_lo+1~prev_lo-in_lo+ri,右子树数组prev_lo-in_lo+ri+1~prev_hi

总结:

统一的思路是找到根节点,然后递归左右子树

 

树的性质

LeetCode中关于树的性质有以下题目:

Maximum Depth of Binary Tree

Minimum Depth of Binary Tree

Balanced Binary Tree

Same Tree

Symmetric Tree

Maximum Depth of Binary Tree

    //思路1:自顶向下的DFS

    //类似前序遍历,max(depth, answer)

    //O(N)/O(logN)

    //思路2:自底向上的DFS

    //类似后序遍历,max(left_height, right_heigh) + 1

    //O(N)/O(logN)

    //思路3:BFS(队列),遍历完了后,遍历层数level即最大深度

Minimum Depth of Binary Tree:

 

    //思路1:递归
    //父节点的深度=min(左子树最小深度,左子树最小深度)
    //区别于求树的最大深度,要对左右叶子是否为空进行判断;若为空,则非空一边递归的结果,而不是取min
    //O(N):所有节点遍历一遍 
    //O(logN):递归树的高度

    //思路2:迭代(BFS)
    //类似树的层次遍历,当第一次遍历到左右子树都为空的时候,得出最短路径

Balanced Binary Tree:

    //思路1:递归

    //思路与求树的最大深度类似,但是需要维护节点深度,同时还要判断是否是平衡树,所以用利用深度大于0的性质,用深度-1表示非平衡子树

    //O(N)/O(logN)

Same Tree:

    //思路1:递归
    //相同方式遍历两个树,节点值都相同返true,否则返false

Symmetric Tree

    //思路1:递归
    如果一个树的左子树与右子树镜像对称,那么这个树是对称的。
    两个树在什么情况下互为镜像?
    如果同时满足下面的条件,两个树互为镜像:
        (1)它们的两个根结点具有相同的值。
        (2)每个树的右子树都与另一个树的左子树镜像对称。

    另一层理解:对左右子树做相反顺序的遍历:
    A节点的左子树进行前序遍历【DLR,即当前结点, 左孩子, 右孩子】
    同时对A节点的右子树进行【DRL,即当前结点, 右孩子, 左孩子】遍历。

    //思路2:迭代
    类似BFS思想(用队列,队列中连续的节点应该是相同的,最初队列中包含的是 root和root)
    每次提取两个结点并比较它们的值。然后,将两个结点的左右子结点按相反的顺序插入队列中。
    结束条件:当队列为空时,或者我们检测到树不对称(即从队列中取出两个不相等的连续结点)

 

树的求和

(1)题目:

Path Sum
Path Sum II
Sum Root to Leaf Numbers
Binary Tree Maximum Path Sum

 

Path Sum

    //思路1:递归(自顶向下)
    //递归:sum -= node->val
    //如果当前节点是叶子,检查 sum 值是否为 0,也就是是否找到了给定的目标和
    //如果不是叶子,则对它的所有孩子节点,递归调用
    //O(N)/O(logN)

 

    //思路2:迭代
    //将当前节点的val更改为剩余和
    //如果是叶节点,判断剩余和是否为0
    //如果不是叶节点,如左节点存在,更新左节点的剩余和,入栈。。。

Path Sum II

给定一个二叉树和一个目标和,找到所有从根节点到叶子节点路径总和等于给定目标和的路径。

 

    //思路1:递归
    //需要数据结构来维护中间路径结果以及保存满足条件的所有路径。
    //在递归的过程递减sum:sum-=root->val
    //终止条件:
    //(1)root == NULL,return NULL;
    //(2)抵达叶子节点,if(sum == 0) res.push_back(item)


    //Trick1:因为递归的时候维护的是同一个vector<int> item,使用的是引用类型,所以回溯的时候item.pop_back()

Sum Root to Leaf Numbers

求根到叶子节点数字之和
 

    //思路1:递归
    //递归的时候num = num*10 + root->val
    //当root是叶子节点时,res += num

 

Binary Tree Maximum Path Sum

二叉树中的最大路径和

    //思路1:自底向上递归
    //树看作一个无向图,找其中最大的路径
    //经过根节点最大路径,就是root->val + 左子树最长路径 + 右子树最长路径
    //考虑左右子树最长路径可能小于0,所以经过一个节点的最长路径=root-> + max(0, 左子树最长路径) + max(0, 右子树最长路径)
    //求树的最长路径,就是自己的值 + max(0, 左子树最长路径, 右子树最长路径)

    //递归函数helper,以当前节点为根节点的最长路径,同时计算经过当前节点的最长路径cur,更新经过节点的最长路径res 

    //O(N)/O(logN)

总结:

总体来说,求和路径有以下三种:(1)根到叶子结点的路径;(2)父结点沿着子结点往下的路径;(3)任意结点到任意结点(也就是看成无向图)

参考:LeetCode总结 -- 树的遍历篇 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值