数据结构——第七章《查找》

第七章查找

7.1查找的基本概念

1,基本概念
1,查找

在数据集合中寻找满足某种条件的数据元素的过程称为查找

2,查找表(查找结构)

用于查找的数据集合称为查找表,它由同一类型的数据元素(或记录)组成

1,静态查找表

只需要查找操作

2,动态查找表

除了查找,还需要增/删数据操作

3,关键字

数据元素中唯一标识该元素的某个数据项的值,使用基于关键字的查找,查找结果应该是唯一的.

2,查找算法的效率评价

查找长度——在查找运算中,需要对比关键字的次数称为查找长度
平均查找长度(ASL,Average Search Length)——所有查找过程中进行关键字的比较次数的平均值

评价一个查找算法的效率时,通常考虑查找成功/查找失败两种情况的ASL。

7.2顺序查找和折半查找

7.2.1顺序查找
1,算法实现

从头到jio(或者从jio到头)挨个找
适用于顺序表、链表,表中元素有序无序都可以
可在0号位置存“哨兵”,从尾部向头部挨个查找
优点:循环时无需判断下标是否越界

typedef struct{
    Elemtype *elem;
    int Tablelen;
}SSTable;

//顺序查找
int Search_Seq(SSTable ST, ElemType key){
    int i;
    for(i = 0; i < ST.Tablelen && ST.elem[i] != key; ++i);
    return i == ST.Tablelen? -1 : i;
}
2,优化
1,若表中元素有序

当前关键字大于(或小于)目标关键字时,查找失败
优点:查找失败时ASL更少

查找判定树:

成功结点的关键字对比次数=结点所在层数
失败结点的关键字对比次数=其父节点所在层数

2,若各个关键字被查概率不同

可按被查概率降序排列
优点:查找成功时ASL更少

3,时间复杂度

查找成功和查找失败的时间复杂度都是O(n)

7.2.2折半查找
1,适用范围

折半查找,又称“二分查找”,仅适用于有序的顺序表。

2,算法思想

在[low,high]之间找目标关键字,每次检查mid=(low+high)/2
根据mid所指元素与目标关键字的大小调整low或high,不断缩小low和high的范围
若low>high 则查找失败

typedef struct{
    Elemtype *elem;
    int Tablelen;
}SSTable;

//折半查找
int Binary_Search(SSTable L, ElemType key){
    int low = 0, high = L.Tablelen - 1, 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;
    }
}
3,判定树
1,构造

由mid所指元素将原有元素分割到左右子树中
Key:右子树结点数-左子树结点树=0或1

2,特性

折半查找的判定树是平衡的二叉排序树(左<中<右)
折半查找判定树,只有最下面一层是不满的
若查找表有n个关键字,则失败结点有n+1个

树高h=「log2(n+1)]
(不包含失败结点)

4,时间复杂度

o(log2n)

7.2.3分块查找

又称“索引顺序查找”,数据分块存储,块内无序、块间有序

1,算法思想

索引表中记录每个分块的最大关键字、分块的区间
分块查找,又称索引顺序查找,算法过程如下:
①在索引表中确定待查记录所属的分块(可顺序、可折半)
②在块内顺序查找

2,ASL

ASL=查索引表的平均查找长度+查分块的平均查找长度

顺序查找索引表

折半查找索引表

3,易错点

对索引表进行折半查找时,若索引表中不包含目标关键字,则折半查找最终停在low>high,要在low所指分块中查找

7.3树形查找

7.3.1二叉排序树(BST)
1,二叉排序树的定义

二叉排序树,又称二叉查找树(BST,Binary Search Tree)

一棵二叉树或者是空二叉树,或者是具有如下性质的二叉树:

左子树上所有结点的关键字均小于根结点的关键字;

右子树上所有结点的关键字均大于根结点的关键字。

左子树和右子树又各是一棵二叉排序树。

对二叉排序树进行中序遍历,可以得到一个递增的有序序列

2,查找操作

从根节点开始,目标值更小往左找,目标值更大往右找

