数据结构——查找

查找

线性表的查找

  1. 顺序查找(线性查找)
  • 顺序表或线性链表表示的静态查找表

  • 元素之间无序

typedef struct{
	KeyType key;//关键字域
    ...         //其他域
}ElemType;

typedef struct{//顺序表结构类型定义
	ElemType *R; //表基址
    int length;  //表长
}SSTable;
SSTable ST;//定义顺序表ST

在顺序表ST中查找值为key的数据元素,从最后一个元素开始比较

例如: 查找13: 找到,返回位置5
image

int Search_Seq(SSTable ST,KeyType key)
{
   for(i=ST.length; i>=1;--i)//每次循环比较两次
   {
   	if(ST.R[i].key==key) return i;
   }
   return 0;
}

其他形式

int Search_Seq(SSTable ST,KeyType key)
{
    for(i = ST.length;ST.R[i].key != key && i>0; --i);//每次循环比较两次
    if(i > 0) return i;
    else return 0;
}

含监视哨的等概率查找(只比较一次)

int Search_Seq(SSTable ST,KeyType key)
{
    ST.R[0].key = key;//存入下标为0的位置
    for(i = ST.length;ST.R[i].key != key; --i);
    return i;
}

时间复杂度:O(n),查找成功时的平均查找长度,ASL(n)=(1+2...+n)/n=(n+1)/2

空间复杂度:O(1)

优点:算法简单,逻辑次序无要求(无序也可),不同存储结构均适用

缺点:ASL太长,时间效率太低

  1. 折半查找(二分或对分查找)
  • 是有序表的静态查找
  • 每次将待查找记录所在区间缩小一半
    image
    算法步骤
    image

折半查找(非递归算法)

int Search_Bin(SSTable ST,KeyType key)
{
 	low = 1;hight = ST.length;
 	while(low<=high)
    {
        mid = (low+hight)/2;向下取整
        if(ST.R[mid].key==key) return mid;//找到待查元素
        else if(key<ST.R[mid].key) high=mid-1;//继续在前半区查找
        else low = mid+1;//继续在后半区查找
    }
    return 0;//顺序表中不存在待查元素时low>high
}

折半查找(递归)

int Search Bin(SSTable ST,keyType key,int low, int high)
{
    if(low>high) return 0;
    mid = (low+high)/2;
    if(key==ST.elem[mid].key) return mid;
    else if(key<ST.elem[mid].key)
        ...//递归,在前半区查
    else
        ...//递归在后半区查
}

判定树分析折半查找性能

image
image

  • 时间复杂度:O(log(2)n)对数级

优点:效率比顺序查找高

缺点:只适用于有序表,且限于顺序存储结构

  1. 分块查找(索引顺序表的查找)

分块查找

条件:把表分块,且表 或有序,或分块有序(块内部无序)
image
image
性能分析及例题
image

说明:这里的折半法计算结果为log(2)(n+1)=log(2)10

优点:插入和删除容易,无需进行大量移动

缺点:要增加一个索引表的存储空间并对初始索引表进行排序

适用于既要快速查找又要动态变更的线性表

查找方法比较

image

树表的查找

概念

对插入删除操作频繁,为维护表有序性

改用动态查找表——几种特殊的树,表结构在查找过程中动态生成

对于给定值key

若表中存在,成功返回

否则,插入关键字等于key的记录

二叉排序树(二叉搜索树、二叉查找树)

  • 空树

    1. 左子树非空,则左子树上的所有结点的值均小于根节点的值
    2. 右子树非空,则右子树上的所有结点的值均大于等于根节点的值
    3. 左右子树本身又是二叉排序树

性质:

​ 中序遍历二叉排序树得到的数据元素序列是一个按关键字排列的递增有序序列

存储结构

typedef struct{
    KeyType key; //关键字项
    InfoType otherinfo;//其他数据域
}ElemType;

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

BSTree T;//定义二叉排序树T

递归算法

BSTree SearchBST(BSTree T,KeyType key)//查找成功返回结点地址,
    //若二叉树为空,查找不成功,返回空指针
{
    if((!T)||key==T->data.key) return T;
    else if(key < T->data.key)
        return SearchBST(T->lchild,key);//在左子树中继续查找
    else return SearchBST(T->rchild,key);//在右子树中继续查找
}

复杂度(效率)分析
image

ps:可以用平衡二叉树提升最差情况下的效率

插入

插入的元素一定在叶子结点上

操作
image

生成

image

删除

  1. 被删除的结点是叶子结点:直接删去该节点

(其双亲节点中对应指针域值改为“空”)

  1. 被删除的结点只有左子树,用左子树的根节点替换它;

​ 只有右子树,用右子树的根节点替换它

