第七章 查找

查找的基本概念

  1. 查找表
    查找表是由同一类型的数据元素(或记录)构成的集合。
  2. 关键字
    关键字是数据元素(或记录)中某个数据项的值,用它可以标识一个数据元素(或记录)。若此关键字可以唯一地标识一个记录,则称此关键字为主关键字(对不同的记录,其主关键字均不同)。反之,称用以识别若干记录的关键字为次关键字
  3. 查找
    查找是指根据给定的某个值,在查找表中确定一个其关键字等于给定值的记录或数据元素。若表中存在这样的一个记录,则称查找成功,此时查找的结果可给出整个记录的信息,或指示该记录在查找表中的位置;若表中不存在关键字等于给定值的记录,则称查找不成功,此时查找的结果可给出一个“空”记录或“空”指针。
  4. 动态查找表和静态查找表
    若在查找的同时对表执行修改操作(如插入和删除),则称相应的表为动态查找表,否则称之为静态查找表
  5. 平均查找长度
    为确定记录在查找表中的位置,需和给定值进行比较的关键字个数的期望值,称为查找算法在查找成功时的平均查找长度(Average Search Length,ASL)

线性表的查找

顺序查找

顺序查找(Sequential Search)的查找过程为:从表的一端开始,依次将记录的关键字和给定值进行比较,若某个记录的关键字和给定值相等,则查找成功;反之,若查找整个表后,仍未找到关键字和给定值相等的记录,则查找失败。

顺序查找方法既适用于线性表的顺序存储结构,又适用于线性表的链式存储结构。

  1. 顺序查找
#define  _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<stdlib.h>

#define MAXSIZE 100

typedef struct {
	int* R;
	int length;
}SSTable;

bool InitSSTable(SSTable &s) {
	s.R= new int[MAXSIZE];
	if (!s.R)
	{
		exit(-2);
		return false;
	}
	s.length = 0;
	return true;
}
int Search_Seq(SSTable ST,KeyType key)
{//在顺序表ST中顺序查找其关键字等于key的数据元素。若找到,则函数值为该元素
在表中的位置,否则为0
 for(i=ST.length;i>=1;--i)
    if(ST.R[i].key==key) return i; //从后往前查找
 return 0;
}
  1. 设置监视哨的顺序查找
int Search_Seq(SSTable ST,KeyType key)
{//在顺序表ST中顺序查找其关键字等于key的数据元素。若找到,则函数值为该元素在表中的位置,否则为0
 ST.R[0].key=key;              //“监视哨”
 for(i=ST.length;ST.R[i].key!=key;--i); //从后往前找
 return i;                    
}

时间复杂度为O(n)
顺序查找的优点是:算法简单,对表结构无任何要求,既适用于顺序结构,也适用于链式结构,无论记录是否按关键字有序均可应用。

缺点是:平均查找长度较大,查找效率较低,所以当n很大时,不宜采用顺序查找。

折半查找

折半查找(Binary Search)也称二分查找,它是一种效率较高的查找方法。但是,折半查找要求线性表必须采用顺序存储结构,而且表中元素按关键字有序排列。

【算法步骤】
① 置查找区间初值,low为1,high为表长。
② 当low<=high时,循环执行以下操作:
 mid取low和high的中间值;
 将给定值key与中间位置记录的关键字进行比较,若相等则查找成功,返回中间位置mid;
 若不相等则利用中间位置记录将表对分成前、后两个子表。如果key比中间位置记录的关键字小,则high取为mid−1,否则low取为mid+1。
③ 循环结束,说明查找区间为空,则查找失败,返回0。

int Search_Bin(SSTable ST,KeyType key)
{//在有序表ST中折半查找其关键字等于key的数据元素。若找到,则函数值为该元素在表中的位置,否则为0
 low=1;high=ST.length; //置查找区间初值
 while(low<=high)
 {
   mid=(low+high)/2;
   if(key==ST.R[mid].key) return mid;    //找到待查元素
   else if(key<ST.R[mid].key) high=mid-1; //继续在前一子表进行查找
   else low=mid+1;            //继续在后一子表进行查找
 } //while
 return 0; //表中不存在待查元素
}

折半查找过程可用二叉树来描述。树中每一节点对应表中一个记录,但节点值不是记录的关键字,而是记录在表中的位置序号。把当前查找区间的中间位置作为根,把左子表和右子表分别作为根的左子树和右子树,由此得到的二叉树称为折半查找的决策树
在这里插入图片描述
折半查找的时间复杂度为O(log2n )
折半查找的效率比顺序查找的高,但折半查找只适用于有序表,且限于顺序存储结构。.