//二叉排序树节点
typedef struct BSTnode{
    int key;
    struct BSTnode *lchild, *rchild;
}BSTNode, *BSTree;

//在二叉排序树中查找值为key的节点
BSTNode *BST_Search(BSTree T, int key)
{
    while(T != NULL && key != T->key)
    {
        if(key < T->key)
        {
            T = T->lchild;
        }
        else
        {
            T = T->rchild;
        }
    }
    return T;
}
3,插入操作

找到应该插入的位置(一定是叶子结点),一定要注意修改其父节点指针。

若原二叉排序树为空,则直接插入结点;

否则,若关键字k小于根结点值,则插入到左子树,若关键字k大于根结点值,则插入到右子树。

//在二叉排序树插入关键字为K的新结点
int BST_Insert(BSTree &T, int k)
{
    if(T == NULL)//原树为空,新插入节点为根节点
    {
        T = (BSTree)malloc(sizeof(BSNode));
        T->key = k;
        T->lchild = T->rchild = NULL;
        return 1;
    }
    else if(k == T->key)//树中存在相同节点,插入失败
        return 0;
    else if(k < T->key)
        return BST_Insert(T->lchild, k);
    else
        return BST_Insert(T->rchild, k);
}
4,删除操作

先搜索找到目标结点:

①若被删除结点z是叶结点,则直接删除,不会破坏二叉排序树的性质。

②若结点z只有一棵左子树或右子树,则让z的子树成为z父结点的子树,替代z的位置。

③若结点z有左、右两棵子树,则令z的直接后继(或直接前驱)替代z,然后从二叉排序树中删去这个直接后继(或直接前驱),这样就转换成了第一或第二种情况。

z的后继:z的右子树中最左下结点(该节点一定没有左子树)

z的前驱:z的左子树中最右下结点(该节点一定没有右子树)

5,查找效率分析

取决于树的高度,最好O(log n),最坏O(n)

平均查找长度的计算

查找成功的情况

查找失败的情况(需补充失败结点)

7.3.2平衡二叉树(AVL)
1,定义

平衡二叉树(Balanced Binary Tree),简称平衡树(AVL树)——树上任一结点的左子树和右子树的高度之差不超过1。

结点的平衡因子=左子树高-右子树高。

2,插入操作

和二叉排序树一样,找合适的位置插入新插入的结点可能导致其祖先们平衡因子改变,导致失衡

3,调整不平衡

找到最小不平衡子树进行调整,记最小不平衡子树的根为A

在A的左孩子的左子树中插入导致不平衡(LL)

1)LL平衡旋转(右单旋转)。

由于在结点A的左孩子(L)的左子树(L)上插入了新结点,A的平衡因子由1增至2,导致以A为根的子树失去平衡,需要一次向右的旋转操作。将A的左孩子B向右上旋转代替A成为根结点,将A结点向右下旋转成为B的右子树的根结点,而B的原右子树则作为A结点的左子树。

在A的右孩子的右子树中插入导致不平衡(RR)

2)RR平衡旋转(左单旋转)。

由于在结点A的右孩子(R)的右子树(R)上插入了新结点,A的平衡因子由-1减至-2,导致以A为根的子树失去平衡,需要一次向左的旋转操作。将A的右孩子B向左上旋转代替A成为根结点,将A结点向左下旋转成为B的左子树的根结点,而B的原左子树则作为A结点的右子树。

在A的左孩子的右子树中插入导致不平衡(LR)

3)LR平衡旋转(先左后右双旋转)。

由于在A的左孩子(L)的右子树(R)上插入新结点,A的平衡因子由1增至2,导致以A为根的子树失去平衡,需要进行两次旋转操作,先左旋转后右旋转。先将A结点的左孩子B的右子树的根结点C向左上旋转提升到B结点的位置,然后再把该C结点向右上旋转提升到A结点的位置。

在A的右孩子的左子树中插入导致不平衡(RL)

4)RL平衡旋转(先右后左双旋转)。

