目录

​1.二分/折半查找​

​2.对称矩阵下标​

​3.根据前序确定可能中序​

​4.已知前序&后序​

​5.折半查找&二叉排序树​

​6.hash哈希​

​附:散列表的查找效率​

​处理冲突方法:​

​7.××与初始序列无关​

​8.顺序表插入/删除平均移动个数​

​9.中缀转后缀表达式​

​变种题:中转后缀的堆栈题​

​10.后缀求表达式值​

​11.满k叉树性质​

​12.WPL带权路径长度​

​13.AVL旋转​

​典例1​

​14.AVL树查找​

​15.双端队列​

​16.Prim​

​17.二维数组求地址题​

​18.AOE网​

​19.森林转成二叉树​

​20.树的存储​

​21.树性质​

​22.DFS和BFS生成树​

​23.快速排序​

​24.图&树的概念​

​25.Dijkstra​

​26.DFS和BFS(邻接表)​

​27.线索二叉树​

​28.拓扑&邻阵​

​29.B-树&B+树​

​30.B树的add​

​31.B树的del​

​32.del后add型题​

​(1)二叉排序树​

​(2)平衡二叉树​

​(3)折半查找树​

​33.二路归并算法​

​34.栈​

​35.递归求WPL​

​二叉树(二叉链表存储)​

​36.KMP算法​

​一.求next数组​

​二.匹配过程考察​

​37.哈夫曼树​

​38.归并的比较次数​

​39.表达式的min顶点数​

​39.堆栈​

​40.森林&树&BT遍历​

​41.​


1.二分/折半查找

二分基本的查询下标的方法为(首下标+尾下标)/2,如果该下标非查询值,要剔除后在左边或右边继续循环。
记住:把已经查询的值剔除,剔除,剔除
例:若有18个元素的有序表存放在一维数组A[19]中,第一个元素放A[1]中,现进行二分查找,则查找A[3]的比较序列的下标依次为______(中国科学院大学2012)            D
A.   1,2,3   B.   9,5,2,3  C.   9,5,3   D.   9,4,2,3
第一次查找,队首为下标1,队尾下标18,所以是(1+18)/2=9
第二次查找,队首为1,队尾为9-1=8,所以是(1+8)/2=4
第三次,队首1,队尾4-1=3,(1+3)/2=2
第四次,队首2+1=3,队尾3,(3+3)/2=3

数据结构学习笔记_1024程序员节

【解析】(A)中荧光处(4和5点):对(4、5、空)折半,即取靠结点(5)作为【中心结点】;(8和7点):对(7、8、空)折半,即取靠结点(5)作为【中心结点】,可预判他们的取整方式相同;
而对(B)中(4、5、空)折半,分界线在4和5中间,即取靠结点(5)作为【中心结点】;(空、7、8)折半,分界线在7和8中间,即取靠结点(7)作为【中心结点】,矛盾故错。
注意(D)中子树都是取靠右的结点作为【中心结点】,但是对于根结点的左右子树,对左子树有4个结点,右子树有5个结点——即根结点在顺数第5个,可知是取靠左的结点作为【中心结点】,矛盾故错。

数据结构学习笔记_二叉树_02

【复习折半查找树】平衡树;折半查找要求线性表具有【随机存取】,仅适合【顺序存储结构】,元素要按关键字有序排列。

数据结构学习笔记_1024程序员节_03

查找成功(圆形结点)的ASL=(1×1+2×2+3×4+4×4)/11=3
----------查找成功时进行的关键字比较次数最多是log2 n(取下限)  +1即判定树的高度;

查找不成功(方形结点)的ASL=(3×4+4×8)/12=11/3
-----------查找不成功时进行的关键字比较次数最多是log2 n(取下限)  +1即判定树的高度。

数据结构学习笔记_二叉树_04

2.对称矩阵下标

数据结构学习笔记_1024程序员节_05

数据结构学习笔记_二叉树_06

【解析】对角矩阵的存储可以存储下三角也可存储上三角,如果是下三角则是(1+7)*7/2+5=33,如果是存储其上三角则a8,5是对应于a5,8,地址为(10+7)*4/2+4=38,而答案无此选项,故选B。

3.根据前序确定可能中序

一棵二叉树的前序遍历序列为ABCDEFG,它的中序遍历序列可能是         (中国科学院大学 2015年)
A. CABDEFG  B. ABCDEFG     C. DACEFBG     D. ADCFEGB           答案:B D
法一:求中序序列,就看能不能和先序构成一棵二叉树。B和D与题目给出的先序能够构成一棵二叉树。
法二:转化成入栈出栈问题。
1.一棵二叉树的前序遍历结果,就是前序遍历时候元素入栈顺序。 
2.一颗二叉树的中序、后序遍历的结果,就是中序遍历、后序遍历遍历时候元素出栈顺序
所以这个问题就变成了,如果给定一个栈,入栈顺序是ABCDEFG,那么哪种出栈顺序是不可能的。

【已知前序&中序

先序为。。a。。b。。;中序为。。b。。a。。则(结点b在a的左子树)

【解析】结合先序的情况(下图一)和中序的情况(下图二),取2种情况的交集即b在a的左子树。

数据结构学习笔记_二叉树_07

4.已知前序&后序

【408真题】一棵二叉树的前序遍历:aebdc,后序遍历:bcdea,则根结点的孩子结点( )
(a)只有e  (b)有e,b  (c)有e,c  (d)无法确定
法一:用性质】性质:当2个结点的前序为XY,后序为YX时,则X为Y的祖先。
前序:a(ebdc),后序:(bcde)a,所以a为bcde祖先(a为根结点)。
前序:e(bdc),后序:(bcd)e,所以e为bcd祖先(a的孩子结点只有e)

法二:排除法】可确定a为根结点,e为a的孩子结点,排除D。
若b也是a的孩子结点,则abe的前序应为aeb,后序为eba,而后序实际上为bea,故只有e。

5.折半查找&二叉排序树