折半查找的优点是:比较次数少,查找效率高。
其缺点是:对表结构要求高,只能用于顺序存储的有序表。采用折半查找前元素需要排序,而排序本身是一种费时的运算。同时为了保持顺序表的有序性,对有序表进行插入和删除时,平均比较和移动表中一半元素,这也是一种费时的运算。因此,折半查找不适用于数据元素经常变动的线性表。

分块查找

分块查找(Blocking Search)又称索引顺序查找,这是一种性能介于顺序查找和折半查找之间的查找方法。

在这里插入图片描述
分块查找过程需分两步进行。
先确定待查记录所在的块(子表),然后在块中顺序查找。
假设给定值key = 38,则先将key依次和索引表中各分块的最大关键字进行比较,因为22<key<48,则关键字为38的记录若存在,必定在第二个子表中。由于同一索引项中的指针指示第二个子表中的第一个记录是表中第7个记录,则自第7个记录起进行顺序查找,直到ST.elem[10].key = key为止。假如此子表中没有关键字等于key的记录(例如,key = 29时自第7个记录起至第12个记录的关键字和key都不等),则查找不成功。

在这里插入图片描述
分块查找的优点是:在表中插入和删除数据元素时,只要找到该元素对应的块,就可以在该块内进行插入和删除运算。由于块内是无序的,故插入和删除比较容易,无须进行大量移动。如果线性表既经常动态变化,又需对其进行快速查找,则可采用分块查找。

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

树表的查找

二叉排序树

二叉排序树(Binary Sort Tree)又称二叉查找树,它是一种对排序和查找都很有用的特殊二叉树。

  1. 二叉排序树的定义
    (1)若它的左子树不空,则左子树上所有节点的值均小于它的根节点的值;
    (2)若它的右子树不空,则右子树上所有节点的值均大于它的根节点的值;
    (3)它的左、右子树也分别为二叉排序树。
    二叉排序树是递归定义的。由定义可以得出二叉排序树的一个重要性质:中序遍历一棵二叉树时可以得到一个节点值递增的有序序列
//- - - - -二叉排序树的二叉链表存储表示- - - - -
typedef struct
{
 KeyType key;    //关键字项
 InfoType otherinfo; //其他数据项
}ElemType;             //每个节点的数据域的类型
typedef struct BSTNode
{
 ElemType data;           //每个节点的数据域包括关键字项和其他数据项
 struct BSTNode *lchild,*rchild; //左右孩子指针
}BSTNode,*BSTree;
  1. 二叉排序树的查找
    二叉排序树的递归查找

【算法步骤】
① 若二叉排序树为空,则查找失败,返回空指针。
② 若二叉排序树非空,将给定值key与根节点的关键字T−>data.key进行比较:
 若key等于T−>data.key,则查找成功,返回根节点地址;
 若key小于T−>data.key,则递归查找左子树;
 若key大于T−>data.key,则递归查找右子树。

BSTree SearchBST(BSTree T,KeyType key)
{//在根指针T所指二叉排序树中递归地查找某关键字等于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);         //在右子树中继续查找
}

含有n个节点的二叉排序树的平均查找长度和树的形态有关。

二叉排序树的平均查找长度仍然和log2n是同数量级的。

对于需要经常进行插入、删除和查找运算的表,采用二叉排序树比较好。

  1. 二叉排序树的插入

【算法步骤】
① 若二叉排序树为空,则将待插入节点S作为根节点插入空树。
② 若二叉排序树非空,则将key与根节点的关键字T−>data.key进行比较:
 若key小于T−>data.key,则将
S插入左子树;
 若key大于T−>data.key,则将*S插入右子树。

void InsertBST(BSTree &T,ElemType e)
{//当二叉排序树T中不存在关键字等于e.key的数据元素时,则插入该元素
 if(!T)
 {         //找到插入位置,递归结束
   S=new BSTNode;       //生成新节点*S
   S->data=e;         //新节点*S的数据域置为e
   S->lchild=S->rchild=NULL; //新节点*S作为叶子节点
   T=S;       //把新节点*S链接到已找到的插入位置
 }
 else if(e.key < T->data.key) 
   InsertBST(T->lchild,e); //将*S插入左子树
 else if(e.key > T->data.key) 
   InsertBST(T->rchild,e); //将*S插入右子树
}

二叉排序树插入的基本过程是查找,所以时间复杂度同查找一样,是O(log2n)。

  1. 二叉排序树的创建

