数据结构丨【学习笔记】第九章 查找

一、基本概念:

查找表、静态查找/动态查找、关键字(唯一确定一个记录的叫主关键字)、平均查找长度ASL(关键字比较次数*成功查找到第i个记录的概率)

二、线性表的查找


顺序查找

(适用于查找表没有规律的情况):从一端开始,逐一按给定值和关键字比较。


1.无监视哨

(直接按顺序查找,找到返回下标符号,失败返回-1)


int SeqSearch1(Record r[],int n,int k)
{
   int i=0;
   while(i<n&&r[i].key!=k)
      i++;
    if(i<n)
    return i;
    else
    return -1;
}


2.有监视哨

多定义一个单元存放要查找元素,每次循环时无需进行i<n判断是否越界的比较)


int SeqSearch2(Record r[],int n,int k)
{
   int i=0;
   r[n].key=k;//数组的下标到n-1,再定义一个单元r[n]存放查找元素
   while(r[i].key!=k)
      i++;
    if(i<n)
    return i;
    else
    return -1;
}

分析:


(1)查找成功时,ASL=(1+2+...+n)*(1/n)=(n+1)/2,在第i个元素查找相等时需要进行i次比较(i=1,2,...,n),而每个记录的查找概率相等为1/n;
2)查找不成功时,需要进行n+1次比较。
    故时间复杂度为O(n)
优点:随机存储

缺点:当n很大时效率低

二分查找

(适用于查找表全部有序):取中间元素进行比较。


1.递归

(根据算法思想直接编写即可)
int BiSearch1(Record r[],int low,int high,int mid,int k)
{
   if(low>high)
      return -1;//不存在,查找失败返回-1
   else{
      mid=(low+high)/2
       if(r[mid].key=k)
          return mid;//返回位置
      else{ 
          if(r[mid].key<k)//在中点的右半区进行比较
             return BiSearch1(r,mid+1,high,mid,k);
          if(r[mid].key>k)//在中点的左半区进行比较
             return BiSearch1(r,low,mid-1,mid,k);
      }
   }
}

2.非递归

(常考)


int BiSearch2(int low,int high,int mid,int k)
{
   int low=0;//比较数组下标
   high=n-1;
   while(low<=high)//存在区间就折半查找比较
   {

    mid=(low+high)/2;
    if(r[mid].key==k)

         return mid;
    else if(r[mid].key<k)
        low=mid+1;
    else
        high=mid-1;
   }
  return -1;//如果不存在,查找失败返回-1
}


二叉判定树:

       用一棵二叉树描述折半查找的过程,二叉树内结点数值即为查找有序表中记录的下标。树根结点即为中间结点,比较次数就是层数即树高。
分析:
例题:

1.判断:用二分查找任何元素都比用顺序查找方法好(×)

2.已知一个长度为16的顺序表,其元素按关键字有序,若采用折半查找算法查找一个不存在的元素,则比较的次数至少为()次,至多是()次。

答案是:5,6:第五层没有满可以找到空的,第五层有元素的孩子是空也可以找到为空。

3.

4.有一个有序表{1,3,9,12,32,41,45,62,75,77,82,95,100},当二分查找值为82的结点时,经( )次比较后查找成功。                                                                                                 答案是3次。
   折半查找求ASL、比较次数等问题关键要画图出来,比较的是下标/位置,记得low,high的值会变化+1、-1之类的。

5.算法设计题:试编写折半查找算法。

6.二分查找方法适用于线性表。(√)

分块索引

适用于部分有序情况:块间有序,块内无序)
      引入索引表,记录每块key(对应指标中最大的关键字值)与索引位置(用指针指向最开始的序号)。


方法:

(1)先查索引表(可以顺序也可以二分,因为是有序的)以确定在哪块

(2)再在块内进行顺序查找到该数


计算平均长度:ASL=索引表+子表
  例:设分块索引一个表,该表有300个数据元素,索引表用二分查找,有10个分块,求平均查找长度。                                                                                                            答案是18.4.

     记得二分查找最好还是自己画图算出平均长度


三、树表的查找

(强调为动态查找,可进行增加、插入、删除操作)


二叉排序树


      遵循规律:左子树所有结点值都小于根节点值,右子树所有结点值都大于根节点值。注意二叉排序树中的子树也满足这样规律,采用递归方法。数据域就是关键字key。
      故用中序遍历后为有序序列则判断该为二叉排序树。

相关操作:


插入:


(1)若二叉树为空,则生生值为k的结点s,并将其作为根节点插入。
(2)若k小于根节点的值,则在根的子树插入。
(3)若k大于根节点的值,则在根的子树插入。
(4)若k等于根节点的值,无需插入。