(A)折半查找的判定树一定平衡树,折半查找要求线性表具有【随机存取】,仅适合【顺序存储结构】,元素要按关键字有序排列。
但是二叉排序树不一定是平衡树,二叉排序树的查找性能与数据的输入顺序有关。
(B)二叉排序树左子树上的所有的值均小于根结点的值,右子树上所有的值均大于根结点的值--》中序序列得到递增序列。
【坑题错项说法】:每个结点的值都比它左孩子结点的值大、右...的二叉树就是二叉排序树(错在不只是右孩子和左孩子,是“子树”)。
【插入结点】新插入的关键字总是作为叶结点来插入,但叶结点不一定总是处于最底层。
【删除结点】(要分2种情况)
-----------------【删除非叶结点】重新得到的二叉排序树和原来的不同。
-----------------【删除叶子结点】和原来相同。(附三种BST删除例子如下图)

数据结构学习笔记_子树_08

数据结构学习笔记_结点_09

【例题】95,22,91,24,94,71不可能构成二叉排序树的一条查找路径。
【方法】画图!画图!画图(在91为根的左子树出现了比91大一丢丢的结点94)

6.hash哈希

1. 已知一个线性序列{38,25,74,63,52,48},假定用散列函数Hash(key)=key%7计算散列地址,散列存储在表A[10]中。如果采用线性探测法解决冲突,且各元素的查找概率相等,则在该散列表上查找不成功的平均查找长度为        (中国科学院大学 2016)     A. 2.60      B. 3.14       C. 3.71        D. 4.33         答案: B