【算法步骤】
① 将二叉排序树T初始化为空树。
② 读入一个关键字为key的节点。
③ 如果读入的关键字key不是输入结束标志,则循环执行以下操作:
 将此节点插入二叉排序树T中;
 读入一个关键字为key的节点。

void CreatBST(BSTree &T)
{//依次读入关键字为key的节点,将相应节点插入二叉排序树T中
 T=NULL; //将二叉排序树T初始化为空树
 cin>>e; 
 while(e.key!=ENDFLAG) //ENDFLAG为自定义常量,作为输入结束标志
 {
   InsertBST(T,e);      //将此节点插入二叉排序树T中
   cin>>e; 
 }   
}

假设有n个节点,则需要n次插入操作,而插入一个节点的算法时间复杂度为O(log2n),所以创建二叉排序树算法的时间复杂度为O(nlog2n)。

  1. 二叉排序树的删除
void DeleteBST(BSTree &T,KeyType key)
{//从二叉排序树T中删除关键字等于key的节点
 p=T;f=NULL;           //初始化
 /*------------下面的while循环从根开始查找关键字等于key的节点*p---------------*/
 while(p)
 {         
   if(p->data.key==key) break; //找到关键字等于key的节点*p,结束循环
   f=p;                 //*f为*p的双亲节点
   if(p->data.key>key) p=p->lchild;   //在*p的左子树中继续查找
   else p=p->rchild;        //在*p的右子树中继续查找
 } //while
 if(!p) return;             //找不到被删节点则返回
 /*----考虑3种情况实现p所指子树内部的处理 :*p左右子树均不空、无右子树、无左子树---*/
 q=p;
 if((p->lchild)&&(p->rchild)) //被删节点*p左右子树均不空
 {   s=p->lchild;
   while (s->rchild)     //在*p的左子树中继续查找其前驱节点,即最右下节点
   {
     q=s; s=s->rchild; //向右到尽头
   }      
   p->data=s->data;        //s指向被删节点的“前驱”
   if(q!=p) q->rchild=s->lchild;   //重接*q的右子树
   else q->lchild=s->lchild;     //重接*q的左子树
   delete s;
   return;
  } //if
 else if(!p->rchild) //被删节点*p无右子树,只需重接其左子树
 {
   p=p->lchild; 
 } //else if
 else if(!p->lchild) //被删节点*p无左子树,只需重接其右子树
 {
   p=p->rchild;
 } //else if
 /*-----------------将p所指的子树挂接到其双亲节点*f相应的位置----------------*/
 if(!f) T=p;            //被删节点为根节点
 else if(q==f->lchild) f->lchild=p; //挂接到*f的左子树位置
 else f->rchild=p;         //挂接到*f的右子树位置
 delete q;
}

同二叉排序树插入一样,二叉排序树删除的基本过程也是查找,所以时间复杂度仍是O(log2n)。
在这里插入图片描述

平衡二叉树

  1. 平衡二叉树的定义

希望二叉树的高度尽可能小称为平衡二叉树(Balanced Binary Tree或Height-Balanced Tree),因由苏联数学家阿德尔森-维尔斯基(Adelson-Velskii)和兰迪斯(Landis)提出,所以又称AVL树

平衡二叉树或者是空树,或者是具有如下特征的二叉排序树:
(1)左子树和右子树的深度之差的绝对值不超过1
(2)左子树和右子树也是平衡二叉树。
在这里插入图片描述
时间复杂度是O(log2n)。

  1. 平衡二叉树的平衡调整方法
    调整方法是:找到离插入节点最近且平衡因子绝对值超过1的祖先节点,以该节点为根的子树称为最小不平衡子树,可将重新平衡的范围局限于这棵子树。
    在这里插入图片描述
    一般情况下,假设最小不平衡子树的根节点为A,则失去平衡后进行调整的规律可归纳为下列4种情况。

    • LL型
      在这里插入图片描述
      在这里插入图片描述

    • RR型
      在这里插入图片描述
      在这里插入图片描述

    • LR型
      在这里插入图片描述
      在这里插入图片描述

    • RL型
      在这里插入图片描述

  2. 平衡二叉搜索树的插入
    在平衡二叉搜索树(Balanced Binary Search Tree,BBST)上插入一个新的数据元素e的递归算法可描述如下:
    在这里插入图片描述

在这里插入图片描述

手算平衡二叉树的插入

在这里插入图片描述

