数据结构第七章小结——查找

一、基本概念和专业术语:

(1)查找:根据给定的某个值,在查找表中确定一个其关键字等于给定值的数据元素(或记录)。

(2)查找算法分类

  1)静态查找和动态查找;
     注:静态或者动态都是针对查找表而言的。动态表指查找表中有删除和插入操作的表。

    常见静态表:顺序查找、二分查找、插值查找、索引查找等

    常见动态表:二叉排序树、平衡二叉树、B树、散列表

  2)无序查找和有序查找。
      无序查找:被查找数列有序无序均可;
      有序查找:被查找数列必须为有序数列。

(3)平均查找长度(ASL):需和指定key进行比较的关键字的个数的期望值,称为查找算法在查找成功时的平均查找长度。

(4)关键字(Key):是数据元素中某个数据项的值,又称为键值,用它可以标识一个数据元素。若关键字可以唯一地标识一个记录,则称此关键字为主关键字;若关键字对应多个记录,则称此关键字为次关键字。

二、顺序查找算法:

(1)定义:顺序查找又叫线性查找,是最基本的查找技术,它的查找过程是:从表中第一个(或最后一个)记录开始,逐个进行记录的关键字和给定值比较,若某个记录的关键字和给定值相等,则查找成功,找到所查的记录;如果直到最后一个(或第一个)记录,其关键字和给定值比较都不等时,则表中没有所查的记录,查找不成功。

(2)基础实现方法

int Search(SSTable ST,keyType key)
{
    for(i = ST.length;i>=1;--i)
        if(ST.R[i].key == key)
            return i;
    return 0;
}
顺序查找

(3)带哨兵

// 有哨兵顺序查找数组a中从a[1]到数组末尾的key
// 设置一个哨兵可以解决不需要每次让i与a.length作比较。
// 返回-1说明查找失败,注意:从数组下标为1的位置开始查找
 int seqSearch(int[] a, int key)
 {
    int i = a.length - 1;    // 设置循环从数组尾部开始
    a[0] = key;              // 设置a[0]:"哨兵"
    while (a[i] != key)
        i--;
    if(i>0)
        return i;
    else
        return -1;
}
哨兵

(4)时间复杂度估算

  对于顺序查找算法来说,查找成功最好的情况就是在第一次就找到了,算法时间复杂度为O(1),最坏的情况是查找到只余一个元素时才找到,需要n次比较,时间复杂度为O(n),需要n+1次比较,时间复杂度为O(n),因为关键字在任意一位置的概率是相等的,所以平均查找次数为(n+1)/2,所以最终时间复杂度还是O(n)。当n很大时,查找效率极为低下,对一些小型数据的查找时,这种查找方式是可以适用的。

三、二分查找算法:

(1)定义:二分查找,又称为折半查找。它的前提是线性表中的记录必须是关键码有序(通常从小到大有序),线性表必须采用顺序存储。二分查找的基本思想是:在有序表中,取中间记录作为比较对象,若给定值与中间记录的关键字相等,则查找成功;若给定值小于中间记录的关键字,则在中间记录的左半区继续查找;若给定值大于中间记录的关键字,则在中间记录的右半区继续查找。不断重复上述过程,直到查找成功,或所有查找区域无记录,查找失败为止。

(2)基础实现

// 二分查找:数组a中所有数据中包含key的数组下标,如果没有,返回-1,有则返回
int binarySearch(int[] a, int key)
{
    int low = 0;                          // 定义最低下标
    int high = a.length-1;            // 定义最高下标
    while(low <= high)
    {
        int mid = (low + high)/2;   // 得到中间下标
        if(key < a[mid])                // 若查找值比中值小
            high = mid - 1;             // 最高下标调整到中间下标小一位
        else if(key > a[mid])        // 若查找值比中值大
            low = mid + 1;            // 最低下标调整到中间下标大一位
        else
            return mid;                  // 若相等则说明中间记录的下标即为查找到的值
    }
    return -1;
}
二分查找

(3)时间复杂度估算

二分查找通过每次mid和key的比对确定每次将待查找的有序序列不断的减半,直到比对数据只剩下一个,从而减少查找的时间。

根据二叉树的性质,即“具有n个结点的完全二叉树的深度为【Log2n】+1”,可以得到二分查找最坏情况下查找到关键字或查找失败的次数是【Log2n】+1,最好的情况当然是1次了,因此二分查找的时间复杂度为O(Logn),显然远远好于顺序查找的O(n)时间复杂度了。但是缺点是,对表的结构要求很高,只能用于顺序存储的有序表,并且查找之前还需要排序,而排序本身就是一个比较费时的运算,不适合用于动态变化的线性表。

二分查找应用场景的局限性:

