数据结构第9章 查找

查找的基本概念

查找表:是由一组记录组成的表或文件,而每个记录由若干个数据项组成,并假设每个记录都有一个能唯一标识该记录的关键字。
关键字:记录中某个数据项的值,可用来识别一个记录;
主关键字:唯一标识数据元素;
次关键字:可以标识若干个数据元素;
若整个查找过程都在内存进行,则称之为内查找
若查找过程中需要访问外存,则称之为外查找
若在查找的同时对表做修改操作(如插入和删除),则相应的表称之为动态查找表;否则称之为静态查找表。

静态表查找

顺序查找

int SeqSearch(SqList L,KeyType k)
{  int i=L.length;
   while (i>0 && L.R[i].key!=k) //从表尾往前找
           i--;
   if (i<=0)	
          return 0; //未找到返回0
   else  
          return i;	//找到返回逻辑序号i
}

改进:把待查关键字key存入表头(“监视哨”),从后向前逐个比较,可免去查找过程中每一步都要检测是否查找完毕,加快速度

int SeqSearch(SqList L,KeyType k)
{     int i=L.length;  
      L.R[0].key=k;
      while (L.R[i].key!=k)	 //从表尾往前找	       
            i--;
      return i;	
}

成功情况下的平均查找长度:
A S L = ∑ i = 1 n p i c i = 1 n ∑ i = 1 n i = 1 n × n ( n + 1 ) 2 = n + 1 2 ASL=\sum_{i=1}^{n} p_{i} c_{i}=\frac{1}{n} \sum_{i=1}^{n} i=\frac{1}{n} \times \frac{n(n+1)}{2}=\frac{n+1}{2} ASL=i=1npici=n1i=1ni=n1×2n(n+1)=2n+1
不成功情况下的平均查找长度 A S L = n + 1 ASL=n+1 ASL=n+1

二分查找(折半查找)——有序表的查找

若k==R[mid].key,查找成功
若k<R[mid].key,则high=mid-1
若k>R[mid].key,则low=mid+1
若low>high,查找失败

非递归算法

int Search_Bin( SqList L , KeyType key )
{	//若找到,则函数值为该元素在表中的逻辑序号,否则为0
     int low=1 , high=L.length , mid;
     while(low<=high)
     {
          mid=( low + high )/2;
          if( key == L.R[mid].key) 
                 return mid; 
           else if( key < L.R[mid].key) 
                 high=mid-1;           //修改上界
           else    low=mid+1;      //修改下界
      }	
      return 0;		//表中不存在待查元素
}							

递归算法

int Search_Bin (SqList L, keyType key, int low, int high) 
{     int mid=(low+high)/2; 
      if(low<=high)
      {
            if(key == L.R[mid].key)  return mid; 
            else if( key < L.R[mid].key)  
                   return Search_Bin (L, key, low, mid-1);
             else   
                  return Search_Bin (L, key, mid+1,high);
       }
       else
             return 0;   //查找不到时返回0
}

动态表查找(树表查找)

树表:以二叉树或树作为表的组织形式,称为树表,它是一类动态查找表,不仅适合于数据查找,也适合于表插入和删除操作。

二叉排序树

二叉排序树或是空树,或是满足如下性质的二叉树:

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

在二叉排序树bt上查找关键字为k的记录,成功时返回该结点指针,否则返回NULL

BSTNode *SearchBST(BSTNode *bt,KeyType k)
{ 
      if (bt==NULL || bt->key==k) 	            //递归出口
            return bt;
      if (k<bt->key)
        return SearchBST(bt->lchild,k);   //在左子树中递归查找
      else
        return SearchBST(bt->rchild,k);   //在右子树中递归查找
}

在二叉排序树中插入一个关键字为k的新结点,要保证插入后仍满足BST性质。

int InsertBST(BSTNode *&p,KeyType k)	
{    if (p==NULL)	 //原树为空, 新插入的记录为根结点
     {     p=(BSTNode *)malloc(sizeof(BSTNode));
            p->key=k;p->lchild=p->rchild=NULL;
            return 1;
      }
      else if  (k==p->key) 	//存在相同关键字的结点,返回0
           return 0;
      else if (k<p->key) 
          return InsertBST(p->lchild,k); 	//插入到左子树中
      else  
          return InsertBST(p->rchild,k);  	//插入到右子树中
 }

平衡二叉树(AVL)

若一棵二叉树中每个结点的左、右子树的高度至多相差1,则称此二叉树为平衡二叉树(AVL)。
如果在一棵AVL树中插入一个新结点,就有可能造成失衡,此时必须重新调整树的结构,使之恢复平衡。我们称调整平衡过程为平衡旋转。调整操作可归纳为4种情况。

  1. LL平衡旋转
  2. RR平衡旋转
  3. LR平衡旋转
  4. RL平衡旋转

LL型调整