(其双亲结点指针域改为“指向被删除结点的左子树或右子树”)

  1. 被删除的结点既有左子树也有右子树
  • 以其中序前驱的值替换被删除结点的值,然后删除该前驱结点(前驱是左子树中的最大结点),删除时也要判断是1,2,3中的哪种情况

  • 以其中序后继的值替换被删除结点的值,然后删除该后继结点(后继是右子树中的最小结点),删除时也要判断是1,2,3中的哪种情况

3中两方法得到的取树的高度较小者为佳,查找效率更好

平衡二叉树

定义

  • 又称AVL树

  • 性质:

  1. 左子树与右子树的高度之差绝对值<=1
  2. 左子树和右子树也是二叉平衡树

定义平衡因子BF(结点左子树高度减右子树的高度),只可能-1,1,0三个取值,以判断平衡二叉树

image

平衡调整方法

有多个平衡因子时,对最小失衡子树操作,应当为几个失衡树中包含结点个数最少的那个小树

失衡情况的四种类型:
image
调整的四种方法:
image

LL型调整过程

B结点带左子树α一起上升

A结点成为B结点的右孩子

原来B结点的右子树β作为A的左子树

image

RR型调整过程

B结点带右子树β一起上升

A结点成为B结点的左孩子

原来B结点的左子树α作为A的右子树

image

LR型调整过程

  • C结点穿过A、B结点上升

  • B结点成为C的左孩子(B直观就在左边)

    A结点成为C的右孩子

  • 原来C的左子树β作为B的右子树(这里其实相当于在同层按顺序放置,先填满B右后A左)

    原来C的右子树γ作为A的左子树

image

RL型调整过程与上面类似,依然要满足二叉排序树性质
image

应用例题

构造平衡二叉树时要实时进行平衡判断,以得到最终的AVL(平衡二叉)树

散列表(哈希表)的查找

概念

存储位置与关键字之间存在对应关系——hash函数

散列(Hashing)是一种重要的查找方法。它的基本思想是:以数据对象的关键字key为自变量,通过一个确定的函数关系h,计算出对应的函数值h(key),把这个值解释为数据对象的存储地址,并按此存放,即“存储位置=h(key)”。

优点:查找效率高

缺点:空间效率低

散列表的定义

在查找数据对象时,由函数h对给定值key计算出地址,将key与该地址单元中数据对象关键字进行比较,确定查找是否成功。因此,散列法又称为“关键字-地址转换法”。散列方法中使用的计算函数称为散列函数(也称哈希函数),按这个思想构造的表称为散列表,所以它是一种存储方法。

散列函数用于将键值经过处理后转化为散列值。具有以下特性:

  • 散列函数计算得到的散列值是非负整数
  • 如果 key1 == key2,则 hash(key1) == hash(key2)
  • 如果 key1 != key2,则 hash(key1) != hash(key2)

散列冲突,简单来说,指的是 key1 != key2 的情况下,通过散列函数处理,hash(key1) == hash(key2),这个时候,我们说发生了散列冲突。设计再好的散列函数也无法避免散列冲突,原因是散列值是非负整数,总量是有限的,但是现实世界中要处理的键值是无限的,将无限的数据映射到有限的集合,肯定避免不了冲突。

同义词:具有相同函数值的多个关键字(他们互相之间属于散列冲突的情况)

散列函数构造方法

要求:

​ 需要的空间尽可能小;存放均匀;有好的冲突处理方法

直接定址法

Hash(key) = a*key + b(a、b为常数)

优点:以关键字key的某个线性函数值为散列地址,不会产生冲突

缺点:要占用连续地址空间,空间效率低

数字分析法

平方取中法

折叠法

除留余数法

Hash(key) = key mod p

关键:如何取合适的p?

技巧:设表长为m,取p<=m且为质数

随机数法

处理冲突的方法

1. 开放定址法(开地址法)

基本思想:有冲突就去寻找下一个空的散列地址

例如:除留余数法Hi=(Hash(key)+di)mod m di为增量序列

常用方法:

​ 线性探测法 di为1,2,...m-1线性序列
image
​ 二次探测法 di为12,-12,22,-22...二次序列
image

​ 伪随机探测法 di为伪随机数序列

2. 链地址法(拉链法)

基本思想:相同散列地址的记录链成一单链表

m个不同散列地址就设m个单链表,然后用一个数组(左侧纵向)将m个单链表的表头指针储存起来
image

优点:只有同义词才会冲突,处理方法为把相同散列地址的用单链表串起来;非同义词不会冲突,无“聚集”现象

空间可以动态申请,适合表长不定的情况

例题
线性探测法
image
链地址法处理冲突
image

3. 再散列法(双散列函数法)

4.建立一个公共溢出区

散列表查找效率分析

image
image

几点结论

image

posted @ 2021-12-09 17:34  群青Bleu  阅读(147)  评论(0编辑  收藏  举报