数据结构之查找

前言

查找的基本方法分为两大类:比较式查找法计算式查找法
比较式查找法根据数据元素的组织结构分为:基于线性表的查找法基于树的查找法
计算式查找法也称为哈希查找法

比较式查找法

基于线性表的查找法

基于线性表的查找法具体可分为:顺序查找法折半查找法以及分块查找法
1.顺序查找法
顺序查找法的特点是,用所给关键字与线性表中各元素的关键字逐个比较,直到成功或失败。存储结构通常为顺序结构,也可为链式结构。
顺序结构数据类型的定义:

#define LIST_SIZE 20
typedef struct
{
	KeyType key;
	OtherType other_data;
}RecordType;
typedef struct
{
	RecordType r[LIST_SIZE+1];		/*r[0]为工作单元*/
	int length;
}RecordList;

(1)设置监视哨的顺序查找法

int SeqSearch(RecordList l,KeyType k)
{
	l.r[0].key=k;i=l.length;
	while(l.r[i].key!=k)
		i--;
	return(i);
}

(2)不设置监视哨的顺序查找法

int SeqSearch(RecordList l,KeyType k)
{
	i=l.length;
	while(i>=1&&l.r[i].key!=k)
		i--;
	if(i>=1)
		return(i);
	else 
		return 0;
}

2.折半查找法
折半查找法又称为二分查找法,这种方法对待查找的列表有两个要求:
(1)必须采用顺序存储结构
(2)必须按关键字大小有序排列

int BinSrch(RecordList l,KeyType k)
{
	low=1;
	high=l.length;
	while(low<=high)
	{
		mid=(low+high)/2;
		if(k==l.r[mid].key)
			return(mid);
		else if(k<l.r[mid].key)
			high=mid-1;
		else 
			low=mid+1;
	}
	return 0;
}

3.分块查找法
在这里插入图片描述
先将列表组织成以下两种索引顺序结构。
(1)首先将列表分成若干个块(子表)。一般情况下,块的长度均匀,最后一块可以不满。每块中元素任意排列,即块内无序,但块与块之间有序。
(2)构造一个索引表。其中每个索引项对应一个块并纪录每块的起始位置,以及每块中的最大关键字(或最小关键字)。索引表按关键字有序排列。
分块查找的基本过程如下:
(1)首先,将待查关键字k与索引表中的关键字进行比较,以确定待查记录所在的块。具体的可用顺序查找法或折半查找法进行。
(2)进一步用顺序查找法,在相应块内查找关键字为k的元素。

基于树的查找法

基于树的查找法是将待查表组织成特定树的形式并在树结构上实现查找的方法,故又称为树表式查找法,主要包括二叉排序树、平衡二叉排序树和B树等
1.二叉排序树
二叉排序树又称为二叉查找树,它是一种特殊的二叉树。其定义为:二叉排序树或者是一颗空树,或者是具有如下性质的二叉树。
(1)若它的左子树非空,则左子树上所有结点的值均小于根结点的值。
(2)若它的右子树非空,则右子树上所有结点的值均大于(或大于等于)根结点的值。
(3)它的左右子树也分别为二叉排序树。
二叉排序树的结点结构:

typedef struct node
{
	KeyType key;					/*关键字的值*/
	struct node *lchild,*rchild;	/*左右指针*/
}BSTNode,*BSTree;

(1)二叉排序树的插入

void InsertBST(BSTree *bst,KeyType key)
{
	BiTree s;
	if(*bst==NULL)
	{
		s=(BSTree)malloc(sizeof(BSTNode));
		s->key=key;
		s->lchild=NULL;s->rchild=NULL;
		*bst=s;
	}
	else if(key<(*bst)->key)
		InsertBST(&((*bst)->lchild),key);
	else if(key>(*bst)->key)
		InsertBST(&((*bst)->rchild),key);
}

(2)创建二叉排序树