由于在A的右孩子(R)的左子树(L)上插入新结点,A的平衡因子由-1减至-2,导致以A为根的子树失去平衡,需要进行两次旋转操作,先右旋转后左旋转。先将A结点的右孩子B的左子树的根结点C向右上旋转提升到B结点的位置,然后再把该C结点向左上旋转提升到A结点的位置。

补充做题技巧!!!

只有左孩子才能右上旋,只有右孩子才能左上旋

  • LL

在A的左孩子的左子树中插入导致不平衡

调整:A的左孩子结点右上旋

  • RR

在A的右孩子的右子树中插入导致不平衡

调整:A的右孩子结点左上旋

  • LR

在A的左孩子的右子树中插入导致不平衡

调整:A的左孩子的右孩子先左上旋再右上旋

  • RL

在A的右孩子的左子树中插入导致不平衡

调整:A的右孩子的左孩子先右上旋后左上旋

4,查找效率分析

若树高为h,则最坏情况下,查找一个关键字最多需要对比h次,即查找操作的时间复杂度不可能超过o(h)

平衡二叉树—一树上任一结点的左子树和右子树的高度之差不超过1。

假设以n,表示深度为h的平衡树中含有的最少结点数。

则有no=0,n1=1,n2=2,并且有nh=nh-1+nh-2+1。

可以证明含有n个结点的平衡二叉树的最大深度为O(log2n),平衡二叉树的平均查找长度为O(log2n)。

7.3.3平衡二叉树(删除操作)

①删除结点(方法同“二叉排序树”)

·若删除的结点是叶子,直接删。
·若删除的结点只有一个子树,用子树顶替删除位置
·若删除的结点有两棵子树,用前驱(或后继)结点顶替,并转换为对前驱(或后继)结点的删除。

②一路向北找到最小不平衡子树,找不到就完结撒花

③找最小不平衡子树下,“个头”最高的儿子、孙子

④根据孙子的位置,调整平衡(LL/RR/LR/RL)

·孙子在LL:儿子右单旋
·孙子在RR:儿子左单旋
·孙子在LR:孙子先左旋,再右旋
·孙子在RL:孙子先右旋,再左旋

⑤如果不平衡向上传导,继续②

对最小不平衡子树的旋转可能导致树变矮,从而导致上层祖先不平衡(不平衡向上传递)。

7.3.4红黑树的定义和性质
1,背景

平衡二叉树AVL:插入/删除很容易破坏“平衡”特性,需要频繁调整树的形态。如:插入操作导致不平衡,则需要先计算平衡因子,找到最小不平衡子树(时间开销大),再进行LL/RR/LR/RL调整

红黑树RBT:插入/删除很多时候不会破坏“红黑”特性,无需频繁调整树的形态。即便需要调整,一般都可以在常数级时间内完成

平衡二叉树:适用于以查为主、很少插入/删除的场景

红黑树:适用于频繁插入、删除的场景,实用性更强

2,定义

左子树结点值≤根结点值≤右子树结点值

①每个结点或是红色,或是黑色的
②根节点是黑色的
③叶结点(外部结点、NULL结点、失败结点)均是黑色的
④不存在两个相邻的红结点(即红结点的父节点和孩子结点均是黑色)
⑤对每个结点,从该节点到任一叶结点(不是叶子节点,而是失败节点)的简单路径上,所含黑结点的数目相同

左根右——RBT是一种BST,需满足左s根s右
根叶黑——根节点、叶结点一定是黑色
不红红——任何一条查找路径上不能连续出现两个红结点
黑路同——从任一结点出发,到达任一空叶结点的路径上经过的黑结点数量都相同

3,性质

性质1:从根节点到叶结点的最长路径不大于最短路径的2倍
性质2:有n个内部节点的红黑树高度h≤2logz(n+1)

红黑树查找操作时间复杂度=0(log2n)

4,查找

与BST、AVL相同,从根出发,左小右大,若查找到一个空叶节点,则查找失败

7.3.5红黑树的插入

先查找,确定插入位置(原理同二叉排序树),插入新结点
新结点是根一一染为黑色
新结点非根一—染为红色