解析:由于H(key)=0~6,查找失败时可能对应的地址有7个,查找失败的平均查找次数:(2+1+1+6+5+4+3)/7。
查找失败的注意点:(1)比较到空结点才算失败,所以比较次数=冲突次数+1;(2)只有与关键字的比较才算比较次数。
注意,散列函数不可能计算出地址7。(注意1和2的空格也要算,7后的不算
若算查找成功的ASL=(1+1+1+2+4+2)/6=11/6.

(1)在查找失败情况下,all散列地址到它后面的第一个key为空的散列地址的距离之和【除】P;因此既不是根据表中元素个数,也不是根据表长来计算ASL的,上面的分母7是除余%符号后的“7”(即只算0~6)决定的;并且注意要算空格。
(2)如果是计算查找成功的ASL则总比较次数除元素个数。(因为初始只可能在0~6)
(3)设计哈希函数即确定P,若用线性探测再散列解决冲突,设计哈希函数:H(key)=key%P,P取不大于散列表长但最接近10的质数7(若装填因子=0.8,表中记录数有8个则散列表的表长为10)。

0

1

2

3

4

5

6

7

8

9

63



38

25 

74

52

48



1

1

1

2

4

2

数据结构学习笔记_结点_10

【用链地址的冲突处理方法构造散列表】
黄色结点区域内不算进探测查找次数里面,即ASL(成功)=(1×4+2×3+3)/8=13/8;ALS(失败)=(3+4+2+1+3+1×5)=18/11

附:散列表的查找效率

概念:【同义词】发生碰撞的不同关键字。
hash查找效率取决于【散列函数】【处理冲突的方法】【装填因子】(表中记录数/散列表长度)
(1)冲突的产生概率和装填因子的大小成正比(装填的记录越满越容易冲突)。
(2)采用合适的冲突处理方法可避免聚集现象&提高查找效率,如【用拉链法处理冲突时不存在聚集现象(用拉链法时存储效率——找到空闲单元并放入的过程不受影响)】,【再散列法(双散)用2个散列函数或者平方探测法,不易产生堆积(聚集)现象。】而【用线性探测法(冲突时找下一个空位)处理冲突时易引起聚集现象】。

处理冲突方法:

(1)开放定址法:线性/平方探测法、再(双)散列法。(2)拉链法。
其中开放定址法不能随便删除物理表中元素,若要删除要【逻辑删除】(即作删除标记)。

7.××与初始序列无关

(1)总排序趟数与初始状态无关的有:除了快速排序(递归深度)和优化的冒泡,其他都是
——快排考虑极端:每趟都是找出最左边的元素作位枢轴则需要n趟排序;而如果能够每次划分平均则为log2n次(注意一趟是指前后两个子表都找出枢轴,也可能是1个字表,见​​​23.快速排序​​​)
(2)元素的移动次数与初始序列无关的是:基数排序、归并排序
(3)元素的比较次数与初始序列无关是:基数排序、选择排序——比较n(n-1)/2次。
(4)算法的时间复杂度与初始序列无关的是:选择排序,堆排序,归并排序O(nlogn),基数排序
——即最好和最坏情况都一毛一样~“一堆乌龟选基友”。

【2020年408】对大部分元素已有序的数组进行排序时,直接插入排序比简单选择排序效率更高,其原因是:
I、直接插入排序过程中元素之间的比较次数更少; II、直接插入排序过程中所需要的辅助空间更少;
III、直接插入排序过程中元素的移动次数更少。
【解析】考虑极端的完全有序,直接插入排序的比较次数为n-1(第2、3...个元素分别每次和前一个比较一次),简单选择排序的比较次数始终为n(n-1)/2次;两种排序方法的辅助空间都是O(1);通常情况下,直接插入排序每趟插入都要依次向后挪位,而简单选择排序只需与找到的最小元素交换位置,后者的移动次数少很多。

8.顺序表插入/删除平均移动个数

在等概率情况下,顺序表中插入一个结点需要平均移动n/2个结点。删除一个结点需要平均移动(n-1)/2个结点。具体的移动次数取决于顺序表的长度n以及需插入或删除的位置i,i越近n,则所需移动的结点数越少。
注意:删除第一个结点虽然不改变整体“顺序”,但也要后续结点全部往前移1位,故时间复杂度为O(n)。
【双向链表】
用双向链表存储线性表,没提高查找速度(仍是顺序查找),但数据的插入和删除会更快。
【典例】若线性表非空,下列哪种链表可以在O(1)内在表尾插入一个新结点。
【解析】带表头结点的单循环链表,1个链表指针指向表头结点——在表头结点和第一个元素之间插入一个新的表头结点;由于是单循环链表,原来的表头就改为了链表尾结点(当做新插入的)。

9.中缀转后缀表达式

表达式(a-b-c)*d的后缀表达式为        。(厦门大学 2011年)A.ab-c-d*  答案:A
解析:此题为一道基础题, 对于此类问题有两种解决方式,一是用堆栈,另一种是用表达式二叉树。
法一:遇到数字则直接输出,遇到运算符或者左括号入栈,如果遇到右括号,则出栈到左括号为止,如果当前运算符比栈顶运算符的优先级高,则入栈,否则出栈,直到满足当前运算符的优先级比栈顶高为止,如果是括号内表达式依然如此。
法二:利用表达式二叉树,运算符为子树根,操作数为叶子节点。利用后序遍历就可得到后序表达式序列。
法三:加括号法(最简单)。举个栗子:a+b*c-(d+e)
第一步:按照运算符的优先级对所有的运算单位加括号
则式子变成拉:((a+(b*c))-(d+e))
第二步:转换前缀与后缀表达式
1. 前缀表达式:把运算符号移动到对应的括号前面 
          则变成拉:-( +(a *(bc)) +(de))
          把括号去掉:-+a*bc+de  前缀表达式出现 

2. 后缀表达式:把运算符号移动到对应的括号后面 
          则变成拉:((a(bc)* )+ (de)+ )- 
          把括号去掉:abc*+de+-  后缀表达式出现

变种题:中转后缀的堆栈题

(1)假设栈初值为空,将中缀表达式a/b+(c*d-e*f)/g转换为等价的后缀表达式的过程中,当扫描到f时,栈中的元素依次为B
(A)+(*-     (B)+(-* (C)/+(*-* (D)/+-*
【解析】后缀表达式:ab/cd*ef*-g/+;
后缀的符号顺序:/ * f * - / +          
中缀的符号顺序:/ +(* - * f ) /
中缀表达式中f后只有)和/ 两个符号,而在后缀中f后有一系列符号。(由后缀性质导致)
在处理中缀的过程中,栈就是用于存放“现在还没轮到,之后再输出”的符号。
以f为分界线看,中缀中在f前面而后缀中在f后的元素:*-(+,注意从中缀分析,将括号也考虑进去,
*-(+是从栈顶到栈底的情况,反向得到B。
(2)将中缀表达式转换为等价的后缀表达式中要用到堆栈保存运算符,对于中缀表达式A-(B+C/D)×E,当扫描读到操作数E时,堆栈中保存的运算符依次是( A)A -×   B -+  (和上题同理)

10.后缀求表达式值

实质:后缀转中缀表达式。
(1)顺序扫描表达式每一项;
——若该项是操作数,则压入栈中;
——若该项是操作符<op>,则连续从栈中退出两个操作数Y和X
(2)形成运算指令X<op>Y,并将结果重新压入栈中;
(3)当扫描完表达式后,存放在栈顶的即最后的运算结果

PS:后缀和前缀式都只有唯一一种运算次序,而中缀式则不一定,如a+b+c可以先算a+b也可以先算b+c。
​​​如何用栈求表达式的值?(超级详细)​​​(逆波兰表达式求值讲解)
选择题常考栈求表达式——设立运算数栈和运算符栈,看清栈有几个存储单元,左/右括号是放入运算符栈中。

11.满k叉树性质

一棵深度为H的满k叉树有如下性质:第H层上的结点都是叶子结点,其余各层上每个结点都有k棵非空子树。如果按层次顺序从1开始对全部结点编号,问:
(1) 各层的结点数目是多少?
(2) 编号为p的结点的父结点(若存在)的编号是多少?
(3) 编号为p的结点的第i个儿子结点(若存在)的编号是多少?
(4) 编号为p的结点有右兄弟的条件是什么?其右兄弟的编号是多少?

数据结构学习笔记_1024程序员节_11

(2)如果p是其双亲的最小的孩子(右孩子),则p减去根结点的一个结点,应是k的整数倍,该整数即为所在的组数,每一组为一棵满k叉树,正好应为双亲结点的编号。如果p是其双亲的最大的孩子(左孩子),则p+k-1为其最小的弟弟,再减去一个根结点,除以k,即为其双亲结点的编号。综合来说,对于p是左孩子的情况,i=(p+k-2)/k;对于p是右孩子的情况,i=(p-1)/k如果左孩子的编号为p,则其右孩子编号必为p+k-1,所以,其双亲结点的编号为

数据结构学习笔记_子树_12

向下取整,如1.5向下取整为1

(3)结点p的右孩子的编号为kp+1,左孩子的编号为kp+1-k+1=k(p-1)+2,第i个孩子的编号为k(p-1)+2+i-1=kp-k+i+1。

(4)当(p-1)%k != 0时,结点p有右兄弟,其右兄弟的编号为p+1。

12.WPL带权路径长度

概念:树中所有叶结点的带权路径长度之和;哈夫曼树可以构造最小带权路径长度。
PS:哈夫曼树——>前缀编码:在一个字符集中,任何一个字符的编码都【不是】另一个字符编码的前缀,如含有1100和110的编码方式就不是前缀编码。
下图举例:WPL=7*1+5*2+2*3+4*3=35。

数据结构学习笔记_二叉树_13

注意区别【树的路径长度】:从根结点到树中每个结点的路径长度之和。(最小路径长度的二叉树是完全二叉树)

数据结构学习笔记_二叉树_14

数据结构学习笔记_二叉树_15


PS:若改成最佳k-路归并,问总的读写外存次数——做法一样,求带权路径长度WPL;注意不要散装求和,根据分别累加路径为k的点权值(不重不漏)。

13.AVL旋转

【注意点】
(1)每次对“最小不平衡子树”调整,;平衡因子=左子树-右子树高度;
(2)问某种不平衡状态应用何种调整方式等价于问出现了那种不平衡状态。
(3)LL/RR/LR/RL的命名不是对调整过程的描述,而是对不平衡状态的描述(如LR调整即新插入结点落在最小不平衡子树根结点的左(L)孩子的右(R)子树上)
(4)LR和RL旋转时,新节点插入C的左子树 与 插入C的右子树【不影响】旋转过程(如下1,2图所示)。
(5)AVL树最后插入的结点可能会导致平衡调整,所以不一定是叶结点,而二叉排序树则一定是叶子结点(结合第五点笔记)。
【2019年408】删除非空AVL树的非叶v结点后再插入v结点后,既可能变为叶结点,也可能和原来一样是非叶结点(通过旋转后继续变成非叶结点)。

数据结构学习笔记_子树_16

数据结构学习笔记_结点_17

典例1

若将关键字1,2,3,4,5,6,7 依次插入到初始为空的平衡二叉树T 中,则T 中平衡因子为0 的分支结点的个数是         。(武汉大学 2014年)A.0B.1C.2D.3

数据结构学习笔记_二叉树_18

答案:D 解析:注意是平衡因子为0的“分支结点”的个数。
上图中左下角的这步最为关键,由于上面是RR,是2的右孩子(R)的右子树(R)上插入了新结点导致2的平衡因子从-1变成-2,根结点2的右孩子需要一次向左的旋转,将根结点的右孩子(此处为4)向左上旋代替2成为根结点,将2结点向左下旋成为新根的左孩子。——注:其他如LR则按英文对应 先左后右旋的常规操作。
PS:LL需要一次向右旋,RR需要一次向左选;LR先左后右旋;RL先右后左旋。

14.AVL树查找

Nh:深度为h的平衡树中含有的min结点数(即所有非叶结点的平衡因子均为1)。【Nh=N(h-1)+N(h-2)+1
n0=1,n1=1,n2=2,n3=1+2+1=4,n4=2+4+1=7,
n5=4+7+1=12,n6=7+12+1=20.

数据结构学习笔记_二叉树_19

【408真题】含有12个结点的平衡二叉树中查找某个结点的最多比较次数(即树的最大高度)为多少?
10、20、32、33中选。
法一:规律法】由N5=12知Hmax=5,即最多比较5次。
法二:画图法】先画出T1和T2,然后新建1个根结点,连接T2、T1构成T3;新建1个根结点,连接T3、T2构成T4;以此类推,直到画出T6——T6的结点数为20。
法三:排除法】A,高度为6,结点数为10的树怎么也无法达到平衡;
C,结点较多时考虑极端情况即,第6层只有最左叶子的完全二叉树刚好有32个结点,虽然满足平衡条件,但显然再删除部分结点不影响平衡,不是最少情况,同理D错。

15.双端队列

数据结构学习笔记_1024程序员节_20

数据结构学习笔记_子树_21

【真题】输出受限的双端队列中,若abcde入队,则不可能的出队序列( )
A.bacde B.dbace  C.dbcae  D.ecbad
【解析】(C)。对选项中的序列两个两个字母一起分析,模拟即可。
【另解】初始时队列为空,第一个元素a左入(或右入),而第2个元素无论左入还是右入都必与a相邻,并且由于输出受限即输出的ab是一起的,所以(C)的a与b不相邻所以错。

16.Prim

性质】设N个结点构成环,N-1条边权值相等,则从不同顶点开始Prim算法会得到N-1种不同的最小生成树。
【附】n个点,最小生成树不唯一,判断:
(1)边数一定大于n-1(正确——因为G的最小生成树的边数为n-1,若最小生成树不唯一,则G的边数一定大于n-1)
(2)权值最小的边一定有多条(错误——如三个点1、2、3互相连接,边权值分别为2、3、3)

Prim和Kruskal区别
Kruskal是每次在候选边中选择权值min的边并入树(该边不会使图构成回路);
Prim是每次从现树相连接的边中选取一条权值min的边。
Prim算法时间复杂度为O(n^2),适合稠密图;Kruskual算法时间复杂度为O(elog2 e),适合稀疏图。
—— 边数较少可以用Kruskal,因为Kruskal算法每次查找最短的边。 边数较多可以用Prim,因为它是每次加一个顶点,对边数多的适用。

17.二维数组求地址题

 数组A[0..5,0..6]的每个元素占五个字节,将其按列优先次序存储在起始地址为1000的内存单元中,则元素A[5,5]的地址是       。(南京航空航天大学 2014年)A.1175        答案:A
【巧妙解法】数组是6行7列,总共是42个元素,每个元素占5个字节,42*5=210,又因为存储地址的起始地址为1000所以总共的地址是1210。题目是按列优先,故A[5,5]后面只有7个元素,5*7=35个字节。1210-35=1175。

18.AOE网

关键路径:从源点到汇点的所有路径中,具有最大路径长度的路径。注意:完成整个工程的最短时间就是关键路径的长度
只提高一条关键路径上的关键活动并不能缩短整个工期时间,只有加快包括在所有关键路径上的关键活动才能缩短工期。

​AOE专题_发现问题,并解决问题-CSDN博客_活动最迟发生时间​​(AOE专题)

数据结构学习笔记_子树_22

19.森林转成二叉树

数据结构学习笔记_二叉树_23

【错项】高度为h(h>0)的完全二叉树对应的森林所含的树的个数一定是h。
【解析】二叉树转换成森林中树的个数, 与该树根节点一直往右遍历, 到叶子节点的节点数相同。
h高的完全二叉树, 最右可能有h或者h-1个节点, 所以表述是错误。

20.树的存储

(1)孩子表示法​树的孩子表示法(C语言)详解​(2)孩子兄弟表示法(即二叉树表示法、二叉链表法)
nextsibling指针(指向结点的下一个兄弟结点的指针~沿此域可知道结点的所有兄弟结点)
特点:易找孩子;难找双亲(解决方法:为每个结点增设parent指针指向父节点)

typedef struct Node{
ElemType data; //数据域
struct Node *firstchild,*nextsibling; //第一个孩子和右兄弟指针
}Node,*Tree;

而若是二叉树的链式存储结构则定义为

typedef struct BiTNode{
ElemType data; //数据域
struct BiTNode *lchild,*rchild; //左右孩子指针
}BiTNode,*BiTree;

重要结论】含有n个结点的二叉链表中,还有【n+1】个空链域。
(1)二叉链表可用于表示树/森林;稀疏矩阵可用三元组/十字链表。
十字链表用于存储有向图(将行单链表&列单链表结合存储);而邻接多重表存储无向图(每条边用一个结点表示;所有依附于同一顶点的边串联在同一个链表中
——邻接多重表和邻接表唯一的区别:前者同一条边用1个结点表示,后者用2个结点)
(2)注意和线索二叉树区别。

数据结构学习笔记_子树_24

数据结构学习笔记_结点_25

【注意】不要有“A只有右结点E”这种错误想法,因为这里是树——应该是A有一个结点E!

数据结构学习笔记_1024程序员节_26

【题目】设计链式存储结构,保存上图的文件目录信息,给出数据类型定义并画出对应图中根目录部分到目录A、B及其子目录和文件的链式存储结构示意图。

数据结构学习笔记_结点_27

21.树性质

【树的三大性质】总结点数=n0+n1+...+nm;总分支数(即总边数)=1n1+2n2+...+mnm(度为m的结点引出m条分支);
2016年408】若森林F有15条边,25个结点,则F包含树的个数是(10)
【解析】每棵树,其结点数比边数多1;25-15=10棵树。
总结点数=总分支数+1(注意是树才有这个性质,如果如下题【图】就不能用)
其实由上面的三大性质可以综合推出【一个度为m的树,叶子结点树n0=1+n2+2n3+...(m-1)nm】
PS:非空二叉树前提下,n0=n2+1;

【完全二叉树性质】(1)高度=log2(n+1)——对log取上整;(log2 n)+1——对log取下整。
(2)若求出n0,n1,n2并且是完全二叉树,则知道1~n的结点中顺序:度=2、1、0结点的序号。

数据结构学习笔记_1024程序员节_28

数据结构学习笔记_1024程序员节_29


【典例2】错项:度为4的树至少在某一层上正好有4个结点。反例如下图:

数据结构学习笔记_二叉树_30

22.DFS和BFS生成树

其实在对无向图进行遍历的时候,遍历过程中所经历过的图中的顶点和边的组合,就是图的生成树或者生成森林

数据结构学习笔记_子树_31


图 1 无向图例如,图 1 中的无向图是由 V1~V7 的顶点和编号分别为 a~i 的边组成。当使用​深度优先搜索​​算法时,假设 V1 作为遍历的起始点,涉及到的顶点和边的遍历顺序为(不唯一):

数据结构学习笔记_结点_32



此种遍历顺序构建的生成树为:

数据结构学习笔记_子树_33


图 2 深度优先生成树

由深度优先搜索得到的树为深度优先生成树。同理,​​广度优先搜索​生成的树为广度优先生成树,图 1 无向图以顶点 V1 为起始点进行广度优先搜索遍历得到的树,如图 3 所示:

数据结构学习笔记_二叉树_34


图 3 广度优先生成树

23.快速排序

void Quicksort(ElemType A[],int low,int high){
if(low<high){
//Partition()是划分操作,将原表划分成2表
int pivot=Partition(A,low,high);//划分
QuickSort(A,low,pivot-1);
QuickSort(A,pivot,high);
}
}
int Partition(ElemType A[],int low,int high){
ElemType pivot=A[low];//将当前表中第一个元素设为枢轴,对表进行划分
while(low<high){ //循环跳出条件
while(low<high&&A[high]>=pivot) --high;
A[low]=A[high];//将比枢轴小的元素移动到左端
while(low<high&&A[low]<=pivot) ++low;
A[high]=A[low];//将比数轴大的元素移动到右端
}
A[low]=pivot;//枢轴元素存放到最终位置
return low;//返回存放枢轴的最终位置
}

(1)快速排序的递归次数与元素的初始排列有关。若每次划分后分区比较平衡则递归次数少。
——快排考虑极端:每趟都是找出最左边的元素作位枢轴则需要n趟排序;而如果能够每次划分平均则为log2n次(注意一趟是指前后两个子表都找出枢轴,也可能是“1个字表”),2019年真题里对一趟的定义是【对尚未确定最终位置的所有元素进行一遍处理称为一趟】
(2)快速排序的递归次数与分区处理顺序无关,即先处理 较长/短 的分区都不影响递归次数。
-----------可以理解为交换某一递归结点的左右子树,不影响树中分支数(快排的递归调用是一个二叉树)。

典例】用快速排序对下列4个序列做升序排列,各以序列第一个元素为轴点进行第一次划分,则在该次划分过程中需要移动元素次数最多的序列是      (中国科学院大学 2016)
B. {50,70,90,10,30}
解析:对于快速排序来说最好的情况就是每次都能选取需要排序序列的中间值作为比较枢轴,最坏情况就是逆序排序或者顺序排序。逆序和顺序比较次数最多,但移动次数不是。选择中间值此时一趟排序的效果尽可能的好,移动次数最多。
——【快排】每次的枢轴都把表等分为长度相近的2个子表时速度最快。
——【举例】{21,25,5,17,9,23,30} 比{21,9,17,30,25,23,5}快
——【分析】前者第一趟结果为{9,17,5}21{25,23,30},后者第一趟结果为{5,9,17}21{25,23,30},虽然枢轴都是在正中间,但是后者的前半部分为【有序的】(更慢)。
快速排序第一趟划分的方法是:将第1个元素放在最终排好序列的最终位置上,则在这个位置右边小于该元素值的元素都移到其左边,则在这个位置左边大于该元素值的元素都移到其右边。
PS:一趟排序(后面的趟则是对前后两个子表各做一次快速排序才叫【一趟】,如果枢轴是端点则是1个字表)的时间复杂度是O(logn),快排总时间复杂度是O(nlogn)。

24.图&树的概念

【回路】第一个顶点和最后一个顶点相同的路径。
【简单路径】序列中顶点不重复出现的路径。
【有向图结点的度】入度+出度——各顶点的度是邻接矩阵中此结点对应的行(出度)和列(对应列度)的非0元素之和。
【二叉树的度】二叉树的度至多为2,也可以小于2,当只有一个结点时,度为0。
【错误:二叉树就是度为2的有序树】度为2的树指树中结点的最大度为2的树。

【连通分量】无向图的极大连通子图,依附于连通分量的所有顶点的现有的所有边都加上。~可能存在回路
(1)BFS可以求无向图的所有连通分量(从一个顶点BFS可以将与这个顶点连通的顶点全部遍历到——即找到该顶点所在的连通分量
(2)n个点的无向图的连通分量最少只有1个(即自身),最多有n个(即该图没有边,每个顶点构成1个连通分量)。
【极大连通子图】无向图(不一定连通)的连通分量。

【生成树】包含连通无向图的所有顶点,≈极小连通子图。。
(1)当各边权值相同时,BFS可解决单源最短路径(由源点沿生成树分支到达其余顶点的距离都是最近的——层数最少);

数据结构学习笔记_1024程序员节_35


注意下图的总结:在有向图找(强)连通分量(1)先找环路;(2)不属于任何一个强连通分量的孤立点自身是一个强连通分量。

数据结构学习笔记_二叉树_36


【典例】n个顶点构成的强连通图至少有n条边。

——强,有向图中任何2个顶点都存在路径(一定注意:强连通图是任何顶点到其他所有顶点都有【有向路径】;类似概念:【完全有向图】是任何顶点到其他所有顶点都有2条方向相反的有向边)。最少的情况即n个顶点构成1个首尾相连的环。

25.Dijkstra


【408真题】对如下有向带权图,若采用迪杰斯特拉( Dijkstra )算法求源点 a 到其他各顶点的最短路径,则得到的第一条最


短路径的目标顶点是 b ,第二条最短路径的目标顶点是 c ,后续得到的其余各最短路径的目标顶点依次是



数据结构学习笔记_1024程序员节_37

(A)d,e,f   (B)e,d,f  (C)f,d,e    (D)f,e,d
【法一】Dijkstra常规求dist数组。
【法二】对于A,若下一个顶点为d,路径a,b,d的长度为5,而a,b,c,f的长度仅为4,由于依次选出的最短路径的长度是逐渐递增的,所以错误;同理,B,若下一个顶点为e,则路径a,b,c,e长度为7,而abcf=4;
对D,若下一个为f,没毛病,若下一个为e,abde=6,而abd=5,故D错误。

26.DFS和BFS(邻接表)

数据结构学习笔记_结点_38

【解析】如果题目给的是一个图,则遍历顺序一般不唯一;若给定了存储结构(邻接矩阵或邻接表——又叫出边表等)一般相应的变量序列是唯一。注意是【有向图】,DFS递归即可,答案(C)。

一、BFS用队列,用邻接表,遍历总时间复杂度O(n+e)
(1)顶点表:每个顶点入队一次——O(n);
(2)边表(有向图则是出边表):每条边至少访问一次——O(e)。
二、拓扑排序,有向图:用邻接表的时间复杂度O(n+e)
输出每个顶点的同时还要删除以它为起点的边(对各顶点和边都要进行遍历)。
三、删除有向图的某顶点v相关的所有边:时间复杂度O(n+e)
(1)出边:遍历v点的顶点表(删除该条单链表,出边最多为n-1,所以O(n))
(2)入边:遍历整个邻接表(删除所有的v点的入边)。
PS:DFS是先访问一个结点,然后离开顶点越远越优先访问(相当于二叉树的先序遍历)。

27.线索二叉树

二叉树线索化——
【规定】若无左子树,令lchild指向其前驱结点;若无右子树,令rchild指向其后继结点。
ltag和rtag——判断当前指针指向的是左右结点还是直接前驱(后继)。

ltag

lchild

data

rchild

rtag

数据结构学习笔记_1024程序员节_39

typedef struct ThreadNode{
ElemTpye data; //数据元素
struct ThreadNode *lchild,*rchild; //左右孩子指针
int ltag,rtag; //左右线索标志
}ThreadNode,*ThreadTree;

线索化后仍不能解决:后序线索二叉树求后序后继

28.拓扑&邻阵

【408真题】若邻接矩阵存储有向图,矩阵的主对角线下的元素均为0,则该图拓扑序列(存在,可能不唯一)。
【解析】邻接矩阵存储有向图且主对角线以下的元素均为零,说明在此有向图中,1为起点,n为终点。任何一个顶点都不能到达比其号码小的顶点(该有向图是无环图)。
在这种有向图中拓扑序列是存在的,但是可能唯一,也可能不唯一。例如,只有两个顶点的有向图,其拓扑序列就唯一。但是,三个顶点的有向图中拓扑序列就可能不唯一了。
【注意】有环的有向图可能存在拓扑序列

PS:拓扑排序既可以用栈(最后的出栈序列是逆拓扑,只需逆转过来即可)也可用队列(只是效率不同)。

29.B-树&B+树

【引子】B+树磁盘读写代价更低,查找效率更稳定,更适合OS的文件索引和数据库索引。

【m阶B树】根结点的key数为[1,m-1](即根结点最少有2棵子树);非叶结点的key数为[(m-2)/2,(2m-2)/2](里面均取上整)

(1)只支持从根结点开始的随机索引(每个结点内“关键字”是个有序表)/多路查找。

(2)和B+树一样——所有的叶结点都出现在同一层次上(B和B+树除了B树的叶结点外其余结点都存储信息)。

(3)注意B树的根的key数min为1,而其余结点的key数min为公式。

数据结构学习笔记_子树_40


【B+树】叶结点包含了全部关键字(即非叶结点含的key也会出现在叶结点中,而非叶结点没吊用,只用来索引),而B-树中所有的叶结点都出现在同一层次上,不存储信息。支持从根结点开始的随机检索和直接从叶结点开始的顺序检索

B树叶结点关键字和其他结点包含的关键字是不重复的;而B+树中的所有叶子结点包含了全部关键字信息(如下图)。

数据结构学习笔记_二叉树_41

PS:B+树在查找过程中,即使非叶结点是目标值也不会停止,而是继续向下查找,直到叶结点上的该关键字为止(无论查找是否成功,每次查找都是一条从根结点到叶结点的路径)

30.B树的add


数据结构学习笔记_二叉树_42

数据结构学习笔记_子树_43

数据结构学习笔记_结点_44

注意:每次分裂都是“中间元素m/2(取上整)”插入其父节点,若父节点的关键字也超过上限则继续分裂(中间的元素升天)。

31.B树的del

【说明】下图为4阶B-树。

1)删后还正常(key数满足)的就直接删
2)删后不正常(key小于min要求的key)且(左/右)兄弟够借:
父位替换法(你把他当兄弟,他要当你爸爸)。

数据结构学习笔记_结点_45

3)删后不正常(key小于min要求的key)且(左/右)兄弟不够借:
del后将左(右)兄弟结点及双亲结点的某key合并。
【注意】若合并中双亲结点是根结点且key减少到0(根结点关键字个数为1时,有2棵子树),则直接将根结点del,合并后的新结点成为根;
若双亲结点不是根结点,且关键字个数减少至不满足min要求的key,则要和自己的兄弟结点进行调整或合并,重复至满足。

32.del后add型题

(1)二叉排序树

【插入结点】新插入的关键字总是作为叶结点来插入,但叶结点不一定总是处于最底层。
【删除结点】(要分2种情况)
-----------------【删除非叶结点】重新得到的二叉排序树(中序遍历为从小到大序列;不一定平衡)和原来的不同。
-----------------【删除叶子结点】和原来相同。(3种二叉排序树BST删除例子见第5点笔记)。

(2)平衡二叉树

回忆:平衡二叉树是左右子树的高度差的绝对值不大于1的二叉树(错误——把二叉树改为二叉排序树)
另外,完全二叉树不一定是平衡二叉树(因为平衡二叉树是有序的,而完全二叉树不一定有序)

平衡AVL树最后插入的结点可能会导致平衡调整,所以不一定变成叶结点,而二叉排序树最后插入的结点则一定是叶子结点(结合第五点笔记)。
【2019年408】删除非空AVL树的非叶v结点后再插入v结点后,既可能变为叶结点,也可能和原来一样是非叶结点(通过旋转后继续变成非叶结点,导致变化后的树和原来一毛一样)。
PS:平衡二叉树的结点删除~王道里没有(可以参考​​​平衡二叉树之节点删除 - 简书​​)

(3)折半查找树

平衡树;折半查找要求线性表具有【随机存取】,仅适合【顺序存储结构】,元素要按关键字有序排列。

33.二路归并算法

void mergeSort(int A[],int low,int high){
if(low<high){
int mid=(low+high)/2;
mergeSort(A,low,mid); //归并排序前半段
mergeSort(A,mid+1,high);//归并排序后半段
merge(A,low,mid,high);
//直接调用天勤第一章的merge:把A数组中low到mid和mid+1到high范围内的2段有序序列归并成一段有序序列
}
}

34.栈

【栈的链式存储

链栈,多个栈共享空间(不存在栈满上溢的情况);

单链表实现(【一般规定】链栈没有头结点,Lhead指向栈顶元素——注意入栈和出栈都在链表表头进行)

数据结构学习笔记_1024程序员节_46


【典例】向一个栈顶指针为head的带头结点的链栈中插入指针L所指向的结点,操作为:

L->next=head->next;head->next=L;(不要sb写多个next)

13年408】一个栈的入栈序列为1,2,3,4,...n,其出栈序列为p1,p2,p3。。pn。若p2=3,则p3可能取值的个数为(n-1)
【解析】入1出1,入2、3,出3(p2=3)——可出2;入1,入2,出2,入3,出3(p2=3)——可出1;
其他的4~n都是可以取为p3的,如1234,入1,入2,出2,入3,出3(p2=3),入4,出4——可出4.
【易错点】栈只能在栈顶访问,插入和删除(无论是顺序栈还是链栈都只能顺序访问、不能直接存取)。

35.递归求WPL

二叉树(二叉链表存储)

typedef struct BiTNode{
int weight;
struct BiTNode,*lchild,*rchild;
}BiTNode,*BiTree;
int wpl_preorder(BiTree root,int deep){
int lwpl,rwpl; //用于存储左子树和右子树产生的wpl
lwpl=rwpl=0;
if(root->lchild==NULL&&root->rchild==NULL)//若为根结点
return deep*root->weight;
if(root->lchild!=NULL) //若左子树不空,对左子树递归遍历
lwpl=wpl_preorder(root->lchild,deep+1);
if(root->rchild!=NULL) //若右子树不空,对右子树递归遍历
rwpl=wpl_preorder(root->rchild,deep+1);
return lwpl+rwpl;
}

36.KMP算法

(1)如果串的位序是从1开始的,则next数组才需要整体加1;
(2)如果串的位序是从0开始的,则next数组不需要整体加1.
(3)【规则】当失配(s[i]≠t[j])时,i不变,j回退到next[j]的位置并重新比较。

一.求next数组

数据结构学习笔记_子树_47

数据结构学习笔记_1024程序员节_48

第一步:部分匹配值数组为001120(上图);第二步:右移1位(最左补“-1”)。

数据结构学习笔记_子树_49

二.匹配过程考察

【2019年408】主串T="abaabaabcabaabc",模式串S="abaabc",采用KMP算法进行模式匹配,到匹配成功为止,在匹配过程中进行的单个字符间的比较次数是(10次)
【规则】当失配(s[i]≠t[j],j为next数组下标)时,i不变,j回退到next[j]的位置并重新比较。
假设位序从0开始,则next数组不需整体加1(若从1开始则最后的结果也是一样的),按照旧方法(快)生成next数组:

数据结构学习笔记_结点_50

第一趟排序比较6次,在模式串的5号(j=5)位和主串的5号(i=5)位匹配失败后,模式串的下一个比较位置为next【5】=2(即下一次比较从模式串的2号位和主串的5号(i不变)位开始,然后直到模式串5号位和主串的8号位匹配,即第二趟比较4次),最后模式串匹配成功,6+4=10次。

37.哈夫曼树

数据结构学习笔记_1024程序员节_51

(1)使用一棵二叉树保存字符集中各字符的编码,每个编码对应从根开始到达某叶结点的一条路径,路径长度等于编码位数,路径到达的叶结点中保存该编码对应的字符。
(2)译码过程:从左至右依次扫描0/1串中的各位。
从根开始,根据串中当前位沿当前结点的左子指针或右子指针下移,直到移动到叶结点时为止。
输出叶结点中保存的字符,然后从根开始重复这个过程,直到0/1串结束,译码完成。
(3)二叉树既可用于保存各字符的编码,又可用于检测编码是否具有前缀特性。
判定编码是否具有前缀特性的过程,即构建二叉树的过程。


只需判定存储有字符信息的节点是否全部为叶子结点即可。若存储有某个字符信息


的节点非叶子结点,即有子节点,那么它的 0/1 编码一定是它孩子节点 0/1 编码的


前缀,违反了前缀特性。



数据结构学习笔记_二叉树_52


该例子的WPL=从根结点到树中 每个结点的路径长度之和=



数据结构学习笔记_1024程序员节_53


PS:


(1)若上栗中采用3位固定长度编码—— 二进制编码长度为300位,相比WPL=224,哈夫曼树压缩了25%数据。


相同且最优。


非叶结点的权值一定不小于下一层任一结点的权值。


38.归并的比较次数

【典例1】将两个各有n个元素的有序表归并成一个有序表,其最多的比较次数是(2n-1)
【法一】最多的比较次数是当两个有序表的数据刚好是插空顺序的时候,
比如:第一个序列是1,3,5,第二个序列是2,4,6,
把第二个序列插入到第一个序列中,先把第二个序列中的第一个元素2和第一个序列依次比较,需要比较2次(和1,3比较);第二个元素4需要比较2次(和3,5比较,因为4比2大,2之前的元素都不用比较了),第三个元素6需要比较1次(只和5比较),所以最多需要比较5次。即2n-1次。
【法二】
最好的归并情况:n次比较即可完成

数据结构学习笔记_子树_54

最坏的归并情况:2n-1次完成(每个元素要比较两次,最后一个比较一次就是2*n-2+1)

数据结构学习笔记_结点_55

【拓展】设有两个有序数组 arr1 与 arr2,数组长度分别为 m 与 n, 要合并成一个长度位 m+n 的有序数组 arr3.
最差情况下:比较次数为 m+n-1
此时,将数组 arr1 与数组 arr2 中的元素两两比较,将值小的放进数组 arr3, 直到数组 arr3 填满为止。
因为 arr3 有 m+n 个空位,每次两两比较就放进去一个数,而最后一个剩下的元素可以不用比较直接放进去,所以一共两两比较了 m+n-1 次。
最好情况下:比较次数为 min{m, n}
有个疑问: 若一个数组为 1, 2,3 ; 另一个数组为 4, 5, 6, 7; 则直接将后一个数组最小的与前一个数组最大的比较,仅需一次比较就行了,一定要注意,这是简单归并排序,必须从第一个元素比。因此,最好情况下,至少需要两两比较一个数组的长度,比较次数为 min{m,n}

【2012年408】6个升序表要求两两合并成1个升序表,使在最坏情况下的比较次数达到min——最坏情况是指一直比较到2个表尾元素,比较次数为m+n-1;而多个有序表两两合并时,若表长不同,则最坏情况的比较次数依赖于表的合并次数,于是采用【哈夫曼树】构造思想,依次选择最短的2个子表合并。

39.表达式的min顶点数

【2019年408】用有向无环图描述表达式(A+B)*((A+B)/A),至少需要顶点的数目为(5)
解析】第一步:将一个表达式转化成二叉树(相当于中序遍历);第二步:将二叉树去重转换成有向无环图。
这里的重指的是节点的重。

数据结构学习笔记_结点_56

39.堆栈

堆是指程序运行时申请的动态内存,而栈只是指一种使用堆的方法(即先进后出),所以堆栈一般用来指栈这种数据结构

40.森林&树&BT遍历

数据结构学习笔记_1024程序员节_57

【树的后根遍历】——(树T的后根遍历序列和对应的二叉树BT的中序遍历序列相同)
(1)从左到右访问双亲结点的每个孩子(转化为二叉树后就是先访问根结点再访问右子树——如下面的BT先访问根结点E再访问右子树F);
(2)访问完所有孩子后再访问它们的双亲结点(转化为二叉树后就是先访问左子树再访问根结点——如下面的访问完2个孩子E和F后访问双亲结点B即BT中左子树的根结点)。

数据结构学习笔记_二叉树_58

数据结构学习笔记_二叉树_59

上面树T的先根遍历:A B E F C D G;后根遍历:E F B C G D A.
二叉树BT的先序:A B E F C D G;中序(左中右):E F B C G D A;后序:F E G D C B A
森林的中序遍历
遍历森林中第一棵树的根节点的子树;访问第一棵树的根节点;中序遍历除了第一棵树之后剩余的树构成的子树森林。
【2020年408】已知森林 F 及与之对应的二叉树 T,若 F 的先根遍历序列是 a,b,c,d,e,f,中根遍历序列是 b,a,d,f,e,c 则 T 的后遍历序列:b,f,e,d,c,a 。
【解析】森林F的先根序列与其对应BT的先序遍历,森林F的中根遍历对应BT的中序遍历。即已知BT的先序和中序,构造后序。

数据结构学习笔记_结点_60

41.小细节

(1)当链队列的唯一一个元素出队时,需要将尾指针置为NULL(不带头结点)或指向头结点(带头结点)。
(2)未完待续。。

42.破圈法

数据结构学习笔记_二叉树_61

43.败者树

产生初始归并段——置换选择排序
构造WPL最小的哈夫曼树(减少归并过程中的读写记录数)——最佳归并树
增大归并路数——败者树

(1)外部排序败者树是一棵完全二叉树;

数据结构学习笔记_二叉树_62

数据结构学习笔记_二叉树_63

(2)置换-选择排序得到的初始归并段长度不一定相等
(3)已知:OS要求一个程序同时可用的输入/输出文件的总数不超过15个。则m路归并即至少需要m个输入缓冲区和1个输出缓冲区,因为一个缓冲区对应1个文件,所以m+1=15,m=14,可做14路归并。

数据结构学习笔记_结点_64

数据结构学习笔记_结点_65

数据结构学习笔记_二叉树_66


为了在执行内部归并时,可同时进行输入和输出操作,对于m路平衡归并排序,需要设置2m个输入缓冲区和2个输出缓冲区。

44.动态分配

在顺序表的动态存储定义中需要的数据成员:
数组指针*data、表中元素个数n、表的大小maxSize。(没有数组基址——因为动态存储空间是通过malloc或new动态分配)
【解析】表的大小和表的元素个数是必须要有的。数组的首地址用数组指针data来存储。静态分配中,数组大小和空间固定,一旦占满,再加入新的数据则溢出。
注意数组首址和数组基址不同:
数组基址是数组首地址在内存中的真实地址(物理地址),而malloc动态分配无法确定该数据基址。
数组首址是数组第一元素的下标(通常为0)。

45.卡特兰数

已知前序遍历序列(或进栈序列)为1、2、3、4,问中序遍历序列(或出栈序列)排列种数;

特别注意:已知1、2、3、4构成的二叉排序树有( )个,也可以用卡特兰数,问其中的AVL树就要画图了。

数据结构学习笔记_结点_67

46.有序表

有序表≠顺序表。
错误选项:某有序表长度为n,则可以在1~(1+n)的位置上插入元素。
 有序表插入时若指定位置,则可能使插入后的表不再是有序表。

47.防止断链

数据结构学习笔记_结点_68

48.红黑树

红黑树是BST(二叉搜索树)和平衡BST(即AVL树,平衡二叉搜索树)的权衡,红黑树也是一种平衡BST。

因为红黑树只要求部分达到平衡要求,降低了对旋转的要求,能够以O(log2 n) 的时间复杂度进行搜索、插入、删除操作。任何不平衡都会在三次旋转之内解决。即红黑树牺牲了部分平衡性以换取插入/删除操作时少量的旋转操作,整体来说性能要优于AVL树。

49.