(1)基于顺序表的存储结构;

(2)针对有序数据;

(3)数据量小且比较操作不耗时时,不需要二分;

(4)数据量太大也不行(超出内存可用连续空间);

四、二叉排序树算法:

(1)定义:二叉排序树或者是空树或者是具有下列性质的二叉树

                   1)若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值

                   2)若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值

                   3)它的左、右子树也分别为二叉排序树。

 

(2)二叉排序树的二叉链表存储表示

typedef struct{
    KeyType key;//关键字项;
    InfoType otherinfo; //其他数据项;
}ElemType;
typedef struct BSTNode{
    ElemType data;//每个结点的数据域包括关键字项和其他数据项
    struct BSTNode *lchild,*rchild;//左右孩子的指针;
}BSTNode,*BSTree;
二叉链表表示

(3)二叉排序树的创建

void CreatBST(BSTree &T)
{//依次读入关键字为key的结点,将此结点插入二叉排序树T中;
   T = NULL;//初始化为;
   cin>>e;
   while(e.key!=end)//end作为输入结束的条件标志;
   {
         InsertBST(T,e);
         cin>>e;
    }
}
创建

(4)二叉排序树的查找

void InsertBST(BSTree &T,ElemType e)
{//当二叉排序树T中不存在关键字等于e.key的数据元素时,则插入该元素;
    if(T==NULL)
    {
        s = new BSTNode;
        s->data = e;
        s->lchild = s->rchild = NULL;
        T = s;
     }else
     if(e.key<T->data.key)
      InsertBST(T->lchild,e);
     else if(e.key>T->data.key)
      InsertBST(T->rchild,e);

}
查找算法

(3)时间复杂度估算

 由于二叉树排序的时间复杂度是和树的形态相关的,其查找复杂度是在O(log2n)~O(n)之间,平均时间复杂度约为O(logn),近似于二分查找。不平衡的极端情况就是斜树,查找时间复杂度为O(n),等同于顺序查找。为了提高二叉排序树的查找效率,尽量让二叉排序树的形状均衡,所以引入了平衡二叉树的概念。

 

五、平衡二叉树算法:

(1)定义:平衡二叉树是一种二叉排序树,其中每一个结点的左子树和右子树的高度差至多等于1。平衡二叉树是一种高度平衡的二叉排序树,即要么是一棵空树,要么它的左子树和右子树都是平衡二叉树,且左子树和右子树的深度之差的绝对值不超过1。将二叉树上结点的左子树深度减去右子树深度的值称为平衡因子BF(Balance Factor),那么平衡二叉树上所有结点的平衡因子只可能是-1、0和1。距离插入结点最近的,且平衡因子的绝对值大于1的结点为根的子树,称为最小不平衡树。

六、B树简介:

(1)背景:如果要操作的数据集非常大,大到内存已经没办法处理,对数据的处理需要不断从硬盘等存储设备中调入或调出内存页面。一旦涉及到这样的外部设备,关于时间复杂度的计算就会发生变化,访问该集合元素的时间已经不仅仅是寻找该元素所需比较次数的函数,而考虑对硬盘等外部存储设备的访问时间以及将会对该设备做出多少次的单独访问。

(2)定义B树是平衡的多叉树,一个节点有多于两个(不能小于)结点的平衡多叉树。
(3)基本性质
                             <1>.根节点至少有两个孩子
                             <2>.每个非根节点至少有M/2(上取整)个孩子,至多有M个孩子。
                             <3>.每个非根节点至少有M/2-1(上取整)个关键字,至多有M-1个关键字。并以升序排列。
                             <4>.key[i]和key[i+1]之间的孩子节点的值介于key[i]和key[i+1]之间。
                             <5>.所有的叶子节点都在同一层。

多路查找树:其每一个结点的孩子树可以多于两个,且每一个结点处可以存储多个元素。

(3)B+树定义:B+树是对B树的一种变形树,它与B树的差异在于:

  • 有k个子结点的结点必然有k个关键码;
  • 非叶结点仅具有索引作用,跟记录有关的信息均存放在叶结点中。
  • 树的所有叶结点构成一个有序链表,可以按照关键码排序的次序遍历全部记录。

七、散列表查找

(1)定义:散列技术是在记录的存储位置和它的关键字之间建立一个确定的对应关系f,使得每个关键字key对应一个存储位置f(key)。查找时,根据这个确定的对应关系找到给定值key的映射f(key),若查找集合中存在这个记录,则必定存在在f(key)的位置上。把对应关系f称为散列函数,又称为哈希(Hash)函数,采用散列技术将记录存储在一块连续的存储空间中,这块连续存储空间称为散列表或哈希表。关键字对应的记录存储位置称为散列地址。

