数据结构与算法----查找

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

查找表---用于查找数据集合称为查找表,它由同一类型的数据元素组成

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

查找长度--在查找运算中,需要对比关键字的次数称为查找长度

平均查找长度(ASL)--所有查找过程中进行关键字的比较次数的平均值

顺序查找

通常用于线性表

typedef struct{         //查找表的数据结构
    ElemType *elem;     //动态数组的基址
    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
}

一个成功结点的查找长度=自身所在的层数

一个失败结点的查找长度=其父节点所在的层数。默认等概率发生

被查概率不相等时,将概率大的放在靠前位置,这样查找成功的查找长度会减少但是失败不会变

折半查找

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;             //查找失败,返回﹣1
}

折半查找判定树的构造

当low和high之间有奇数个元素,则mid分隔后,左右两部分元素个数相等

当low和high之间有偶数个元素,则mid分隔后,左半部分比右半部分少一个元素

该树为相对的平衡二叉树

树高h=log2(n+1)

时间复杂度:O(log2n)

分块查找

分块查找(索引顺序查找):

  • 在索引表中确定待查记录所属的分块(可顺序,可折半)

  • 在块内顺序查找

//索引表
typedef struct{
    ElemType maxValue;
    int low,high;
}Index;
 

查找效率分析

ASL=L1+L2;L1:索引查找平均查找长度;L2:块内查找的平均查找长度 ASL最小=(n^1/2)+1

二叉排序树

二叉查找树:左子树结点值<根节点值<右子树结点值

二叉排序树查找:

若树非空,目标值与根节点的值比较:

若相等,则查找成功;

若小于根节点,则在左子树上查找,否则在右子树上查找。

查找成功,返回结点指针,查找失败返回NULL;