在这里插入图片描述

调整过程:

  1. A结点成为B的右孩子
  2. 原来B结点的右子树β作为A的左子树

LR型调整

在这里插入图片描述

调整过程:

  1. B结点成为C的左孩子,A结点成为C的右孩子
  2. 原来C结点的左子树β 作为B的右子树;原来C结点的右子树γ 作为A的左子树

在这里插入图片描述

哈希表查找

哈希存储基本思想:选取某个函数,将关键字作为自变量进行计算,将运算后的函数值作为元素的存储位置,并按此存放。
哈希地址:将分散的关键字利用hash函数映射到一个存放关键码信息的地址(通常为数组下标) 。
Hash函数(哈希):将关键字k转换为地址的函数。
哈希冲突(同义词冲突): 不同的关键码映射到同一个哈希地址:
k e y 1 ≠ k e y 2 , 但 H ( k e y 1 ) = H ( k e y 2 ) key1 \neq key2,但H(key1)=H(key2) key1=key2H(key1)=H(key2)

同义词:具有相同函数值的两个关键字
影响冲突的因素:

  1. 装填因子 α = 元 素 个 数 / 哈 希 表 长 = n / m α=元素个数/哈希表长=n/m α=/=n/m
    α越小,冲突的可能性就越小;
    α越大(最大可取1),冲突的可能性就越大。
  2. 与所采用的哈希函数有关
  3. 与解决冲突方法有关

构造哈希函数

  1. 直接定址法
    H ( k e y ) = a ⋅ k e y + b H(key) = a·key + b H(key)=akey+b
    优点:不会产生冲突。
    事先知道关键码,关键码集合不是很大且连续性较好。
  2. 除留余数法
    H ( k e y ) = k e y % p , ( % 为 求 余 运 算 , p ≤ m ) H(key)=key \% p, (\%为求余运算,p≤m) H(key)=key%p,(%pm)
  3. 数字分析法
    根据关键码在各个位上的分布情况,选取分布比较均匀的若干位组成哈希地址。
  4. 平方取中法
    对关键字平方后,按哈希表大小,取中间的若干位作为哈希地址(平方后截取)。

解决哈希冲突

开放定址法(开地址法,线性探测法)

基本思想:有冲突时就去寻找下一个空的哈希地址,只要哈希表足够大,空的哈希地址总能找到,并将数据元素存入。
优点
只要哈希表未被填满,就能找到一个空地址单元存放有冲突的元素;
缺点
可能出现很多元素在相邻的哈希地址上“堆积”,降低了查找效率。
解决方案
可采用平方探测法或伪随机探测法,以改善“堆积”问题。
平方探测法的数学描述公式为:
h i = ( h ( k ) ± i 2 ) % m ( 1 ≤ i ≤ m − 1 ) hi=(h(k) ± i2) \% m (1≤i≤m-1) hi=(h(k)±i2)%m(1im1),即查找的位置依次为: h ( k ) 、 h ( k ) + 1 、 h ( k ) − 1 、 h ( k ) + 4 、 h ( k ) − 4 、 … h(k) 、h(k) +1、h(k) -1 、h(k) +4、h(k) -4、… h(k)h(k)+1h(k)1h(k)+4h(k)4

链地址法

基本思想:相同哈希地址的记录链成一单链表,m个哈希地址就设m个单链表,然后用用一个数组将m个单链表的表头指针存储起来,形成一个动态的结构

typedef struct node
{
	KeyType key;			//关键字域
	struct node *next;		//下一个结点指针
} Node;				//单链表结点类型
typedef struct
{
	node *firstp[m];		//首结点指针
            int length;
} HashChain;				//哈希表类型

链地址法建立哈希表步骤

  1. 取数据元素的关键字key,计算其哈希函数值(地址)。若该地址对应的链表为空,则将该元素插入此链表;否则执行step2解决冲突。
  2. 根据选择的冲突处理方法,计算关键字key的下一个存储地址。若该地址对应的链表为不为空,则利用链表的前插法或后插法将该元素插入此链表。
void SearchHT(HashChain ha,int p,KeyType k)	
//在哈希表中查找关键字k
{	int i=0,adr;
	adr=k % p;			//计算哈希函数值
	node *q;
	q=ha. firstp[adr];		//q指向对应单链表的首结点
	while (q!=NULL && q->key!=k)//扫描单链表adr的所有结点
	{
		i++;
		q=q->next;
	}
	if (q!=NULL)			//查找成功
		printf("成功:关键字%d,比较%d次\n",k,i);
	else				//查找失败
		printf("失败:关键字%d,比较%d次\n",k,i);
}

优点:非同义词不会冲突,无“聚集”现象;链表上结点空间动态申请,更适合于表长不确定的情况
小结

  1. 链地址法优于开地址法
  2. 除留余数法作哈希函数优于其它类型函数
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值