void CreateBST(BSTree *bst)
{
	KeyType key;
	*bst=NULL;
	scanf("%d",&key);
	while(key!=ENDKEY)
	{
		InsertBST(bst,key);
		scanf("%d",&key);
	}
}

(3)二叉排序树查找的递归算法

BSTree SearchBST(BSTree bst,KeyType key)
{
	if(!bst)
		return NULL;
	else if(bst->key==key)
		return(bst);
	else if(bst->key>key)
		return SearchBST(bst->lchild,key);
	else
		return SearchBST(bst->rchild,key);
]

(4)二叉排序树查找的非递归算法

BSTree SearchBST(BSTree bst,KeyType key)
{
	BSTree q;
	q=bst;
	while(q)
	{
		if(q->key==key)
			return q;
		else if(q->key>key)
			q=q->lchild;
		else
			q=q->rchild;
	}
	return NULL;                
}

(5)二叉排序树的删除
在这里插入图片描述
基本思想:删除操作首先查找要删除的结点,看是否在二叉排序树中,若不在则不做任何操作;否则,假设要删除的结点为p,结点p的双亲结点为f,并假设结点p是结点f的左孩子。下面分三种情况讨论。
情况一:若p为叶结点,则可直接将其删除:
f->lchild=NULL;free§
情况二:若p结点只有左子树,或只有右子树,则将p的左子树或右子树,直接改为其双亲结点f的左子树。即:f->lchild=p->lchild(或 f->lchild=p->rchild);free§。
情况三:若p既有左子树,又有右子树,可以首先找到p结点在中序序列中的直接前驱s结点,然后用s结点的值代替p结点的值,再将s结点删除,原s结点的左子树改为s的双亲结点q的右子树:
p->data=s->data;q->rchild=s->lchild;free(s);
算法如下:

 BSTNode *DelBST(BSTree t,KeyType k)
 {
 	BSTNode *p,*f,*s,*q;
 	p=t;f=NULL;
 	while(p)
 	{
 		if(p->key==k) break;
 		f=p;
 		if(p->key>k)  p=p->lchild;
 		else p=p->rchild;
 	}
 	if(p==NULL) return t;
 		if(p->lchild==NULL)
 		{
 			if(f==NULL) t=p->rchild;
 			else if(f->lchild==p)
 				f->lchild=p->rchild;
 			else
 				f->rchild=p->rchild;
 			free(p);
 		}
 		else
 		{
 			q=p;s=p->lchild;
 			while(s->rchild)
 			{q=s;s=s->rchild;}
 			if(q==p) q->lchild=s->lchild;
 			else q->rchild=s->lchild;
 			p->key=s->key;
 			free(s);
 		}
 		return t;
 }

2.平衡二叉排序树
平衡二叉排序树又称AVL树。一颗平衡二叉树或者是空树,或者是具有下列性质的二叉排序树。
(1)左子树和右子树的高度之差的绝对值小于等于1。
(2)左子树和右子树也是平衡二叉排序树。
引入平衡二叉排序树的目的:为了提高查找效率,其平均查找长度为O(log2n)。
3.B树
B树:
在这里插入图片描述
一颗B树是一颗平衡的m路查找树,它或者是空树,或者是满足如下性质的树:
(1)树中每个结点最多有m颗子树。
(2)根结点至少有两颗子树。
(3)除根结点之外的所有非叶结点至少有[m/2]颗子树。
(4)所有叶结点出现在同一层上,并且不含信息,通常称为失败结点。失败结点为虚结点,在B树中并不存在,指向它们的指针为空指针。引入失败结点是为了便于分析B树的查找性能。
B+树:
在这里插入图片描述
在B+树中,所有纪录结点都是按键值的大小顺序存放在同一层的叶结点中,各叶结点指针进行连接。
B+树比较B树的优点:
(1)单一结点储存更多的元素。
(2)所有查询都要查找到叶子结点,查询性能稳定。
(3)所有叶子结点形成有序链表,便于范围查询。
注: B树中每个结点的每个关键字都有卫星数据,B+树中间结点没有卫星数据,只有索引。所以B树的查找只需找到匹配元素即可,最好情况查找到根结点,最坏情况下查找到叶子结点,所以性能很不稳定。而B+树每次必须查找到叶子结点,性能稳定。