(2)查找步骤:散列技术既是一种存储方法,也是一种查找方法。散列过程的步骤分为两步: 在存储时,通过散列函数计算记录的散列地址,并按次散列地址存储该记录。 当查找记录时,通过同样的散列函数计算记录的散列地址,按此散列地址访问该记录。

(3)适合场景:散列技术与线性表、树、图结构不同的是,散列技术的记录之间不存在什么逻辑关系,它只与关键字有关联。因此,散列主要是面向查找的存储结构。 散列技术最适合的求解问题是查找与给定值相等的记录。不适合同样的关键字对应很多记录或者范围查找。   对于两个不同的关键字key1≠key2,但是却f(key1)=f(key2),这种现象称为冲突,并把key1和key2称为这个散列函数的同义词。

(4)散列函数的构造方法

     1>直接定址法 :f(key) = a X key + b(a,b为常数)

     2>数字分析法

     3>平方取中法

     4>折叠法

     5>除留余数法(最常用的散列函数):对于散列表长为m的散列函数公式为:f(key) = key mod p (p ≤m)

     6>随机数法

(5)处理散列冲突的方法:设计得再好的散列函数也不可能完全避免冲突。

1.开放定址法:fi(key) = (f(key) + di) MOD m(di = 1,2,3...,m-1)

开放定址法就是一旦发生了冲突,就去寻找下一个空的散列地址,只要散列表足够大,空的散列地址总能找到,并将记录存入。

增加平方运算的目的是为了不让关键字都聚集在某一块区域,称这种方法为二次探测法fi(key) = (f(key) + di) MOD m (di=1²,-1²,2²,-2²,...,q²,-q²(q≤m/2))

还可以对于位移量di采用随机函数计算得到,称之为随机探测法。即设置随机种子相同,每次调用随机函数可以生成不会重复的数列,在查找时,用同样的随机种   子,它每次得到的数列是相同的,相同的di可以得到相同的散列地址。fi(key) = (f(key) + di) MOD m(di是一个随机数列)

2.再散列函数法

再散列函数法就是事先准备多个散列函数fi(key) = RHi(key) (i=1,2,...,k),每当发生散列地址冲突时,就换一个散列函数计算,这种方法能够使得关键字不产生聚集,也相应增加了计算的时间。  

3.链地址法

将所有关键字为同义词的记录存储在一个单链表中,称这种表为同义词子表,在散列表中只存储所有同义词子表的头指针,无论有多少个冲突,都只是在当前位置给单链表增加结点而已。

链地址法对于可能会造成很多冲突的散列函数来说,提供了绝不会出现找不到地址的保障。同时也带来了查找时需要遍历单链表的性能损耗。

4.公共溢出区法

公共溢出区法就是将所有与之间的关键字位置有冲突的关键字{37,48,34}存入一个公共的溢出区表中。

在查找时,对给定值通过散列函数计算出散列地址后,先与基本表的相应位置进行对比,如果相等,则查找成功;如果不相等,则到溢出表中进行顺序查找。

如果相对于基本表而言,有冲突的数据很少的情况下,公共溢出区的结构对查找性能来说还是非常高的。

 (6)学习内容:

(1)如何构造散列函数;

(2)如何处理冲突

    1)造表时如何解决;  

    2)查找时如何解决;

 八、->next

期末考试快到了,网课时期的学习虽然自由度大了很多,但感觉有一些知识点还是过了脑但是没留下哈哈哈,剩下的两个星期要好好复习,大一下几乎就这样过去了啊,想想就有点小可惜,下学期又是一个大二新生,感觉有一些上学期的知识点(特别是数学),有点迷迷瞪瞪的了,hai,来自一只小菜鸡的忧愁。

复习规划:

根据期末考试的形式,将以论文形式提交的期末作业在考试周前完成:

1)综英期末口语考试:7.6

    6.30前拟好讲稿(修改),7.3前背熟,7.4、5和组员合稿。

2)eap期末literature review:第14周发布 第16周前提交期中summary pre视频和期末literature review

   7.5前完成summary pre视频

   7.8前完成literature review(修改)

3)其他学科的复习就不一一概述了哈哈哈

考试加油!!!大吉大利!

翻译 朗读 复制 正在查询,请稍候…… 重试 朗读 复制 复制 朗读 复制 via 谷歌翻译(国内)

翻译 朗读 复制 正在查询,请稍候…… 重试 朗读 复制 复制 朗读 复制 via 谷歌翻译(国内)

翻译 朗读 复制 正在查询,请稍候…… 重试 朗读 复制 复制 朗读 复制 via 谷歌翻译(国内)

猜你喜欢

转载自www.cnblogs.com/apiao127/p/13183765.html