赞
踩
二叉排序树、平衡二叉树和红黑树了解概念、性质和相关操作
B树——插入删除和查找操作;B+树了解基本概念和性质(难点)
散列查找掌握散列表构造、冲突处理方法、查找成功和失败的ASL、查找特征和性能分析
目录
编辑 五、B树和B+树(难点!!——绝对平衡树,链表存储)
在数据集中寻找满足某种条件的数据元素的过程,分为查找成功和查找失败两种情况。
用于查找的数据集合称为查找表,由同一类型的数据元素或记录组成。
基本操作:①查找某个元素是否在表中;②检索满足条件的元素的属性;③插入元素;④删除元素。
仅允许查找和检索,而不允许更改的查找表,更注重索引速度。
需要进行插入删除操作的查找表,注重插入删除是否方便。
数据元素中唯一标识该元素的某个数据项的值。
查找时,一次查找的长度 = 比较关键字的次数。
平均查找长度ASL = 所有查找过程中进行关键字比较次数的平均值。
适用于顺序表和链表,时间复杂度:O(n)
从头到尾或从尾到头依此访问查找的一种方式。
- typedef struct{
- ElemType *elem; //元素存储的空间基址,可空出0号空间存放“哨兵”
- int TableLen;
- }SSTable;
-
- int Search_Seq(SSTable ST, ElemType key)
- {
- ST.elem[0] = key; //哨兵(减少数据越界判断,非必需,对时间开销有一定但不多的帮助)
- for(int i=ST.TableLen; ST.elem[i] != key; i--);
- return i; //若不存在待查找的元素,则将返回0
- }
-
查找效率分析:
已知查找表有序的情况下,查找失败时不再比较另一端则直接返回失败信息,降低查找失败的平均查找长度。
即当待查元素大于第 i 个元素而小于第 i+1 个元素时,直接返回失败信息。
查找判定树:
查找效率分析:
顺序查找优化: 按照被查找频率按查找频率从高往低对数据元素依此排序,这样可使查找成功时ASL更小
针对有序查找表的一种查找方式。
时间复杂度:O()
适用范围:有序的顺序表(不适用于链表,链表不可随机访问)
基本思想:将给定值与查找表中值进行比对,若小于中值则在中值以左再次查找,若大于中值则在中值以右进行查找,以此类推,直到返回被查值的下标或左右指针互换位置(查找失败)。
若目标值<mid值:high = mid-1; mid = (low+high)/2
若目标值>mid值:low = mid +1; mid = (low+high)/2
- int Binary_Search(SSTable L, ElemType key)
- {
- int low = 0, high = L.TableLen, mid;
- while(low<=high)
- {
- mid = (low+high)/2;
- if(L.elem[mid] == key)
- return mid;
- else if(L.elem[mid] > key)
- high = mid-1;
- else
- low = mid+1;
- }
- return -1;
- }
判定树的构造:
若mid选择向上取整,则左子树节点数-右子树节点数 = 0或1,左右子树情况对调:
当low和high之间有偶数个元素,则mid分隔后,左半部分比右半部分多一个元素
查找成功的ASL = 近似于 <=h
查找失败的ASL<=h
分块查找又称为索引顺序查找,既有动态结构又适用于快速查找。
基本思想:将查找表分块,块内可无序而块间元素必需有序。建立一个索引表,表中个元素含有的最大关键字和第一个元素地址,索引表按关键字有序排列。
查找过程:①在索引表中确定待查记录所在块,可以顺序查找或折半查找索引表;②在块内顺序查找。
若索引表中不包含目标关键字,则折半查找索引表最终停在low>high,要在low所指分块中查找,若low超出索引表范围则查找失败。
查找次数:满树情况:最多次数 = 最少次数 = 树高;不满树情况:最多次数 = 树高 = 最少次数+1
查找效率分析(ASL):查找失败情况复杂,一般不考(考就生算);设索引查找和块内查找的平均查找长度分别为和,则平均查找长度为ASL = +
若查找表为“动态查找表”,则分块查找最好使用索引表+链表形式实现。
二叉排序树又称为二叉查找树,特点为:左子树节点值<根节点值<右子树节点值
因此对二叉排序树进行中序遍历,可得到一个递增的有序序列。
从根节点开始,沿某个分支逐层向下比较的过程:若BST非空,则先将给定值与根节点的关键字比较,若相同则成功;若小于根节点关键字则沿其左子树根节点向下进行类似操作;若大于根节点关键字则沿其右子树根节点向下进行类似操作。
- typedef struct BSTNode{
- int key;
- struct BSTNode *lchild, *rchild;
- }BSTNode, *BSTree;
-
- //查找(非递归)
- BSTNode *BST_Search(BiTree T, ElemType key)
- {
- while(T != NULL && key != T->data)
- {
- if(key<T->data)
- T = T->lchild;
- eles
- T = T->rchild;
- }
- return T; //失败会返回NULL
- }
-
- //查找(递归)
- BSTNode *BST_Search(BiTree T, ElemType key)
- {
- if(T == NULL)
- return NULL;
- if(key == T->key)
- return T;
- else if(key < T->key)
- return BST_Search(T->lchild, key);
- else if
- return BST_Search(T->rchild, key);
- }
BST是一种动态生成的树表,通过查找比较不断生成。
步骤:①若BST为空则直接插入;②若BST非空,比较待插入数据的值与根节点的值,若小于则下移插入左子树,若大于则下移插入右子树;③在子树中将节点与子树根节点进行比对,进行类似步骤②的操作,直到节点插入到对应的叶节点位置上( = 查找失败路径上访问的最后一个节点的孩子)。
- //最坏时间复杂度O(h)
- int BST_Insert(BiTree &T, KeyType key)
- {
- if(T == NULL)
- {
- T = (BiTree)malloc(sizeof(BSTNode));
- T->data = k;
- T->lchild = T->rchild = NULL;
- return 1;
- }
- else if(key == T->data)
- return 0;
- eles if(key<T->data)
- return BST_Insert(T->lchild, key);
- else
- return BST_Insert(T->rchild, key);
- }
从空树开始以此输入元素调用插入函数,逐渐构造出一棵树。
注意:BST的生成与输入的顺序相关,相同数据按不同顺序输入得到的BST树也不同(不同的关键字序列可能得到相同的BST,也可能得到不同的BST)。
- void Creat_BST(BiTree &T, KeyType str[], int n)
- {
- T = NULL;
- int i = 0;
- while(i<n)
- {
- BST_Insert(T, str[i++]);
- }
- }
BST的节点删除相对麻烦,需要保证删除该节点后其左右子树接到原树,且仍保持BST特征。
共有三种情况:
①若删除的是叶节点,则直接删除,不会破坏BST性质
②若删除节点z只有一棵左子树或右子树,则让子树根节点直接替代节点z的位置
③若删除节点z有左右两棵子树,则令z的直接后继(或前驱)替代z,然后从二叉排序树中删除这个直接后继(或前驱),这样就转换成情况①②
ps:情况③中的直接后继或前驱为中序遍历中的后继或前驱,可能为左子树最右下节点或右子树最左下节点(值与根节点最接近,不打破大小排序关系)
BST中某个元素的比较次数 = 该节点所在层的层数。
理想状态下树高 = (向上取整)——看完全二叉树部分
重点:计算平均查找长度
7.对比二分查找判定树&二叉排序树
二分查找判定树 | 二叉排序树 | |
查找平均时间性能 | 平均 = 最大 = O() | 平均 = O() 最大 = O(n) ”单支“ |
唯一性 | 唯一 | 不唯一,看输入 |
维护表有序性 | 针对有序表,插入删除需移动元素 | 无需移动节点,仅改变指针完成插入和删除 |
维护时间开销 | O(n) | O() |
适用性 | 适用于静态查找表,顺序结构进行存储 | 适用于动态表,用BST作为逻辑结构 |
在插入和删除节点时保证任意节点的左右子树高度差的绝对值小于1,这样的树称为平衡二叉树(AVL树)
平衡因子:节点左子树高 - 右子树的高(只能是-1,0,1)
为保证树的平衡,在插入节点时需检查插入路径上的节点是否因为此次的插入而出现不平衡,如果发生则找到距离插入节点最近的不平衡节点A(最小不平衡子树),对以A为根的树进行调整使其达到平衡。
步骤:插入与BST相同,检查是否出现不平衡,若没有则继续插入,若出现需进行调整:
①LL型(右单旋——父变爷,爷变右兄):因为在节点A的左孩子(L)的左子树(L)上增加新节点打破了平衡,需要进行一次右旋操作,将A的左孩子B顶替A的位置成为新的父节点,A成为B的右孩子,B原本的右孩子挂在A的左指针下。
- //伪代码思路——B是父节点,C是孩子,A是爷节点,gf为这棵子树的父节点
- A->lchild = B-> rchild
- B->rchild = A
- gf->lchild/rchild = B
②RR型(左单旋——父变爷,爷变左兄):因为节点A的右孩子(R)的右子树(R)上增加新节点打破了平衡,需要进行一次左旋操作,将A的右孩子B顶替A的位置成为新的父节点,A成为B的左孩子,B原本的左孩子挂在A的右指针下。
- //伪代码思路——B是父节点,C是孩子,A是爷节点,gf为这棵子树的父节点
- A->rchild = B-> lchild
- B->lchild = A
- gf->lchild/rchild = B
③LR型(左右双旋——子变爷,父左子,爷右子):因为在节点A的左孩子(L)的右子树(R)上增加新节点打破了平衡,需要进行一次左旋操作,将A的左孩子B的右孩子C成为A的左孩子,再进行一次右旋,将C成为新的爷节点,A、B分别成为它的左右子树,C原本的左右子树分别挂在AB的右左子树。
- //伪代码思路——B是父节点,C是孩子,A是爷节点,gf为这棵子树的父节点
- //左旋
- A->lchild = C
- B->rchild = C->lchild
- C->lchild = B
- //右旋
- A->lchild = C->rchild
- C->rchild = A
- gf->lchild/rchild = C
④RL型(右左双旋——子变爷,父右子,爷左子):因为在节点A的右孩子(R)的左子树(L)上增加新节点打破了平衡,需要进行一次右旋操作,将A的右孩子B的左孩子C成为A的右孩子,再进行一次左旋,将C成为新的爷节点,A、B分别成为它的右左子树,C原本的左右子树分别挂在AB的左右子树。
- //伪代码思路——B是父节点,C是孩子,A是爷节点,gf为这棵子树的父节点
- //右旋
- A->rchild = C
- B->lchild = C->rchild
- C->rchild = B
- //左旋
- A->rchild = C->lchild
- C->lchild = A
- gf->lchild/rchild = C
与插入操作类似,AVL的节点删除时也需要进行平衡性的检查。
步骤:①用BST方法对节点X进行删除;
②若出现不平衡现象则从节点X向上进行回溯(一路向北找最小不平衡子树),找到第一个不平衡节点A,B为节点A最近的孩子节点,C是B最近的孩子节点(找“个头”最高的儿孙节点),然后以A为根,利用与插入相同的四种调整方法进行判断调整(LL、RR、LR、RL)
③若不平衡向上传导,重复②
ps:删除与插入对节点的调整方式完全一致,但需注意,插入仅针对最小不平衡子树A,而删除时可能出现树A调整后仍需对A的祖先节点进行进一步调整,甚至回溯到根节点的情况。
AVL中查找操作与BST相同,因此查找时与给定值比较的关键字个数不超过树高,因此,含n个节点的AVL树的最大深度为,因此AVL的平均查找长度为
红黑树是以BST(注意红黑树不是AVL)为基础的,满足以下条件的树:
①左根右(左<根<右)
②根叶黑(根和叶节点是黑节点,叶节点非传统意义上的叶节点,是虚构的外部节点值为NULL,补充:如果树的全部节点都为黑节点,则树一定是满二叉树,因为黑路同)
③不红红(不可出现两个连续的红色节点)
④黑路同(每条从根节点到叶子节点的路径上拥有的黑色节点数量相同)
- struct RBnode{
- int ksy;
- RBnode *parent;
- RBnode *lchild, *rchild;
- int color;
- };
1.从根节点到叶节点的最长路径不大于最短路径的2倍
2.有n个内部节点的红黑树的高度 h<=2
3.红黑树的黑高(根节点到叶节点路径上黑色节点数量)至少为h/2
4.红黑树查找操作时间复杂度 = O()
5.若根节点黑高为h,则内部节点最少为
与AVL相似,红黑树在插入新节点时要对节点进行检查,并根据检查结果进行对应的调整,但红黑树相对AVL而言无需频繁调整树的形态,调整时也可在常数级时间内尽快完成,更适合用于频繁插入删除的场景。
插入步骤:
①若新节点是根节点,染为黑色,否则染为红色;
②若插入后仍满足红黑树定义(仅需查看是否满足不红红,其他三条都不会被打破),若满足则插入结束,若不满足,根据其叔叔(父节点的兄弟节点)的颜色进行相应调整:
(i) 黑叔:旋转+染色(染色就是换成和之前不同的颜色)
LL型:右单旋,父换爷+染色(指给父和原爷节点染色)
RR型:左单旋,父换爷+染色
LR型:左右双旋,儿换爷+染色(指给儿和原爷节点染色)
RL型:右左双旋,儿换爷+染色
(ii) 红叔:染色+变新——叔父爷染色,爷变成新节点
以下是一个具体实例,好好体会~
红黑树的删除处理和AVL的节点删除一样,按照红黑树特性(插入的调整方法)进行调整。
时间复杂度:
B树是基于平衡二叉树衍生出的m路平衡查找树,即每个节点可存放m-1个元素,这m-1个元素将区间划分为m份,继而可将后续元素插入到对应的区间内,方便后续查找使用,查找方法与平衡二叉树中元素查找相同,但需注意,B树中的“叶节点”并不存在,是虚拟设定的查找失败节点,没有具体关键字。
- //5叉排序树
- struct Node{
- ElemType keys[4];
- struct Node *child[5];
- int num;
- };
当节点内关键字较少,树高增高,为提高查找效率,要求B树中除根节点外,任何节点都要有至少m/2(向上取整)个分叉,至少有m/2(向上取整)-1个关键字。
m阶B树特点(不考虑空树情况):
①树中每个节点至多有m棵子树,即至少有m/2(向上取整)-1个关键字
②若根节点不是终端节点,则至少有两棵子树
③除根节点外所有非叶节点至少有m/2(向上取整)棵子树,即含有m/2(向上取整)-1个关键字
④所有叶节点都出现在同一层次上,且不带信息(等同于折半查找判定树上的失败节点,实际值为NULL)
⑤非叶节点内结构为指针与数据交叉存放的状态,如图,P为指针,K为关键字
因此B树的高度范围为:
总结:
与AVL的查找类似,先与根节点进行比较,根据左根右的性质进行进一步比对查找,若与某非叶节点关键字匹配则查找成功,若未匹配则进入失败节点,查找失败。
以5阶B树为例,除根节点外每个非叶节点关键字个数不小于2,不超过4,按25,38,49,60,80,90,99,88,83,87,92,93,94,73,74,75,71,72,76,77的顺序进行插入,有以下几个关键步骤
①在节点未满时,按顺序(这里的顺序是满足BST规则的大小顺序,不等于输入顺序)正常插入,当一个节点已满,令其中间元素成为根节点,左右两侧元素成为其子树的节点内关键字,树的高度+1:
-->
②继续插入元素,注意新元素一定插入到最底层的“终端节点”,利用查找来确定插入位置,不可插入上层根节点内(根节点内是调整过去的关键字)
③当再次出现节点已满的情况,将中间元素上移至根节点,节点内剩余m-2个元素划分到两个节点内:
④当当前最大根节点已满时,与处理终端节点类似,将根节点分割为新的最大根节点和其两个子树节点,并改变指针指向,同时树的高度+1:
⑤最终结果树:
总结:
以插入的最终结果树为例进行删除操作,初始树的形态如图:
删除顺序:77,82,38,49,82
①被删除关键字在终端节点(不是失败节点),直接删除该关键字;
i 若删除后节点内关键字个数低于最小的要求(如五阶每个节点要求关键字>=2) ,且与此关键字所在节点相邻的兄弟节点内关键字个数宽裕,则让父节点内对应元素落入该节点,让兄弟节点中的最小/最大元素升入父节点中;(就是拿节点后继/前驱,后继的后继/前驱的前驱来填补)
删除关键字38
ii 若兄弟节点内没有宽裕的关键字,则将删除关键字后的节点与父节点内对应关键字、兄弟节点内元素进行合并,形成新的节点。
删除关键字49:
iii 若删除根节点内最后一个关键字,则判断其孩子节点关键字个数和是否超出节点内允许的最大关键字个数,若未超出,则直接合并形成新的根节点:
删除82:
iv 若孩子节点中有富裕的元素,则同之前一样,将其中合适的拿个提上来成为新的根节点内关键字(根节点的处理和其他节点处理没什么不同,只是不限制内部最小元素个数而已)
②被删除关键字在非终端节点,用直接前驱(左侧指针所指子树最“右下”元素)或直接后继(右侧指针所指子树最“左下”元素)代替被删除的关键字。
注意:①对非终端节点关键字的删除必然可转换为对终端节点的删除操作
与B树类似AVL的特性不同,B+树更类似于分块索引,其所有关键字全在终端节点,每个终端节点连接一个记录(类似失败节点),在查找和删除时可选择利用索引表或直接对链表进行操作。
m阶B+树满足的条件:
①每个分支节点最多有m棵子树(孩子节点),且所有关键字全在叶子节点上,满足绝对平衡条件
②非叶根节点至少有两棵子树,其他每个分支节点至少有m/2(向上取整)棵子树
③节点的子树个数与关键字个数相同
④所有叶节点包含关键字及指向相应记录的指针,叶节点中将关键字按大小顺序排列,相邻节点按大小顺序相互链接(支持顺序查找)
⑤所有分支节点中仅包含其各个子节点中关键字最大值和相应指针(和分块索引一样)
B+树的查找与分块查找相同,遵循多路查找原则(当然也可以顺序查找),如下图查找关键字9,就可以从根节点进行比对,<15选择走左侧,<15选择走9和15中间指针所指引的区域,=9查找成功。
注意:查找成功时一定是停留在叶子节点上,可能分支节点内也有与关键字相同值的元素,但并非我们要查询的结果。
m阶B树 | m阶B+树 |
节点中n个关键字对应n+1棵子树(失败节点) | 节点中n个关键字对应n棵子树(记录) |
根节点关键字个数1<=n<=m-1 其他节点关键字个数m/2(向上取整)-1<=n<=m-1 | 根节点关键字个数1<=n<=m 其他节点关键字个数m/2(向上取整)<=n<=m |
各节点内包含的关键字不重复且都包含信息 | 仅叶节点包含信息,其他节点仅用于索引(使磁盘块内包含更多关键字,使树更矮读盘次数更少) |
应对这类题,需要按折半查找判定树的性质对节点依次编号,利用编号代替数值依次判定每个子树是否满足树的性质,同时注意,不是只能向下取整,也可能是向上取整,只要一棵树内部条件一致即可。
7.3选择20题
这类题记住这个递推公式,当平衡因子全为1时树高和节点数之间关系有:
7.3选择题21 & 25 对比来看这两个题:
在BST中删除一个节点,再将该节点插入,若删除的是叶节点,则插入后该节点仍在原位,但如果删除的不是叶节点,则插入后BST改变。 (BST插入的节点都在叶节点)
在AVL中删除一个节点再插入,如果这个节点是叶子节点则树有可能变也可能不变,若为非叶节点则前后树也可能相同也可能不同。
在AVL中插入一个节点,在失去平衡调整前,一定插入在叶节点的位置。
若删除的是叶节点则可能不会破坏平衡,即不会调整,再插入该节点得到的树与原树相同;若删除后失去平衡调整,在插入该节点可能树与原树不同。
7.4选择题5~9 重灾区!!!
①高为h的m阶B树,至少有[(m/2)(向上取整)-1]^h-1(每个节点都存放最少的关键字)个关键字,最多有(m^h-1) /2(满m叉树)个关键字
②含n个非叶节点(除根节点外每个节点至少(m-2)(向上取整)-1个关键字)的m阶B树中至少包含 (n-1)((m-2)(向上取整)-1)+1 个关键字
③一棵m阶B树中共有k个关键字,则树高度范围为
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。