1 / 132

第 8 章 查找

第 8 章 查找. 8.1 基本概念 8.2 静态查找表 8.3 动态查找表 8.4 哈希表及其查找. 8.1 基本概念. 1 、查找表和查找 查找表 :被查找的对象是由一组结点组成的表 (Table) 或文件,而每个结点则由若干个数据项组成。并假设每个结点都有一个能唯一标识该结点的关键字。 查找 :给定一个值 K ,在含有 n 个结点的表中找出关键字等于给定值 K 的结点。若找到,则查找成功,返回该结点的信息或该结点在表中的位置;否则查找失败,返回相关的指示信息。. 8.1 基本概念. 2 、查找的分类

sereno
Download Presentation

第 8 章 查找

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. 第8章 查找 8.1 基本概念 8.2 静态查找表 8.3 动态查找表 8.4 哈希表及其查找

  2. 8.1 基本概念 1、查找表和查找 查找表:被查找的对象是由一组结点组成的表(Table)或文件,而每个结点则由若干个数据项组成。并假设每个结点都有一个能唯一标识该结点的关键字。 查找:给定一个值K,在含有n个结点的表中找出关键字等于给定值K的结点。若找到,则查找成功,返回该结点的信息或该结点在表中的位置;否则查找失败,返回相关的指示信息。

  3. 8.1 基本概念 2、查找的分类 按查找条件分:按主关键字查找、按次关键字查找。 按查找数据存放存储器分:内查找、外查找。 内查找:整个查找过程都在内存中进行。 外查找:查找过程中需要访问外存。 按查找目的分:静态查找、动态查找。 若在查找的同时对表做修改操作,则相应的表称之为动态查找表(Dynamic Search Table),否则称之为静态查找表(Static Search Table)。

  4. 8.1 基本概念 3、如何进行查找 由于查找表中的数据元素之间不存在明显的组织规律,因此不便于查找。 为了提高查找的效率,需要在查找表中的元素之间人为地加入某种确定关系,即用另外一种结构来表示查找表,而查找的方法则取决于查找表的结构。 4、查找方法的评价 ① 查找速度; ② 查找过程中占用存储空间的多少;

  5. 8.1 基本概念 ③ 查找算法的复杂程度; ④ 平均查找长度ASL。 5、平均查找长度ASL 平均查找长度:查找过程中对关键字需要执行的平均比较次数,是衡量一个查找算法效率优劣的标准之一。 其中:① n:结点的个数;

  6. 8.1 基本概念 ② pi:查找第i个结点的概率。若不特别声明,认为每个结点的查找概率相等,即  pl=p2…=pn=1/n ③ ci:找到第i个结点所需进行的比较次数。 注意:为了简单起见,假定表中关键字的类型为整数。 定义 8.1 typedef int KeyType;/*KeyType应由用户定义*/

  7. 8.2 静态查找表 线性表上进行查找的方法主要有三种: • 顺序查找 • 二分查找 • 分块查找

  8. 8.2.1 顺序查找(Sequential Search) 1、存储结构要求 顺序查找方法既适用于线性表的顺序存储结构,也适用于线性表的链式存储结构(使用单链表作存储结构时,扫描必须从第一个结点开始)。 2、基本思想 从表的一端开始,顺序扫描线性表,依次将扫描到的结点关键字和给定值K相比较。若当前扫描到的结点关键字与K相等,则查找成功;若扫描结束后,仍未找到关键字等于K的结点,则查找失败。扫描线性表可以从表尾开始,也可以从表头开始。

  9. 8.2.1 顺序查找(Sequential Search) 3、查找实例 在顺序表L=(3,9,14,21,33,47,55,73,80,97)中查找关键字55的过程图如图8.1示:

  10. 8.2.1 顺序查找(Sequential Search) 4、基于顺序结构的顺序查找算法 (1)类型说明 定义 8.2 typedef struct{     KeyType key; InfoType otherinfo; /*此类型依赖于应用*/ }NodeType; typedef NodeType SeqList[n+1]; /*0号单元用作哨兵*/

  11. 8.2.1 顺序查找(Sequential Search) (2)查找算法 算法 8.1 顺序查找算法一 int SeqSearch(Seqlist R,KeyType K) {/*在顺序表R[1..n]中顺序查找关键字为K的结点,成功时返回结点位置,失败时返回0*/   int i; R[0].key=K; /*设置哨兵于表头*/      for(i=n;R[i].key!=K;i--); /*从表后往前找*/       if(i>0) return i; /*若i为0,表示查找失败,否则R[i]是要找的结点*/ else return 0; }

  12. 8.2.1 顺序查找(Sequential Search) 算法 8.2 顺序查找算法二 int SeqSearch(Seqlist R,KeyType K) {/*在顺序表R[0..n-1]中顺序查找关键字为K的结点,成功返回结点位置,失败返回0*/   int i; R[n].key=K; /*设置哨兵于表尾*/   for(i=0;R[i].key!=K;i++); /*从表后往前找*/   if(i<n) return i; /*若i在0~n-1之间,则R[i]是要找的结点,返回i*/ else return 0; /*否则查找失败,返回0*/ } /*SeqSearch*/

  13. 8.2.1 顺序查找(Sequential Search) (3)算法分析 成功时的顺序查找的平均查找长度: pi=1/n(1≤i≤n)时, ASLsq= (n+…+2+1)/n=(n+1)/2。 查找概率不等,则 pn≥pn-1≥……≥p2≥p1时ASLsq取极小值。 查找概率不定时,改进:每次查找后,将刚找到的记录移至表尾。

  14. 8.2.1 顺序查找(Sequential Search) 对算法的修改:每当查找成功,就将找到的结点和其后继(若存在)结点交换。 顺序查找的优点:算法简单,且对表的结构无任何要求,无论是用向量还是用链表来存放结点,无论结点之间是否按关键字有序,它都同样适用。 顺序查找的缺点:查找效率低,因此,当n较大时不宜采用顺序查找。

  15. 8.2.2 二分查找(Binary Search) 1、存储要求 二分查找:又称折半查找,它是一种效率较高的查找方法。 二分查找要求:线性表是有序表,并且使用向量作为表的存储结构。不失一般性,不妨假设该有序表是递增有序的。 2、二分查找的基本思想 (设R[low..high]是当前的查找区间) (1)确定该区间的中点位置:mid= (low+high)/2

  16. 8.2.2 二分查找(Binary Search) (2)将待查K值与R[mid].key比较: 若相等,则查找成功并返回此位置, 不相等,则须确定新的查找区间,方法如下: 若R[mid].key>K,则新的查找区间是左子表R[1..mid-1]。 若R[mid].key<K,则新的查找区间是右子表[mid+1..n] 。 (3)重复过程(2),直至找到关键字为K的结点,或者直至当前的查找区间为空(即查找失败)时为止。

  17. 8.2.2 二分查找(Binary Search) 3、二分查找算法 算法 8.3 二分查找算法 int BinSearch(SeqList R,KeyType K) { /*在有序表R[1..n]中进行二分查找,成功时返回结点的位置,失败时返回零*/    int low=1,high=n,mid; /*置当前查找区间上、下界的初值*/    while(low<=high) /*当前查找区间R[low..high]非空*/ {  mid=(low+high)/2; if(R[mid].key==K) return mid; /*查找成功返回*/        if(R[mid].key>K) high=mid-1; /*继续在R[low..mid-1]中查找*/        else low=mid+1; /*继续在R[mid+1..high]中查找*/      }    return 0; /*当low>high时表示查找区间为空,查找失败*/ }

  18. 8.2.2 二分查找(Binary Search) 4、二分查找算法的执行过程 例:在序列(3,9,14,21,33,47,55,73,80,97)中查找关键字Key分别是21和70的查找过程如图所示: key=21的查找过程,找到

  19. 8.2.2 二分查找(Binary Search) 4、二分查找算法的执行过程 例:在序列(3,9,14,21,33,47,55,73,80,97)中查找关键字Key分别是21和70的查找过程如图所示: key=70的查找过程,失败

  20. 8.2.2 二分查找(Binary Search) 5、二分查找的性能分析 对于成功的查找,如查找21,二分查找可以用图8.3(a)的判定树来描述其查找过程。比较的次数与判定叉树的层次对应,如查找21共比较了4次,才找到④号元素,而④元素在判定树上的位置正好位于第4层,因此,二分查找的过程与判定树查找一致,因此二分查找的成功查找次数至多为(log2n)+1。

  21. 8.2.2 二分查找(Binary Search) 对于不成功的查找,如查找70,可以用图8.3(b)的判定树来描述其查找过程。比较的次数超出判定叉树的层次时,查找失败,如查找70共比较了5次,从而超过了判定树的深度,因此,二分查找的过程与判定树查找一致,可以证明查找失败时查找次数至多为Г(log2n)+1˥。

  22. 8.2.2 二分查找(Binary Search) 假设查找每个元素的概率相等,即pi=1/n,则平均查找长度为: 因此,二分查找的时间复杂度为O(log2n)。

  23. 8.2.2 二分查找(Binary Search) 6、二分查找的优点和缺点 虽然二分查找的效率高,但是要将表按关键字排序。而排序本身是一种很费时的运算。既使采用高效率的排序方法也要花费O(nlogn)的时间。 二分查找只适用顺序存储结构。为保持表的有序性,在顺序结构里插入和删除都必须移动大量的结点。因此,二分查找特别适用于那种一经建立就很少改动、而又经常需要查找的线性表。 对那些查找少而又经常需要改动的线性表,可采用链表作存储结构,进行顺序查找。链表上无法实现二分查找。

  24. 8.2.3 索引查找(Blocking Search) • 索引顺序查找(Blocking Search)又称分块查找。 • 它是一种性能介于顺序查找和二分查找之间的查找方法。 • 在建立顺序表的同时,建立一个索引项,包括两项:关键字项和指针项。索引表按关键字有序,顺序表则为分块有序。

  25. 8.2.3 索引查找(Blocking Search) 1、索引查找表存储结构 (1)“分块有序”的线性表。 表R[1..n]均分为b块,前b-1块中,每块结点个数为 ,第b块的结点数小于等于s;每一块中的关键字不一定有序,但前一块中的最大关键字必须小于后一块中的最小关键字,即表是“分块有序”的。 (2)索引表 抽取各块中的最大关键字和起始位置构成一个索引表ID[l..b],即:ID[i](1≤i≤b)中存放第i块的最大关键字及该

  26. 8.2.3 索引查找(Blocking Search) 块在表R中的起始位置。由于表R是分块有序的,所以索引表是一个递增有序表。

  27. 8.2.3 索引查找(Blocking Search) 2、查找的基本思想 • 首先查找索引表,确定待查的块。因为是分块有序,所以查找索引表时采用二分查找,以确定待查的结点在哪一块; • 然后在已确定的块中进行顺序查找,由于块内无序,只能用顺序查找。 3. 算法实现 用数组存放待查记录,每个数据元素至少含有关键字域,建立索引表,每个索引表结点含有最大关键字域和指向本块第一个结点的指针。

  28. 8.2.3 索引查找(Blocking Search) 4. 索引顺序查找示例 线性表L=(21,8,17,19,14,31,33,22,25,40,52, 61, 78, 46),在其中查找25的过程,如图8.5所示。

  29. 8.2.3 索引查找(Blocking Search) 5. 算法分析 (1)平均查找长度ASL ①若以二分查找来确定块,分块查找成功时的平均查找长度 ASLblk=ASLbs+ASLsq≈log2(b+1)- 1+(s+1)/2 ≈log2(n/s+1)+s/2 ②若以顺序查找确定块,分块查找成功时的平均查找长度 ASL'blk=(b+1)/2+(s+1)/2=(s2+2s+n)/(2s) (2)块的大小 在实际应用中,分块查找不一定要将线性表分成大大小相等

  30. 8.2.3 索引查找(Blocking Search) 的若干块,可根据表的特征进行分块。例如一个学校的学生登记表,可按系号或班号分块。 (3) 结点的存储结构 各块可放在不同的线性表中,也可将每一块存放在一个单链表中。 (4)索引顺序查找的优点 优点是: ①在表中插入或删除一个记录时,只要找到该记录所属的块,就在该块内进行插入和删除运算。

  31. 8.2.3 索引查找(Blocking Search) ②因块内记录的存放是任意的,所以插入或删除比较容易,无须移动大量记录。 代价是: 增加一个辅助数组的存储空间和初始表分块排序运算。

  32. 8.2.4 线性表查找方法的比较 1、顺序查找 顺序存储和链表存储皆可。优点是算法简单且对表的结构无任何要求;缺点是查找效率低;适用于n较小的表的查找和查找较少但改动较多的表。 2、二分查找 只用于顺序存储结构。优点是查找效率高;缺点是要求线性表按键值有序排列,且只适用顺序存储结构;特别适用于一经建立就很少改动又经常需要查找的线性表。 3、分块查找 顺序存储和链表存储皆可。优点是在表中插入和删除记录时,只要找到该元素所属的块,就在该块内进行插入和删除运算。因块内记录的存放是任意的,故插入和删除较容易,不需移动大量元素;缺点是要增加一个辅助数组的存储空间和将初始表分块排序的运算;适用于有分块特点的记录。

  33. 8.3 动态查找表 • 二叉排序树 • 平衡二叉树 • 2-3树 • B-树和B+树 • 键树

  34. 8.3.1 二叉排序树 1、二叉排序树的定义: 二叉排序树或者是空树,或者是满足如下性质的二叉树: ① 若其左子树非空,则左子树所有结点值均小于根结点的值; ② 若其右子树非空,则右子树所有结点值均大于根结点的值; ③ 左、右子树本身各是一棵二叉排序树。 上述性质简称二叉排序树性质(BST性质),故二叉排序树是满足BST性质的二叉树。

  35. 8.3.1 二叉排序树 二叉排序树(Binary Sort Tree)又称二叉查找(搜索)树(Binary Search Tree)。 2、二叉排序树的特点 ① 二叉排序树中任一结点x,其左(右)子树中任一结点y(若存在)的关键字必小(大)于x的关键字。 ② 二叉排序树中,各结点关键字是唯一的。 ③ 按中序遍历该树所得到的中序序列是一个递增有序序列。

  36. 8.3.1 二叉排序树 2、二叉排序树的特点 例如,如图8.7所示的是二叉排序树,它的中序序列为有序序列:11, 15, 18, 20, 30, 33, 36, 41。

  37. 8.3.1 二叉排序树 3、 二叉排序树的存储结构 定义 8.3 typedef int KeyType; /*假定关键字类型为整数*/ typedef struct node { /*结点类型*/   KeyType key; /*关键字项*/   InfoType data; /*data为记录中其他数据项总称*/   struct node *lchild,*rchild; /*左右孩子指针*/ } BSTNode; typedef BSTNode *BSTree /*BSTree是二叉排序树的类型*/

  38. 8.3.1 二叉排序树 4. 二叉排序树上的运算 (1) 二叉排序树的插入和生成 ①二叉排序树插入新结点的过程 (a)若二叉排序树T为空,则为待插入的关键字key申请一个新结点,并令其为根; (b)若二叉排序树T不为空,则将key和根的关键字比较: (i)若二者相等,则说明树中已有此关键字key,无须插入。 (ii)若key<T->key,则将key插入根的左子树中。 (iii)若key>T->key,则将它插入根的右子树中。 子树中的插入过程与上述的树中插入过程相同。如此进行下去,直到将key作为一个新的叶结点的关键字插入到二叉排序

  39. 8.3.1 二叉排序树 树中,或者直到发现树中已有此关键字为止。 ②二叉排序树插入新结点的递归算法 (算法 8.4) int Insert_BST( BSTree *p, KeyType k,BSTree *f) {/*在以p为根结点的BST中插入关键字k。插入成功返回1,否则返回0*/ if( p==NULL) /*原树为空,新插入的记录为根结点*/ { p=(BSTree *)malloc(sizeof(BSTree)); p->key=k; p->lchild=p->rchild=NULL; return 1; } else if( k==p->key) /*树中存在相同关键字的结点,返回0*/ return 0; else if( k<p->key) return Insert_BST(p->lchild, k,p); /*插入到p的左子树中*/ else return Insert_BST(p->rchild, k,p); } /*插入到p的右子树中*/

  40. 8.3.1 二叉排序树 ③二叉排序树插入新结点的非递归算法(算法 8.5 ) void InsertBST(BSTree *Tptr,KeyType key) { /*若二叉排序树 *Tptr中没有关键字为key,则插入,否则直接返回*/         BSTNode *f,*p=*Tptr; /*p的初值指向根结点*/         while(p) /*查找插入位置*/ { if(p->key==key) return; /*树中已有key,无须插入*/  f=p; /*f保存当前查找的结点*/  if (key<p->key)p=p->lchild /*若key<p->key,则在左子树中查找*/ else p=p->rchild; } /*否则在右子树中查找*/ p=(BSTNode *)malloc(sizeof(BSTNode)); p->key=key; p->lchild=p->rchild=NULL; /*生成新结点*/    if(*TPtr==NULL)   *Tptr=p; /*原树为空,新插入的结点为新的根*/    else /*原树非空*/ if(key<f->key) f->lchild=p ; /* p作为f左孩子插入*/ else f->rchild=p;   }/*或作为f右孩子插入*/

  41. 8.3.1 二叉排序树 ④二叉排序树的生成算法(算法 8.6 ) 从空的二叉排序树开始,每输入一个结点数据,就调用一次插入算法将它插入到当前已生成的二叉排序树中。 BSTree CreateBST(void)  { /*输入一个结点序列,建立一棵二叉排序树,将根结点指针返回*/     BSTree T=NULL; /*初始时T为空树*/     KeyType key; scanf("%d",&key); /*读人一个关键字*/     while(key) /*假设key=0是输人结束标志*/      { InsertBST(&T,key); /*将key插入二叉排序树T*/       scanf("%d",&key);/*读入下一关键字*/      }     return T; /*返回建立的二叉排序树的根指针*/   }

  42. 8.3.1 二叉排序树 ⑤二叉排序树的(输入实例(5,2,7,3,1,14,9))

  43. 8.3.1 二叉排序树 (2) 二叉排序树的查找 • 二叉排序树的查找十分方便, 其平均查找长度明显小于一般的二叉树。 • 二叉排序树的查找从根结点出发, 当访问到树中某个结点时, 如果该结点的关键字值等于给定的关键字值, 就宣布查找成功。 反之, 如果该结点的关键字值大(小)于已给的关键字值, 下一步就只需考虑查找左(右)子树了。 换言之, 每次只需查找左或右子树的一枝便够了, 效率明显提高。

  44. 8.3.1 二叉排序树 ①二叉排序树查找递归算法(算法 8.7) BSTNode *SearchBST(BSTree T,KeyType key) {/*在二叉排序树T上查找关键字为key的结点,成功时返回该结点位置,否则返回NUll*/ if(T==NULL||key==T->key) /*递归的终结条件*/ return T; /*T为空,查找失败;否则成功,返回找到结点位置*/ if(key<T->key) return SearchBST(T->lchild,key); /*继续在左子树中查找*/ else return SearchBST(T->rchild,key);/*继续在右子树中查找*/    }

  45. 8.3.1 二叉排序树 ② 算法分析:在二叉排序树上进行查找时的平均查找长度和二叉树的形态有关。 • 在最坏情况下,二叉排序树是通过把一个有序表的n个结点依次插入而生成的,此时所得的二叉排序树蜕化为一棵深度为n的单支树,它的平均查找长度和线性表的顺序查找相同,亦是(n+1)/2。 • 在最好情况下,二叉排序树在生成的过程中,树的形态比较匀称,最终得到的是一棵形态与二分查找的判定树相似的二叉排序树,此时它的平均查找长度大约是log2n。 • 插入、删除和查找算法的时间复杂度均为O(log2n)。

  46. 8.3.1 二叉排序树 (3)二叉排序树的删除的步骤 ①进行查找。查找时,令p指向当前访问到的结点,parent指向其双亲(其初值为NULL)。若树中找不到被删结点则返回0,否则被删结点是*p。 ②删去*p。删*p时,应将*p的子树(若有)仍连接在树上且保持BST性质不变。按*p的孩子数目分三种情况进行处理。

  47. 8.3.1 二叉排序树 *p是叶子(即它的孩子数为0)。无须连接*p的子树,只需将*p的双亲*parent中指向*p的指针域置空即可。

  48. 8.3.1 二叉排序树 • *p只有一个孩子*child。只需将*child和*p的双亲直接连接后,即可删去*p。 • 注意:*p既可能是*parent的左孩子也可能是其右孩子,而*child可能是*p的左孩子或右孩子,故共有4种状态。

  49. 8.3.1 二叉排序树 • *p有两个孩子。先令q=p,将被删结点的地址保存在q中;然后找*q的中序后继*p,并在查找过程中仍用parent记住*p的双亲位置。*q的中序后继*p一定是*q的右子树中最左下的结点,它无左子树。因此,可以将删去*q的操作转换为删去的*p的操作,即在释放结点*p之前将其数据复制到*q中,就相当于删去了*q。

  50. 8.3.2 平衡二叉树 1.平衡二叉树的定义: 形态匀称的二叉排序树称为平衡二叉树(balanced binary tree)。 其严格定义是:一棵空树是平衡二叉树; 若T是一棵非空二叉树, 其左、 右子树为TL和TR,令hl和hr分别为左、右子树的深度,相应地定义hl-hr为二叉平衡树的平衡因子(balance factor)。当且仅当①|hl-hr|≤1时,即任一结点的平衡因子的绝对值都不大于1,则T是平衡二叉树, ②TL、TR都是平衡二叉树。如图8.13所示。 平衡二叉树上所有结点的平衡因子可能是-1, 0, 1。

More Related