若插入新结点后依然满足红黑树定义,则插入结束
若插入新结点后不满足红黑树定义,需要调整,使其重新满足红黑树定义

黑叔:旋转+染色

·LL型:右单旋,父换爷+染色
·RR型:左单旋,父换爷+染色
·LR型:左、右双旋,儿换爷+染色
·RL型:右、左双旋,儿换爷+染色

红叔:染色+变新

·叔父爷染色,爷变为新结点

7.3.6红黑树的删除

①红黑树删除操作的时间复杂度=O(log2n)
②在红黑树中删除结点的处理方式和“二叉排序树的删除”一样
③按②删除结点后,可能破坏“红黑树特性”,此时需要调整结点颜色、位置,使其再次满足“红黑树特性”。

7.4B树和B+树

7.4.1B树

B树,又称多路平衡查找树,B树中所有结点的孩子个数的最大值称为B树的阶,通常用m表示。一棵m阶B树或为空树,或为满足如下特性的m叉树:
1)树中每个结点至多有m棵子树,即至多含有m-1个关键字。
2)若根结点不是终端结点,则至少有两棵子树。
3)除根结点外的所有非叶结点至少有[m/2]棵子树,即至少含有[m/2]-1个关键字。
5)所有的叶结点(失败节点)都出现在同一层次上,并且不带信息(可以视为外部结点或类似于折半查找判定树的查找失败结点,实际上这些结点不存在,指向这些结点的指针为空)。

m阶B树的核心特性:
1)根节点的子树数E[2,m],关键字数E[1,m-1]。
其他结点的子树数E[[m/21,m];关键字数E[[m/21-1,m-1]
2)对任一结点,其所有子树高度都相同
3)关键字的值:子树0<关键字1<子树1<关键字2<子树2<…(类比二叉查找树左<中<右)

7.4.2B树的插入删除
1,插入

核心要求:
①对m阶B树——除根节点外,结点关键字个数[m/21-1≤n≤m-1
②子树0<关键字1<子树1<关键字2<子树2<……
新元素一定是插入到最底层“终端节点”,用“查找”来确定插入位置
在插入key后,若导致原结点关键字数超过上限,则从中间位置([m/2])将其中的关键字分为两部分,左部分包
含的关键字放在原结点中,右部分包含的关键字放到新结点中,中间位置([m/2])的结点插入原结点的父结点。
若此时导致其父结点的关键字个数也超过了上限,则继续进行这种分裂操作,直至这个过程传到根结点为止,进
而导致B树高度增1。

2,删除
1,非终端节点关键字

用其直接前驱或直接后继替代其位置,转化为对“终端结点”的删除
直接前驱:当前关键字左边指针所指子树中“最右下”的元素
直接后继:当前关键字右边指针所指子树中“最左下”的元素

2,终端节点关键字

1,删除后结点关键字个数未低于下限,无需任何处理

2,低于下限

右兄弟够借,则用当前结点的后继、后继的后继依次顶替空缺
左兄弟够借,则用当前结点的前驱、前驱的前驱依次顶替空缺
左(右)兄弟都不够借,则需要与父结点内的关键字、左(右)兄弟进行合并。
合并后导致父节点关键字数量-1,可能需要继续合并。

7.4.3B+树
1,定义

一棵m阶的B+树需满足下列条件:
1)每个分支结点最多有m棵子树(孩子结点)。

2)非叶根结点至少有两棵子树,其他每个分支结点至少有[m/2]棵子树。

3)结点的子树个数与关键字个数相等。

4)所有叶结点包含全部关键字及指向相应记录的指针,叶结点中将关键字按大小顺序排列,并且相邻叶结点按大小顺序相互链接起来。

5)所有分支结点中仅包含它的各个子结点中关键字的最大值及指向其子结点的指针。

2,比较

m阶B+树:
1)结点中的n个关键字对应n棵子树

m阶B树:
1)结点中的n个关键字对应n+1棵子树