B-树

  1. B- 树的定义
    一棵m阶的B-树,或为空树,或为满足下列特性的m叉树:
    (1)树中每个节点至多有m棵子树;
    (2)若根节点不是叶子节点,则至少有两棵子树;
    (3)除根之外的所有非终端节点至少有⌈m/2⌉棵子树;
    (4)所有的叶子节点都出现在同一层次上,并且不带信息,通常称为失败节点(失败节点并不存在,指向这些节点的指针为空。引入失败节点是为了便于分析B−树的查找性能);
    (5)所有的非终端节点最多有m − 1个关键字。
  2. B- 树的查找
  3. B- 树的插入
  4. B- 树的删除

B+树

  1. B+ 树和 B- 树的差异
    在这里插入图片描述

  2. B+ 树的查找、插入和删除
    在这里插入图片描述

散列表的查找

散列表的基本概念

如果能在元素的存储位置和其关键字之间建立某种直接关系,那么在进行查找时,就无须作比较或只需作很少的比较,按照这种关系直接由关键字找到相应的记录。这就是散列查找法(Hash Search)的思想,它通过对元素的关键字值进行某种运算,直接求出元素的地址,即使用关键字到地址的直接转换方法,而不需要反复比较。因此,散列查找法又叫杂凑法散列法

散列法中常用的几个术语:

  1. 散列函数和散列地址
    在记录的存储位置p和其关键字key之间建立一个确定的对应关系H,使p = H(key),称这个对应关系H为散列函数,p为散列地址。
  2. 散列表
    一个有限连续的地址空间,用以存储按散列函数计算得到相应散列地址的数据记录。通常散列表的存储空间是一个一维数组,散列地址是数组的下标。
  3. 冲突和同义词
    对不同的关键字可能得到同一散列地址,即key1 ≠ key2,而H(key1) =H(key2),这种现象称为冲突。
    具有相同函数值的关键字对该散列函数来说称作同义词,key1与key2互为同义词。

散列函数的构造方法

  1. 数字分析法
    在这里插入图片描述
    在这里插入图片描述

  2. 平方取中法
    在这里插入图片描述

  3. 折叠法
    在这里插入图片描述

  4. 除留余数法
    在这里插入图片描述

处理冲突的方法

处理冲突的方法与散列表本身的组织形式有关。按组织形式的不同,处理冲突的方法通常分两大类:开放地址法链地址法

  1. 开放地址法
    在这里插入图片描述
    在这里插入图片描述
    上述3种处理方法各有优缺点。
    线性探测法的优点是:只要散列表未填满,总能找到一个不发生冲突的地址。
    缺点是:会产生“二次聚集”现象。

而二次探测法和伪随机探测法的优点是:可以避免“二次聚集”现象。其缺点也很显然:不能保证一定找到不发生冲突的地址。

  1. 链地址法
    在这里插入图片描述
    在这里插入图片描述

散列表的查找

//- - - - -开放地址法散列表的存储表示- - - - -
#define m 20             //散列表的表长
typedef struct{
  KeyType key;      //关键字项
  InfoType otherinfo; //其他数据项
}HashTable[m];

算法 散列表的查找
【算法步骤】
① 给定待查找的关键字key,根据创建表时设定的散列函数计算H0 = H(key)。
② 若单元H0为空,则所查元素不存在。
③ 若单元H0中元素的关键字为key,则查找成功。
④ 否则重复下述解决冲突的过程:
 按处理冲突的方法,计算下一个散列地址Hi
 若单元Hi为空,则所查元素不存在;
 若单元Hi中元素的关键字为key,则查找成功。

#define NULLKEY 0         //单元为空的标记
int SearchHash(HashTable HT,KeyType key)
{//在散列表HT中查找关键字为key的元素,若查找成功,返回散列表的单元标号,否则返回-1 
 H0=H(key);             //根据散列函数H(key)计算散列地址
 if(HT[H0].key==NULLKEY) return -1; //若单元H0为空,则所查元素不存在
 else if(HT[H0].key==key) return H0; //若单元H0中元素的关键字为key,则查找成功
 else
  {
   for(i=1;i<m;++i)
   {  
     Hi=(H0+i)%m;      //按照线性探测法计算下一个散列地址Hi
     if(HT[Hi].key==NULLKEY) return -1; //若单元Hi为空,则所查元素不存在
     else if(HT[Hi].key==key) return Hi; //若单元Hi中元素的关键字为key,则查找成功
   } //for
   return -1;
  } //else
}

在这里插入图片描述

小结

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

  • 19
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值