void BiSortTree::Insert(BiNode *&ptr,int k)
{
   if(ptr==NULL)
   {
      ptr=new BiNode;//根节点
      ptr->key=k;
      ptr->lchild=ptr->rchild=NULL;
   }
   else//不是根结点位置
   {
      if(ptr->key<k)
        Insert(ptr->lchild,k);//插入到左子树
      if(ptr->key>k)
        Insert(ptr->rchild,k);//插入到右子树
   }
}

void BiSortTree::Insert(int k)
{
   Insert(root,k);
}

私有成员函数的第一个ptr必须是引用&


建立:

       就是从空二叉排序树逐个插入。
int a[],int n//数组记录关键字
root=NULL;
for(i=0;i<n;i++)
   Insert(root,a[i]);
例1:给定关键字序列画出建立二叉排序树过程。
一开始是一个空树,读到的第一个元素即为根节点,然后依次比较插入。

例2:试编写一个算法将线性表L中的数据建立一棵二叉排序树。

例3:编写算法从键盘读入一组整数,以9999为结束标志,将这些数据建立一棵二叉排序树。

例4:设采用二叉链表存储二叉排序树,

查找(值为k的结点):


1.递归
BiNode *BiSortTree::Search(BiNode *ptr,int k)
{ 
   if(ptr==NULL)
      return NULL;
   else
      if(ptr->key=k)
         return ptr;
      else if(ptr->key>k)
         return Search(ptr->lchild,k);
      else
         return Search(ptr->rchild,k);
}
bool BisortTree::Search(int k)
{
   return Search(root,k)!=NULL;
}

2.非递归:
BiNode*BiSortTree::Search2(BiNode *ptr,int k)
{
   while(ptr!=NULL)
   {if(k==ptr->key)
      return ptr;
    else if(k<ptr->key)
       ptr=ptr->lchild;
    else
       ptr=ptr->rchild;
  }
   return NULL;
}
bool BiSortTree::Search2(int k)
{
    return Search2(root,k)!=NULL;
}

查找性能分析:


     查找次数小于等于树深度(即根节点高度)。
     若是平衡二叉树,则时间复杂度为O(log2n)//类比思考二分查找 ;若是单支树,则时间复杂度为O(n);一般而言是O(log2n)
例:

平衡二叉树

     (形态均匀的二叉排序树遵循规律:左右子树都是平衡二叉树,左子树和右子树的高度差不超过1。
        结点的平衡因子:左子树和右子树的高度差(只可能是0.-1.1)
       如果不满足要求,则需要进行调整

        从下往上依次判断平衡与否,确定最小不平衡树,然后找到与根节点相邻的三个结点,拎出来对这三个结点进行调整,接着其余叶子结点按照二叉排序树规律放置即可。(详见一秒学会 平衡二叉树的调整,非标题党!不简单你打我! (考研数据结构)_哔哩哔哩_bilibili

例1:设一组关键字序列{4,5,7,2,1,3,6},试建立一棵平衡二叉树。

解:

例2:(判断)在建立平衡二叉树过程中,当插入新结点时该树失去平衡,则要进行旋转操作。(√)

例3:已知二叉树T的结点结构为:(bal存储结点的平衡因子)

leftdatarightbal

试编写算法求树T中各结点的平衡因子。

B、B+树

例1:分别画出一个B树和B+树的例子,并指出它们之间的区别。


例2:判断:B+树中的所有非叶子结点仅起索引作用。(✓)
例3:

方法详见「六分钟速通」B树的插入与删除 简明易懂_哔哩哔哩_bilibili

四、散列表的查找


Hash查找(重点,必考)


基本概念:均匀分布,减少冲突


Hash表的构造:

直接定址、除留余数、数字分析、平方取中、折叠
选择适当的Hash函数可以减少冲突但不能避免冲突。

处理冲突的方法:(重要)


(1)开放地址法
一旦发生冲突了就去找空地址。


线性探测:

H=(Hash(key)+1)%m,m为hash表长,可以理解公式用公式计算,也可直接下一位找,因为肯定是小于m的

int SearchHash1(int hash[],int m,int k)
{
   pos=k%m;
   t=pos;
   while(hash[pos]!=EMPTY)
   {
     if(hash[pos]==k)
       return pos;
     else 
        pos=(pos+1)%m;
     if(pos==t)
        return -1;
   }
   return -1;
   }
}


(2)拉链法


把所有Hash地址相同的记录存储在一个单链表中,Hash表中存指向各单链表的指针。
Hash查找:先根据Hash函数算出给定值k的Hash地址,然后用Hash表位于该地址的关键记录与k比较。

Node* SearchHash2(Node *hash[],int m,int k)
{
   pos=k%m;
   p=hash[pos];
   while(p&&p->data!=k)
     p=p->next;
   if(p)
     return p;
   else
     return NULL;
}

例题:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值