8.查找
8.1查找概论
查找表(Search table) 是由同一类型的数据元素(或记录)构成的集合
关键字(key)是数据元素中某个数据项的值,又称为键值
若此关键字可以唯一地标识一个记录,则称此关键字为主关键字(primary key)
对于那些可以识别多个数据元素(或记录)的关键字,我们称为次关键字(sencondary key)
查找就是根据给定的某个值,在查找表中确定一个其关键字等于给定值的数据元素
查找表按照操作方式分有两种,静态查找表和动态查找表
静态查找表:只作查找操作的查找表,主要操作有
- 查询某个"特定的"数据元素是否在查找表中
- 检索某个"特定的"数据元素和各种属性
动态查找表:在查找过程中同时插入查找表中不存在的数据元素,或者从查找表中删除已经存在的某个数据元素。显然动态查找表的操作就是两个:
(1)查找时插入数据元素。
(2)查找时删除数据元素。
8.2顺序表查找
顺序查找(Sequential Search)又叫线性查找,是最基本的查找技术,它的查找过程是:从表中第一个(或最后一个)记录开始,逐个进行记录的关键字和给定值比较,若某个记录的关键字和给定值相等,则查找成功,找到所查的记录;如果直到最后一个(或第一个)记录,其关键字和给定值比较都不等时,则表中没有所查的记录,查找不成功。
顺序表查找算法
//顺序查找,a为数组,n为要查找的数组个数,key为要查找的关键字
int Sequential_Search(int *a,int n,int key)
{
int i;
for(i=1;i<=n;i++)
{
if(a[i]==key)
return i;
}
return 0;
}
顺序表查找优化
//有哨兵顺序查找
int Sequential_Search2(int *a,int n,int key)
{
int i;
a[0]=key; //设置a[0]为关键字值,称为“哨兵”
i=n; //循环从数组尾部开始
while(a[i]!=key)
{
i--;
}
return i; //返回0则说明查找失败
}
8.3有序表查找
折半查找
折半查找(Binary Search)技术,又称为二分查找。它的前提是线性表中的记录必须是关键码有序(通常从小到大有序),线性表必须采用顺序存储。折半查找的基本思想是:在有序表中,取中间记录作为比较对象,若给定值与中间记录的关键字相等,则查找成功;若给定值小于中间记录的关键字,则在中间记录的左半区继续查找;若给定值大于中间记录的关键字,则在中间记录的右半区继续查找。不断重复上述过程,直到查找成功,或所有查找区域无记录,查找失败为止
假设我们现在有这样一个有序表数组{0,1,16,24,35,47,59,62,73,88,99},除0下标外共10个数字。对它进行查找是否存在62这个数。我们来看折半查找的算法是如何工作的。
int Binary_Search(int *a,int n,int key)
{
int low,high,mid;
low=1; //定义最低下标为记录首位
high=n; //定义最高下标为记录末位
while(low<=high)
{
mid=(low+high)/2; //折半
if(key<a[mid]) //若查找值比中值小
high=mid-1; //最高下标调整到中位下标小一位
else if(key>a[mid]) //若查找值比中值大
low=mid+1; //最低下标调整到中位下标大一位
else
return mid; //若相等则说明mid即为查找到的位置
}
return 0;
}
插值查找
8.4线性索引查找
索引就是把一个关键字与它对应的记录相关联的过程
线性索引技术将索引项集合组织为线性结构,也称为索引表
稠密索引
指在线性索引中,将数据集中的每个记录对应一个索引项
对于稠密索引这个索引表来说,索引项一定是按照关键码有序的排列
分块索引
分块有序,是把数据集的记录分成若干块,并且这些块需要满足两个条件
- 块内无序
- 块间有序
对于分块有序的数据集,将每块对应一个索引项,这种索引方法叫做分块索引。如图所示,我们定义的分块索引的索引项结构分三个数据项:
-
最大关键码,它存储每一块中的最大关键字,这样的好处就是可以使得在它之后的下一块中的最小关键字也能比这一块最大的关键字要大;
-
存储了块中的记录个数,以便于循环时使用;
-
用于指向块首数据元素的指针,便于开始对这一块中记录进行遍历。
8.5二叉排序树
二叉排序树(Binary Sort Tree),又称为二叉查找树。它或者是一棵空树,或者是具有下列性质的二叉树。
- 若它的左子树不空,则左子树上所有结点的值均小于它的根结构的值;
- 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值;
- 它的左右子树也分别为二叉排序树
//二叉树的二叉链表结点结构定义
typedef struct BiTNode //结点结构
{
int data; //结点数据
struct BiTNode *lchild,*rchild; //左右孩子指针
}BiTNode,*BiTree;
二叉排序树查找实现
/*递归查找二又排序树T中是否存在key,*/
/*指针f指向T的双亲,其初始调用值为NULL*/
/*若查找成功,则指针p指向该数据元素结点,并返回TRUE*/
/*否则指针p指向查找路径上访问的最后一个结点并返回FALSE*/
Status SearchBST ( BiTree T ,int key,BiTree f,BiTree *p)
{
if(!T)/*查找不成功*/
{
*p=f;
return FALSE;
}
else if(key=-T->data)/*查找成功*/
{
*p=T;
return TRUE;
}
else if (key<T->data)
return SearchBST(T->1child,key,T,p);/*在左子树继续查找*/
else
return SearchBST(T->rchild,key,T,p);/*在右子树继续查找*/
}
8.6散列表查找(哈希表)概述
散列表查找定义
存储位置=f(关键字)
散列技术是在记录的存储位置和它的关键字之间建立一个确定的对应关系f,使得每个关键字key对应一个存储位置f(key)。
对应关系f称为散列函数,又称为哈希(hash)函数。
采用散列技术将记录存储在一块连续的存储空间中,这块连续存储空间称为散列表或哈希表
散列表查找步骤
散列技术既是一种存储方法,也是一种查找方法
散列技术最适合的求解问题是查找与给定值相等的记录
我们时常会碰到两个关键字key1=/key2,但是却有f(key1)=f(key2),这种现象我们称为冲突(collision),并把key1和key2称为这个散列函数的同义词(synonym)。
8.7散列函数的构造方法
好的散列函数两个原则
- 计算简单
- 散列地址分布均匀
直接地址法
取关键字的某个线性函数值为散列地址
f(key)=a x key+b(a,b为常数)
数字分析法
平方取中法
折叠法
除留余数法
随机数法
8.8处理散列冲突的方法
开放定址法
再散列函数法
链地址法
公共溢出区法
8.9散列表查找实现
算法实现
定义散列表结构以及相关常数
#define success 1
#define unsuccess 0
#define hashsize 12 //定义散列表长为数组的长度
#define nullkey -32768
typedef struct
{
int *elem; //数据元素存储基址,动态分配数组
int count; //当前数据元素个数
}hashtable;
int m=0; //散列表表长,全局变量
散列表初始化
/*初始化散列表*/
Status InitHashTable(HashTable *H)
{
int i;
m=HASHSIZE;
H->count=m;
H->elem=(int *)malloc(m*sizeof(int));
for(i=0;i<m;i++)
H->elem[i]=NULLKEY;
return OK;
}
定义散列函数
//散列函数
int Hash(int key)
{
return key % m;//除留余数法
}
插入操作
/*插入关键字进散列表*/
void InsertHash(HashTable*H,int key)
{
int addr-Hash(key);/*求散列地址*/
while(H->elem[addr]!=NULLKEY)/*如果不为空,则冲突*/
addr-(addr+1)%m;/*开放定址法的线性探测*/
H->elem[addr]=key;/*直到有空位后插入关键字*/
}
查找
/*散列表查找关键字*/
Status SearchHash (HashTable H,int key,int*addr)
{
*addr=Hash(key);/*求散列地址*/
while(H.elem[*addr]!=key)/*如果不为空,则冲突*/
{
*addr=(*addr+1)%m;/*开放定址法的线性探测*/
if (H.elem[*addr]==NULLKEY || *addr==Hash(key))
{
/*如果循环回到原点*/
return UNSUCCESS ;/*则说明关键字不存在*/
}
}
return SUCCESS;
}
性能分析