数据结构篇为本人考研时所写笔记,包括知识点和编程思想两大板块,笔记内容依据王道数据结构考研书所写,但比书本上知识更加生动形象,愿本篇章能对您有所帮助
五、树与二叉树
(一)树基本概念
树的定义是递归的,故数是一种递归的数据结构,同时也作为一种逻辑结构,同时也是一种分层结构
树的表示方法:
① 树形表示法
② 嵌套集合表示法
③ 凹入表表示法
④ 广义表表示法
- 基本术语:
① 祖先(与子孙对应):从根到结点的唯一路径上的任意结点
② 双亲(与孩子对应):路径上最接近该结点的连线的上层结点
③ 兄弟(sibling):有相同双亲的结点
④ 结点的出度:结点拥有的非空子树数目
⑤ 结点的入度:指向结点的分支数目
⑥ 结点的度:树中一个结点的孩子个数,即该结点的出度
⑦ 树的度:树中结点的最大度数
⑧ 分支结点(非终端结点、非叶子节点):度大于0的结点
⑨ 叶子节点(终端节点):度为0的结点
⑩ 内部结点:根结点之外的分支结点
⑪ 堂兄弟:在同一层的结点
⑫ 结点的层次:从树根开始定义,树根为第1层依次往下
⑬ 结点的深度:从根节点开始自顶向下逐层累加(注意题目中的深度是从0还是1开始计算的,默认从1开始)
⑭ 结点的高度:从叶节点开始自底向上逐层累加(同上,默认最底下的高度为1)
⑮ 树的高度/深度是树中结点的最大层数
⑯ 有序树:各子树从左到右是有次序的,不能互换,反之无序树
⑰ 结点间路径:这两个结点之间所经过的结点序列构成,树中分支是有向的(从双亲指向孩子),故树中路径是从上往下的
⑱ 路径长度:路径上所经过的边的个数
⑲ 树的路径长度:树根到每个结点的路径长的总和
(二)二叉树
二叉树≠度为2的树≠度为2的有序树
若二叉树用二叉链表做存储结构,则在N个结点的二叉树链表中只有N-1个非空指针域(因为N个节点的二叉树,若用二叉链表表示 则每个节点都有两个链域也就是2N个,然后除了根节点外每个节点都能但只能被指一次,所以有N-1个链域不为空,因而有N+1个链域为空)
不允许两个结点值相等
五种特殊二叉树(二叉树不是树的特殊形式,这是两种不同的数据结构):
① 满二叉树:树中每层都含最多的结点
(1) 高度为h,则结点数为2(h-1)
(2) 若按程序编号(自上而下,自左而右),根节点为1号,对于编号为i的结点,双亲为:向下取整(i/2);左孩子:2i;右孩子:2i+1
② 完全二叉树(若同满二叉树编号):
(1) 若i<=向下取整(n/2)则结点i为分支节点
(2) 若n为奇数,则每个分支结点都有左孩子和右孩子(除去根节点可以对半分)
若n为偶数,编号最大的分支结点(编号为n/2)只有左孩子
③ 二叉排序树(BST):
(1) 左结点值<根值<右结点值
(2) 插入新结点不会引起树的分裂组合
(3) 插入顺序不同,得到的二叉排序树不同
(4) 查找效率取决于树的高度,最好(为平衡二叉树时,与折半查找相同)为O(log_2n),最坏为O(n)
(5) 插入、建立的算法平均时间是O(nlogn),但其执行时间是堆排序的2-3倍
(6) 当各关键字相等时,最佳二叉排序树应是高度最小的二叉排序树,构造方法:
(1 首先对各关键字按值从小到大排序
(2 然后仿照折半查找的判定树构造法构造二叉排序树
(7) 与二分查找的比较:
(1 维护表的有序性时,二叉排序树只需修改指针即可,时间复杂度为O(log_2n),而二分查找因是有序顺序表故为O(n)
(2 当有序表是静态查找表时,宜用顺序表作为存储结构,采用二分查找
(3 当有序表是动态查找表时,宜用二叉排序树
(8) 对于非空二叉排序树T1删除结点v形成的T2又插入结点形成的T3而言:
(1 若v是T1的叶结点,则T1与T3相同
(2 若v不是T1的叶结点,则T1与T3不同
④ 平衡二叉树(AVL):
(1) 左子树深度-右子数深度(此高度差又称平衡因子(BF))只能是1/0/-1(平衡因子的含义和计算方法)
(2) 含有n个结点的平衡二叉树的最大深度为O(log_2n)(也为平均查找长度)
(3) 最后插入的结点不一定为叶子结点,因为可能会导致平衡调整
(4) 对于非空二叉平衡树T1删除结点v形成的T2又插入结点形成的T3而言:
(1 若v是T1的叶结点,则T1与T3可能不相同
(5) 插入操作(写完题记得检查是否符合左<根<右):
(1 每次调整的对象都是最小不平衡子树
(2 只有左孩子才能右上旋(L)(把A(最小不平衡对象)的左子树根节点代替它,并把其右子树连接为自己的左子树),只有右孩子才能左上旋(R)(把A(最小不平衡对象)的右子树根节点代替它,并把其左子树连接为自己的右子树)
(3 插入在左子树的左孩子进行LL
插入在右子树的右孩子进行RR
插入在左子树的右孩子进行LR
插入在右子树的左孩子进行RL
⑤ 线索二叉树(TBT)(加上线索的二叉树,为一种存储结构,是一种物理结构):
(1) 优点:加快了查找结点的前驱和后继的速度
(2) 线索化的实质是遍历一次二叉树,因为前驱和后继信息只有在遍历时才能得到
(3) 至少包含五个域:数据域(data)、左指针域(lchild)、右指针域(rchild)、左标志域(ltag,为1时指向结点的后继)、右标志域(rtag,为1时指向结点的前驱)
(4) 线索链表:以这种结点结构构成的二叉链表
线索:指向结点前驱和后继的指针
完全二叉树和满二叉树采用顺序存储比较合适
二叉树存储结构:
① 顺序存储结构(从数组下标为1开始存储):
(1) 为了让数组下标能反映结点之间关系,只能用0来代替添加一些并不存在的空结点
(2) 最坏情况下,高度为h且只有h个结点的单支树需要占据近2^h-1个存储单元
② 链式存储结构:至少包含三个域:数据域(data)、左指针域(lchild)、右指针域(rchild)
若题目中结点数值很大一般采用特殊值法
二叉树的遍历:
① 先序(序指的是根节点何时被访问)(NLR),中序(LNR),后序(LRN)-填空可能考英文名称
② 平均查找长度(ASL)和查找失败平均长度(ASL)(即计算树中所有空结点(层数算为空结点的双亲的层数)的查找长度)-除不清可写分数,也要用大O表示法表示
③ 时间复杂度都是O(n);
递归遍历中,递归工作栈的栈深恰好为树的深度,最坏情况下,二叉树为n个结点且深度为n的单支树,此时空间复杂度为O(n)(精确值应为树的深度+1,因为叶子的空域也递归了一次)
(三)树、森林、并查集
- 树的顺序存储结构和二叉树的顺序存储结构的区别:
① 树:数组下标代表结点的编号,下标中所存的内容指示了结点之间的关系
② 二叉树:数组下标既代表了结点的编号,又指示了二叉树中各结点之间的关系,当然二叉树属于树所以也可用树的存储结构
- 树的存储结构:
① 数组表示法:
② 双亲链表表示法:采用一组连续的空间来存储每个结点,同时在每个结点中增设一个伪指针,指示其双亲结点在数组中的位置,设域data和parent
③ 孩子链表表示法:将每个结点的孩子结点都用单链表链接起来形成一个线性结构,设域data和firstchild
④ 孩子兄弟链表表示法(又称二叉树表示法):每个结点包括三部分:节点值、指向结点第一个孩子结点的指针、指向结点下一个兄弟结点的指针,设域leftchild、data、rightchild
(1) 优点:可以方便的实现树转换为二叉树的操作
(2) 缺点:从当前结点查找其双亲结点比较麻烦
- 树转换为二叉树(完成后无右子树)的规则(二叉树转树则反操作):
① 每个结点左指针指向它的第一个孩子
② 右指针指向它在树中相邻的右兄弟
③ 二叉树形态唯一
④ 根结点的右子树一定为空
- 林转换为二叉树(二叉树转树则反操作):写题时要分清是树还是森林转二叉树
① 先将森林中每棵树转换为二叉树
② 将第二棵当作第一棵二叉树的右子树,以此类推
③ 二叉树形态唯一
- 树和森林的遍历(名字不要记混了):
① 树的先根遍历:先访问根结点,再依次按照二叉树的先序遍历遍历每棵树,其遍历序列与这棵树相应的二叉树的先序序列相同
② 树的后根遍历:先访问根结点的每棵子树,再访问根结点,其遍历序列与这棵树相应的二叉树的中序序列相同
③ 先序遍历森林:先序遍历第一棵树,以此类推
④ 中序遍历森林(部分教材称为后根遍历/中根遍历):中序遍历第一棵树,以此类推
- 并查集(合并查询的集合):
①
② 通常用树的双亲表示法作为并查集的存储结构
③ 变成树的过程即属于同一个集合的连成一棵树
④ 合并两个子集合:只需将其中一个子集合根结点的双亲结点指向另一个集合的根结点
⑤ 并查集的优化(路径压缩):把每个元素直接连接到所在集合(树)的根上
(四)哈夫曼树(最优二叉树)和哈夫曼编码
带权路径长度最小到二叉树称为最优二叉树/哈弗曼树
结点带权路径长度(WPL):经过的边数*该结点上的权值
树的带权路径长度:所有叶结点的带权路径长度之和
- 哈夫曼树的特点:
① 每个初始结点最终都成叶子结点,且权值越小的结点到根节点的路径长度越大
② 构造过程中工新建了n-1个结点,因此总结点数为2n-1
③ 哈夫曼中不存在度为1的结点
④ 符合②③的树称为严格的、正则的二叉树
- 哈夫曼编码:
① 固定长编码:对每个字符用相等的长度的二进制表示(即000,001,010...)
② 可变长度编码:允许对不同字符用不等长的二进制位表示
③ 前缀编码:没有一个编码是另一个编码的前缀
④ 编码构造树的过程中:
(1) 0,1到底表示左子树还是右子树没有明确规定,一般为左为0,右为1
(2) 左右孩子结点的顺序是任意的,故构造出的哈夫曼树并不唯一
(3) 所构造出的不同哈夫曼树WPL必定是相同且最优的
(五)树堆
1. 堆的概念:
① 堆就是用数组实现的二叉树,所以它没有使用父指针或者子指针
② 最大堆:父节点的值比每一个子节点的值都要大
③ 最小堆:父节点的值比每一个子节点的值都要小
④ 堆和二叉搜索树的主要差别:
(1) 节点的顺序。在二叉搜索树中,左子节点必须比父节点小,右子节点必须必比父节点大但是在堆中并非如此。在最大堆中两个子节点都必须比父节点小,而在最小堆中,它们都必须比父节点大
(2) 内存占用。普通树占用的内存空间比它们存储的数据要多。你必须为节点对象以及左/右子节点指针分配内存。堆仅仅使用一个数据来存储数组,且不使用指针
(3) 平衡。二叉搜索树必须是“平衡”的情况下,其大部分操作的复杂度才能达到O(log n)。你可以按任意顺序位置插入/删除数据,或者使用 AVL 树或者红黑树,但是在堆中实际上不需要整棵树都是有序的。我们只需要满足堆属性即可,所以在堆中平衡不是问题。因为堆中数据的组织方式可以保证O(log n) 的性能
(4) 搜索。在二叉树中搜索会很快,但是在堆中搜索会很慢。在堆中搜索不是第一优先级,因为使用堆的目的是将最大(或者最小)的节点放在最前面,从而快速的进行相关插入、删除操作。
⑤ insert() 插入一个新的元素,和通过 remove()移除最大或者最小值。两者的时间复杂度都是O(log n)
- 树堆的概念:
① 树堆是一种随机的二叉搜索树(满足二叉搜索树的全部性质),但是和二叉搜索树有相同的性质
② 树堆的结构不由元素的添加顺序决定,而是随机的
③ 添加新节点时,给这个节点附加优先级(priority)。这种优先结构与元素大小关系和输入顺序无关,值通过随机数确定。
- 树堆的条件:
① 二叉搜索树的条件:对所有节点而言,左侧子树的节点元素值小于根节点元素值,右侧子树的节点元素值大于根节点
② 堆的条件:所有节点的优先级都大于等于自身子节点的优先级
树堆(也称随机二叉搜索树)的思想:对每一个关键字加上一个随机生成的优先级(例如快排∶快排最坏情况下是O(n^2),但如果在快排之前打乱下可使快排的思想接近0 ( nlog2_n)),当二叉搜索树是有序的情况下进行构造会使二叉搜索树退化成一个链,但如果给一个随机优先级(权值)以树堆的方式进行插入则会是一颗随机插入的二叉搜索树,在之前若插入一个结点假设数高为h则插入最坏情况下=插入+旋转=20 (h),但以树堆形式随机插入的话=0 (h)+O(log2_h),故以树堆形式的构造会有效降低在有序情况下进行插入时的时间复杂度
如果插入顺序随机,则一棵BST的深度是接近O(log_2n)(若真的非常随机可能就是一颗平衡二叉树了)
树堆的构造:
树堆的旋转操作和平衡二叉树相似也是为了保护小根堆/大根堆性质,如以下右旋保护小根堆性质:
https://blog.csdn.net/sdzbyzh/article/details/121446109