m阶B+树:
2)根节点的关键字数ne[1,m]
其他结点的关键字数ne[[m/2],m]

m阶B树:
2)根节点的关键字数nE[1,m-1]。
其他结点的关键字数nE[[m/2]-1,m-1]

m阶B+树:
3)在B+树中,叶结点包含全部关键字,非叶结点中出现过的关键字也会出现在叶结点中

m阶B树:
3)在B树中,各结点中包含的关键字是不重复的

3,总结对比
m阶B树m阶B+树
类比二叉查找树的进化——>m叉查找树分块查找的进化——>多级分块查找
关键字与分叉n个关键字对应n+1个分叉(子树)n个关键字对应n个分叉
节点包含的信息所有节点中都包含记录的信息只有最下层叶子节点才包含记录的信息(可使树更矮)
查找方式不支持顺序查找。查找成功时,可能停留在任何一层节点,查找速度”不稳定“支持顺序查找。查找成功或失败都会到达最下一层节点,查找速度“稳定”

相同:

除根节点外,最少[m/2]个分叉(确保节点不要太空)

任何一个节点的子树都要一样高(确保“绝对平衡“)

7.5散列查找

1,概念

散列表(HashTable),又称哈希表。是一种数据结构,特点是:数据元素的关键字与其存储地址直接相关。

若不同的关键字通过散列函数映射到同一个值,则称它们为“同义词”
通过散列函数确定的位置已经存放了其他元素,则称这种情况为“冲突”

散列查找是典型的“用空间换时间”的算法,只要散列函数设计的合理,则散列表越长,冲突的概率越低。

2,常见散列函数
1,除留余数法

除留余数法—H(key)=key%p
散列表表长为m,取一个不大于m但最接近或等于m的质数p

质数又称素数。指除了1和此整数自身外,不能被其他自然数整除的数

用质数取模,分布更均匀,冲突更少。

2,直接定址法

直接定址法——H(key)=key 或H(key)=a*key + b
其中,a和b是常数。这种方法计算最简单,且不会产生冲突。它适合关键字的分布基本连续的情况,若关键字分布不连续,空位较多,则会造成存储空间的浪费。

3,数字分析法

数字分析法——选取数码分布较为均匀的若干位作为散列地址
设关键字是r进制数(如十进制数),而r个数码在各位上出现的频率不一定相同,可能在某些位上分布均匀一些,每种数码出现的机会均等;而在某些位上分布不均匀,只有某几种数码经常出现,此时可选取数码分布较为均匀的若干位作为散列地址。这种方法适合于已知的关键字集合,若更换了关键字,则需要重新构造新的散列函数。

4,平方取中法

平方取中法——取关键字的平方值的中间几位作为散列地址。
具体取多少位要视实际情况而定。这种方法得到的散列地址与关键字的每位都有关系,因此使得散列地址分布比较均匀,适用于关键字的每位取值都不够均匀或均小于散列地址所需的位数。

3,冲突处理
1,拉链法(链地址法)

用拉链法(又称链接法、链地址法)处理“冲突”:把所有“同义词”存储在一个链表中

2,开放定址法

所谓开放定址法,是指可存放新表项的空闲地址既向它的同义词表项开放,又向它的非同义词表项开
放。其数学递推公式为:

Hi=(H(key)+di)% m
i=0.1.2…k(k≤m-l),m表示散列表表长;di为增量序列;i可理解为“第i次发生冲突”

①线性探测法–di=0,1,2,3,…,m-1;即发生冲突时,每次往后探测相邻的下一个单元是否为空。

②平方探测法。当di=02,12,-12,22,-22,…,k2,(-k)^2时,称为平方探测法,又称二次探测法其中k<=m/2

③伪随机序列法。di是一个伪随机序列,如d=0,5,24,11,…

3,再散列法

再散列法(再哈希法):除了原始的散列函数H(key)之外,多准备几个散列函数,当散列函数冲突时,用下一个散列函数计算一个新地址,直到不冲突为止:

4,查找效率

取决于散列函数、处理冲突的方法、装填因子a。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值