//二叉排序树结点
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;
}
//在二叉排序树中查找值为key的结点(递归实现)
空间复杂度O(h)
BSTNode *BSTSearch(BSTree T,int key){
    if(T == NULL )
        return NULL;        //查找失败
    if(key == T->key) 
        return T;           //查找成功
    else if(key< T->key)
        return BSTSearch(T->lchild,key);//在左子树中找
    else 
        return BSTSearch(T->rchild,key);//在右子树中找
}
若原二叉排序树为空,则直接插入结点;否则,若关键字 k 小于根结点值,则插入到左子树,若关键字 k 大于根结点值,则插入到右子树
//在二叉排序树插入关键字为 k 的新结点(递归实现) 
int BST_Insert(BSTree &T, int k){
    if (T == NULL){             //原树为空,新插入的结点为根结点
        T =(BSTree)malloc(sizeof(BSTNode));
        T->key=k;
        T->lchild=T->rchild=NULL;
        return 1;               //返回1,插入成功
    else if(k==T->key)          //树中存在相同关键字的结点,插入失败
        return 0;
    else if(k< T->key)          //插入到 T 的左子树    
        return BST_Insert(T->lchild,k);
    else                        //插入到 T 的右子树               
        return BST_Insert(T->rchild,k);
}
//按照str[]中的关键字序列建立二叉排序树
void Creat_BST(BSTree &T,int str[],int n){
    T=NULL;     //初始时T为空树
    int i=0;
    while(i<n){     //依次将每个关键字插入到二叉排序树中
        BST_Insert(T,str[i]);
        i++;
    }
}

二叉排序树的删除:

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

  • 若删除的结点只有左子树或者只有右子树,则让它的子树成为它父节点的子树,代替它的位置

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

查找长度--在一次查找运算中,需要对比关键字的次数成为查找长度,反映了查找操作时间复杂度

平衡二叉树

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

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

//平衡二叉树结点
typdef struct AVLNode{
    int key;        //数据域
    int balance;    //平衡因子
    struct AVLNode *lchild,*rchild;
}AVLNode,*AVLTree;

调整最小不平衡子树:

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

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

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

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

Nh=Nh-1+Nh-2+1

例子:N0=0;N1=1;N2=2;N3=4;N4=7;N5=12;

平均查找长度=O(log2N)

平衡二叉树的删除操作:

  • 删除结点(方法同二叉排序树)

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

  • 找最小不平衡子树,“个头“最高的儿子,孙子

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

  • 如果不平衡向上传导,继续2

B树

特性:B 树,又称多路平衡查找树, B 树中所被允许的孩子个数的最大值称为 B 树的阶,通常用 m 表示。一棵 m 阶 B 树或为空树,或为满足如下特性的 m 叉树:

  • 树中每个结点至多有 m 棵子树,即至多含有 m -1个关键字。

  • 若根结点不是终端结点,则至少有两棵子树。

  • 除根结点外的所有非叶结点至少有[ m /2]棵子树,即至少含有[ m /2]-1个关键字。

  • 所有非叶结点的结构如下:

    其中,Ki(i=1,2,...n)为结点的关键字,且满足K1<k2<……<Kn;Pi(i=0,1,2....n)为指向子树根节点的指针,且指针Pi-1所指子树中所有结点的关键字均小于Ki,Pi所指子树中的所有结点的关键字均大于Ki,n([m/2]-1<=n<=m-1)为结点的关键字的个数。

  • 所有的叶结点都出现在同一层次上,并且不带信息(可以视为外部结点或类似于折半查找判定树的查找失败结点,实际上这些结点不存在,指向这些结点的指针为空)。

m阶B树的核心特性:

  • 根节点的子树数E[2,m],关键字数E[1,m-1].

其他节点的子树数E[[m/2],m];关键字数E[[m/2]-1,m-1]

  • 对任一结点,其所有子树的高度都相同。

  • 关键字的值:子树0<关键字1<子树1<关键字2<子树2

含有n个关键字的m阶B数,最小高度h>=logm(n+1),最大高度:h<=log(m/2) (n+1)/2 + 1

至少包含的关键字总数:1+2(K^(h-1)-1)

插入

在插入key后,若导致原结点关键字数超过上限,则从中间位置([m/2])将其中的关键字分为两部分,左部分包含的关键字放在原结点中,右部分包含的关键字放到新结点中,中间位置([m/2])的结点插入原结点的父节点。

注:新插入的结点一定是插入到最底层的终端结点。

删除

若被删除关键字在非终端结点,则用直接前驱或者直接后继来代替被删除的关键字

直接前驱:当前关键字左侧指针所指子树中”最右下“的元素

直接后继:当前关键字右侧指针所指子树中”最左下“的元素

  • 兄弟够借,若被删除关键字所在结点删除前的关键字个数低于下限,且与此结点右(或左)兄弟结点的关键字个数还很宽裕,则需要调整该结点、右(或左)兄弟结点及其双亲结点(父子换位法)

  • 兄弟不够借。若被删除关键字所在结点删除前的关键字个数低于下限,且此时与该结点相邻的左、右兄弟结点的关键字个数均=[ m /2]-1,则将关键字删除后与左(或右)兄弟结点及双亲结点中的关键字进行合并

在合并过程中,双亲结点中的关键字个数会减1。若其双亲结点是根结点且关键字个数减少至0(根结点关键字个数为7时,有2棵树),则直接将根结点删除,合并后的新结点成为根;若双亲结点不是根结点,且关键字个数减少到[ m /2]-2,则又要与它自己的兄弟结点进行调整或合并操作,并重复上述步骤,直至符合 B 树的要求为止。

B+树

特点:块内无序,块间有序,绝对平衡

一个m阶的B+树满足的条件:

  • 每个分支结点最多有m棵子树(孩子节点)

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

  • 结点的子树个数与关键字个数相等

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

B+树与B树对比:

B+树:

  • 结点中的n个关键字对应n棵子树

  • 根节点关键字nE[1,m];其他节点的关键字数nE[[m/2],m]

  • 在B+树中,叶结点包含全部关键字,非叶节点中出现过的关键字也会出现在叶结点中

  • 在B+树中,叶结点包含信息,所有非叶节点仅起索引作用,非叶节点中的每个索引项只含有对应子树的最大关键字和指向该子树的指针,不含有该关键字对应记录的存储地址。

B树

  • 结点中的n个关键字对应n+1棵子树

  • 根节点的关键字数nE[1,m-1];其他节点的关键字数nE[[m/2]-1,m-1]

  • 在B树中,各节点中包含的关键字是不重复的

  • B树的结点中都包含了关键字对应的记录的存储地址。

散列表

是一种数据结构。特点:可以根据数据元素的关键字机算出它在散列表中的存储地址

散列函数:关键字->存储地址的映射关系。

冲突(碰撞):

在散列表中插入一个数据元素时,需要根据关键字的值确定其存储地址,若该地址已经存储了其他元素,则称这种情况为”冲突“。

同义词:若不同的关键字通过散列函数映射到同一个存储地址,则称它们为”同义词“。

处理冲突方法:

  • 拉链法(链接法,连地址法):把所有”同义词“存储在一个链表中

    • 插入元素:

      • 结合散列函数计算新元素的散列地址

      • 将新元素插入散列地址对应的链表(可用头插法,也可用尾插法)

  • 开放定址法:如果发生”冲突“,就给新元素找另一个空闲位置。

    • Hi=(H(key)+di)%m

    • di:

      • 线性探测法 di=0,1,2,3,m-1

      • 平方探测法 di=0^2,1^2,-1^2,2^2,-2^2....k^2(k<=m/2)

      • 双散列法 di=i*hash2(key),hash2(key)为另一散列函数

      • 伪随机序列法 di是一个伪随机序列,如:d=0,5,3,11.......

    • 删除时应该在删除位置上标志删除标记

散列函数构造法:

设计注意:

1.定义域必须涵盖所有可能出现的关键字。

2.值域不能超出散列表的地址范围。

3.尽可能减少冲突。散列函数机算出来的地址应尽可能均匀分布在整个地址空间。

4.散列函数应尽量简单,能够快速计算出任意一个关键字对应的散列地址。

  • 除留余数法:H(key)=key%p

    • 散列表表长为m,取一个不大于m但最接近或等于m的质数p

    • 适用场景:较为通用,只要关键字是整数即可。

  • 直接定址法:H(key)=key或H(key)=a*key+b

    • 其中,a和b是常数。这种方法计算最简单,且不会产生冲突。若关键字分布不连续,空位较多,则会造成存储空间的浪费。

    • 适用场景:关键字分布基本连续。

  • 数字分析法:选取数码分布较为均匀的若干位作为散列地址

    • 设关键字是r进制数,而r个数码在各位上出现的频率不一定相同,可能在某些位上分布均匀一些,每种数码出现的机会均等;而在某些位上分布不均匀,只有某几种数码经常出现,此时可选取数码分布较为均匀的若干位作为散列地址。

    • 适用场景:关键字集合已知,且关键字的某几个数码位分布均匀

  • 平方取中法:取关键字的平方值的中间几位作为散列地址。

    • 具体取多少位要视情况而定。这种方法得到的散列地址与关键字的每位都有关系,因此使得散列地址分布比较均匀。

    • 适用场景:关键字的每位取值都不够均匀。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值