哈希查找法

基本思想: 首先在元素的关键字k和元素的存储位置p之间建立一个对应关系H,使得p=H(k),H称为哈希函数。创建哈希表时,把关键字为k的元素直接存入地址为H(k)的单元;以后当查找关键字为k的元素时,再利用哈希函数计算出该元素的存储位置p=H(k),从而达到按关键字直接存取元素的目的。可见,哈希法是计算式查找的方法。
要解决的问题: 如何构造哈希函数和如何处理冲突。
1.哈希函数的构造方法
构造哈希函数原则:一是函数本身便于计算;二是计算出来的地址分布均匀,即对任一关键字k,H(k)对应不同地址的概率相等,目的是尽可能减少冲突。
构造哈希函数常用的五种方法:
(1)数字分析法
事先要知道关键字集合,并且每个关键字的位数比哈希表的地址码位数多时,可以从关键字中选出分布较均匀的若干位,构成哈希地址。
例如:有80个记录,关键字为8位十进制整数 d1 d2 d3 …d7 d8,如哈希表长度取为100,则哈希表的地址空间为0-99.假设经过分析,各关键字中d4和d7的取值分布较均匀,则哈希函数为H(key)=H(d1d2…d7d8)=d4d7.
(2)平方取中法
当无法确定关键字中哪几位分布较均匀时,可以先求出关键字的平方值,然后按需要取平方值的中间几位作为哈希地址。
(3)分段叠加法
按哈希表地址位数将关键字分成位数相等的几部分(最后一部分可以较短),然后将这几部分相加,舍弃最高进位后的结果就是该关键字的哈希地址。
具体方法:折叠法和移位法。折叠法是从一端向另一端沿分割界来回折叠(奇数段为正序,偶数段为倒序),然后将各段相加。
(4)除留余数法
假设哈希表长为m,p为小于等于m的最大素数,则哈希函数为 H(k)=k%p 其中%为模p取余运算。(冲突较多的话,取较大的m值和p值)
(5)伪随机数法
采用一个伪随机函数作为哈希函数,即H(key)=random(key)。
2.处理冲突的办法
常用的解决冲突方法有以下四种。
(1)开放定址法
当关键字key的初始哈希地址h0=H(key)出现冲突时,以h0为基础,产生另一个地址h1,如果h1仍然冲突,再以h0为基础,产生另一个哈希地址h2…直到找出一个不冲突的地址hi,将相应元素存入其中。
通用的再散列函数形式为:hi=(H(key)+di)%m i=1,2…n(m为表长,di称为增量序列)
(2)再哈希法
同时构造多个不同的哈希函数:Hi=RHi(key) i=1,2…k
当哈希地址H1=RH1(key)发生冲突时,再计算H2=RH2(key)…直到冲突不再产生。
(3)链地址法
将所有哈希地址为i的元素构成一个称为同义词链的单链表,并将单链表的头指针存在哈希表的第i个单元中,因而查找、插入和删除主要在同义词链中进行。链地址法适用于经常进行插入和删除的情况。
(4)建立公共溢出区
将哈希表分为基本表和溢出表两部分,凡是和基本表发生冲突的元素一律填入溢出表。
哈希表的查找算法

int HashSearch(HashTable ht,KeyType k)
{
	h0=hash[k];
	if(ht[h0].key==NULLKEY)	return -1;
	else if(ht[h0].key==k)	return(h0);
	else
	{
		for(i=1;i<=m-1;i++)
		{
			hi=(h0+i)%m;
			if(ht[hi].key==NULLKEY)	return -1;
			else if(ht[hi].key==k)	return(hi);
		}
		return -1;
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值