数据结构——查找
查找
线性表的查找
- 顺序查找(线性查找)
-
顺序表或线性链表表示的静态查找表
-
元素之间
无序
typedef struct{
KeyType key;//关键字域
... //其他域
}ElemType;
typedef struct{//顺序表结构类型定义
ElemType *R; //表基址
int length; //表长
}SSTable;
SSTable ST;//定义顺序表ST
在顺序表ST中查找值为key的数据元素,从最后一个元素开始比较
例如: 查找13: 找到,返回位置5
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太长,时间效率太低
- 折半查找(二分或对分查找)
- 是有序表的静态查找
- 每次将待查找记录所在区间缩小一半
算法步骤
折半查找(非递归算法)
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
...//递归在后半区查
}
判定树分析折半查找性能
- 时间复杂度:O(log(2)n)对数级
优点:效率比顺序查找高
缺点:只适用于有序表,且限于顺序存储结构
- 分块查找(索引顺序表的查找)
分块查找
条件
:把表分块,且表 或有序,或分块有序(块内部无序)
性能分析及例题
说明:这里的折半法计算结果为log(2)(n+1)=log(2)10
优点:插入和删除容易,无需进行大量移动
缺点:要增加一个索引表的存储空间并对初始索引表进行排序
适用于
既要快速查找又要动态变更的线性表
查找方法比较
树表的查找
概念
对插入删除操作频繁,为维护表有序性
改用
动态查找表
——几种特殊的树,表结构在查找过程中动态生成对于给定值key
若表中存在,成功返回
否则,插入关键字等于key的记录
二叉排序树(二叉搜索树、二叉查找树)
-
空树
-
- 左子树非空,则左子树上的
所有
结点的值均小于
根节点的值 - 右子树非空,则右子树上的
所有
结点的值均大于等于
根节点的值 - 左右子树本身又是二叉排序树
- 左子树非空,则左子树上的
性质:
中序遍历二叉排序树得到的数据元素序列是一个按关键字排列的
递增
有序序列
存储结构
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);//在右子树中继续查找
}
复杂度(效率)分析
ps:可以用平衡二叉树提升最差情况下的效率
插入
插入的元素一定在
叶子
结点上
操作
生成
删除
- 被删除的结点是叶子结点:直接删去该节点
(其双亲节点中对应指针域值改为“空”)
- 被删除的结点只有左子树,用左子树的根节点替换它;
只有右子树,用右子树的根节点替换它
(其双亲结点指针域改为“指向被删除结点的左子树或右子树”)
- 被删除的结点既有左子树也有右子树
-
以其中序前驱的值替换被删除结点的值,然后删除该前驱结点(前驱是左子树中的最大结点),删除时也要判断是1,2,3中的哪种情况
-
以其中序后继的值替换被删除结点的值,然后删除该后继结点(后继是右子树中的最小结点),删除时也要判断是1,2,3中的哪种情况
3中两方法得到的取树的高度较小者为佳,查找效率更好
平衡二叉树
定义
-
又称AVL树
-
性质:
- 左子树与右子树的高度之差绝对值<=1
- 左子树和右子树也是二叉平衡树
定义平衡因子BF(结点左子树高度减右子树的高度),只可能-1,1,0三个取值,以判断平衡二叉树
平衡调整方法
有多个平衡因子时,对最小失衡子树操作,应当为几个失衡树中包含结点个数最少的那个小树
失衡情况的四种类型:
调整的四种方法:
LL型调整过程
:
B结点带左子树α一起上升
A结点成为B结点的右孩子
原来B结点的右子树β作为A的左子树
RR型调整过程
:
B结点带右子树β一起上升
A结点成为B结点的左孩子
原来B结点的左子树α作为A的右子树
LR型调整过程
:
C结点穿过A、B结点上升
B结点成为C的左孩子(B直观就在左边)
A结点成为C的右孩子
原来C的左子树β作为B的右子树(这里其实相当于在同层按顺序放置,先填满B右后A左)
原来C的右子树γ作为A的左子树
RL型调整过程与上面类似,依然要满足二叉排序树性质
:
应用例题
构造平衡二叉树时要实时进行平衡判断,以得到最终的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线性序列
二次探测法 di为12,-12,22,-22...二次序列
伪随机探测法 di为伪随机数序列
2. 链地址法(拉链法)
基本思想:相同散列地址的记录链成一单链表
m个不同散列地址就设m个单链表,然后用一个数组(左侧纵向)将m个单链表的表头指针储存起来
优点:只有同义词才会冲突,处理方法为把相同散列地址的用单链表串起来;非同义词不会冲突,无“聚集”现象
空间可以动态申请,适合表长不定的情况
例题
线性探测法
链地址法处理冲突
3. 再散列法(双散列函数法)
4.建立一个公共溢出区
散列表查找效率分析