1 / 66

第8章 查找

第8章 查找. 8.1  查找的基本概念 查找( Search ),就是在数据集合中寻找满足某种条件的数据对象。 数据集合,是由同一类型的数据对象构成。每个数据对象,都有若干属性,其中,有一个受到关注的属性,它可以唯一地标识每个对象,那么我们将这个属性称为关键字( Key )。 查找的概念可简化为:根据给定的某个值,在数据集合中查找一个关键字等于给定值的数据对象。查找的结果就是确定数据对象在数据集合的结构中的位置。. 第8章 查找. 通常有两种可能:

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 查找的基本概念 查找(Search),就是在数据集合中寻找满足某种条件的数据对象。 数据集合,是由同一类型的数据对象构成。每个数据对象,都有若干属性,其中,有一个受到关注的属性,它可以唯一地标识每个对象,那么我们将这个属性称为关键字(Key)。 查找的概念可简化为:根据给定的某个值,在数据集合中查找一个关键字等于给定值的数据对象。查找的结果就是确定数据对象在数据集合的结构中的位置。

  2. 第8章 查找 通常有两种可能: 一种可能是查找成功,即找到符合条件的数据对象,作为结果,这时我们可指出该对象在数据集合的结构中所处的位置,进一步可获得对象的全部信息; 另一种可能是数据集合中不存在符合条件的数据对象,则查找失败,作为结果,这时返回的将是一个“空”对象,或者失败位置。

  3. 第8章 查找 对于不同的基本数据结构(表、树、图),需要设计和选择不同的查找方法。而查找的算法很大程度上决定了查找的效率。 查找算法的基本操作是将数据对象的关键字与给定的值进行比较,因此,可定义关键字与给定值进行比较的数量的平均值,作为算法度量的依据,这就是平均查找长度(Average Search Length,简记ASL),即 其中,Pi为查找第i个数据对象(即给定值与这结构中第i个数据对象的关键字相等)的概率,Ci为找到第i个数据对象所需的关键字与给定值的比较次数。

  4. 第8章 查找 8.2 线性表的查找 基于线性表的查找是最简单的查找。查找算法就是根据给定值,在线性表中进行查找,直到找到键值(key)等于给定值的那个结点并指出其存放位置,或者确定在线性表中找不到这样的结点。

  5. 第8章 查找 8.2.1 顺序查找 顺序查找是一种最基本且最直观的查找方法。它的算法思想是:对于线性表中的所有结点,逐个将其关键字与给定值进行比较,若某个结点的关键字与给定值相等,则查找成功,返回所找的结点的位置;若将所有结点的关键字与给定值作比较后仍未有相等者,则查找失败。 对于这种查找方法,结点在线性表中可任意排列,不必考虑它们关键字的大小。

  6. 第8章 查找 #define MAXARRAY 100 typedef struct node { int key; // 结点的关键字 char otherdata[80]; // 结点的其他字段 } NODE; NODE a[MAXARRAY]; intn;

  7. 第8章 查找 算法1 int SeqSearch1(NODE a[ ], int n, int v) { int i; for (i=0; i<n && a[i].key!=v; i++); if (i<n) return i; return -1; }

  8. 第8章 查找 算法2 int SeqSearch2(NODE a[ ], int n, int v) { int i; a[n].key=v; for (i=0; a[i].key!=v; i++); if (i<n) return i; return -1; }

  9. 第8章 查找 算法3 typedef struct lnode { int key; // 结点的关键字 char otherdata[80]; // 结点的其他字段(属性) struct node * link; // 指向下个结点的指针 } LNODE; NODE * head; LNODE * LinSearch (LNODE * head, int v) { for( ; head!=NULL && head->key!=v; head=head->link); return head; }

  10. 第8章 查找 顺序查找的平均查找长度有 假定线性表中所有结点都有相同的查找概率, 即对于一切i都有 ,那么

  11. 第8章 查找 8.2.2 二分查找 对于顺序存储的有序表,通常可用二分查找(Binary Search)的方法来操作。即考察线性表中间的结点,其关键字若与给定值相等,则查找成功;若大于给定值,则扔掉后半个线性表,在前半个线性表中再实施二分查找;若小于给定值,则下一步二分查找在后半个线性表中进行。一直到查找成功,或确定关键字等于给定值的结点不存在。

  12. 第8章 查找 对于给定值v,采用二分查找方法在数组a[ ]中查找关键字为v的结点,具体操作为: (1)令low=0, high=n-1; (2)若low>high,则查找失败,返回-1; 否则,进入下一步; (3)令mid=(low+high)/2; (4)若a[mid].key==v, 则查找成功,返回mid;否则,进入下一步; (5)若a[mid].key>v,则令high=mid-1;否则,令low=mid+1; (6)转向(2)继续。

  13. 第8章 查找 #define MAXARRAY 100 typedef struct node { int key; // 结点的关键字 char otherdata[80]; // 结点的其他字段 } NODE; NODE a[MAXARRAY]; int n;

  14. 第8章 查找 int BiSeqSearch(NODE a[ ], int n, int v) { int low, high, mid; low=0; high=n-1; while (low<=high) { mid=(low+high)/2; if (a[mid].key==v) return mid; if (a[mid].key>v) high=mid-1; else low=mid+1; } return -1; }

  15. 第8章 查找 假设各结点的查找概率相同,且线性表中的结点数为 (对于 的情况,不妨将n放大到 ),有,则平均查找长度为

  16. 第8章 查找 当 n很大时,平均 另外,我们知道,假若在顺序存储的具有n 个结点的有序表中使用二分查找方法,最长查找长度为j,而 n < 那么,最长查找长度 j 就是 因此,二分查找方法的平均查找长度与其最大查找长度相接近,

  17. 第8章 查找 8.2.3 分块查找 分块查找的前提,就是要将线性表分成若干块,处于前、后不同两个块的结点,其关键字须存在递增(或递减)的关系。也就是说,在某个递增(或递减)的分块线性表中,第一块中任一结点的关键字小于(或大于)第二块中所有结点的关键字,第二块任一结点的关键字小于(或大于)第三块中所有结点的关键字,……以此类推。 显然,每块的结点有自己的起始位置,另外,在这块存放的结点中,应有一个最大关键字的结点。

  18. 第8章 查找 将每块的起始位置(下标或指针)和最大的关键字构成的二元组,按分块排列的次序建立一个线性表,称为索引表(index list)。由于块间的结点是有序的,所以索引表是一个递增(或递减)的有序表。(详见pp.175-176) 可以看出,结点在块间排列有序。现将每块的起始位置下标和块内结点的最大关键字构成的二元组,按块的排列次序组成一个索引表。

  19. 第8章 查找 对于给定值 v ,使用分块查找的方法找出关键字与 v 相等的结点,要经过两个阶段: (1)在索引表中查找该结点所在的块; (2)在块内查找满足条件的结点。 具体地说,首先可使用二分查找方法或者顺序查找方法,在索引表中确定如满足条件的结点存在时它应该在哪一块中,由此获得该块在线性表中的起始位置,然后再在相应的块中顺序查找,便可得到查找结果。

  20. 第8章 查找 平均查找长度为 假若具有 n 个结点的线性表被均分为m块,那么每块有t=n/m个结点,若查找概率相同,分块查找的平均查找长度为

  21. 第8章 查找 假若具有 n 个结点的线性表被均分为 m块,那么每块有 t=n/m 个结点,设查找概率相同,现在第一阶段使用二分查找,那么平均需要比较log2(m+1)-1个结点;而在第二阶段使用顺序查找,那平均需要比较 ,这种分块查找的平均查找长度为

  22. 第8章 查找 8.3 树结构的查找 8.3.1 二叉排序树(Binary Search Tree, 简称BST) 二叉排序树与二叉查找法 二叉排序树是一棵二叉树,且需同时满足: (1)如果根结点的左子树非空,那么左子树中所有结点的关键字都小于根结点的关键字; (2)如果根结点的右子树非空,那么右子树中所有结点的关键字都大于根结点的关键字; (3)根结点的左、右子树也都是二叉排序树。 (p. 177 图8-6)

  23. 第8章 查找 二叉排序树的结点的类型结构及结点指针说明如下: typedef struct node { int key; // 结点的关键字 char otherdata[80]; // 结点的其他字段 struct node *lchild, *rchild; } NODE; NODE *root; // 二叉排序树根结点的指针 NODE *p; // 二叉排序树一般结点的指针

  24. 第8章 查找 二叉查找算法的基本步骤为: (1)令p=root。 (2)如果p为空;那么查找失败,返回;否则,执行下一步。 (3)如果p->key等于v,那么查找成功,返回;否则,执行下一步。 (4)如果v小于p->key,那么,令p=p->lchild;否则,令p=p->rchild。 (5)转到(2)。

  25. 第8章 查找 递归的二叉查找算法。 NODE *BSTSearch1(NODE *root, int v) { if ((root==NULL)||(root->key==v)) return root; if (root->key > v) return (BSTSearch1(root->lchild, v)); else return (BSTSearch1(root->rchild, v)); }

  26. 第8章 查找 非递归的二叉查找算法 : void BSTSearch2(NODE *root, int v, NODE **pp, NODE **pq) { *pp=NULL; *pq=root; while (*pq!=NULL) { if ((*pq)->key==v) return; *pp=*pq; if (v<(*pq)->key) *pq=(*pq)->lchild; else *pq=(*pq)->rchild; } }

  27. 第8章 查找

  28. 第8章 查找 二叉排序树的插入 往二叉排序树中插入一个新结点,必须保证完成插入操作后该树仍是一棵二叉排序树。具体做法为:首先调用函数BSTSearch2(),如果关键字为 a 的结点已经在树 root 中,则不能再插入;否则,插在适当的位置上。插入成功与否,返回一个状态值。

  29. 第8章 查找 #define TRUE 1 #define FALSE 0 int BSTInsert(NODE **proot, int a) { NODE *p; *q, *r; BSTSearch2(*proot, a, &p, &q); if (q!=NULL) return FALSE; r=new NODE; r->key=a; r->lchild = r->rchild = NULL; if (p==NULL) *proot=r; else if (p->key>a) p->lchild=r; else p->rchild=r; return TRUE; }

  30. 第8章 查找 如果指针 r 指向一棵二叉排序树,变量 a 存放插入结点的关键字值,那么,可使用下面语句来实现插入 if (!BSTInsert(&r, a)) cout << “插入失败!” << endl;

  31. 6 6 第8章 查找 6 3 3 8 (a)空树 (b)插入6 (c)插入3 (d)插入8 6 6 6 3 8 3 8 3 8 7 5 7 5 7 (e)插入7 (f)插入5 (g)舍弃8 从空树出发,根据结点关键字的序列{6,3,8,7,5,8,4,1,9},生成一棵二叉排序树。

  32. 第8章 查找 6 6 6 3 8 3 3 8 8 5 7 1 5 7 1 5 7 9 从空树出发,根据结点关键字的序列{6,3,8,7,5,8,4,1,9},生成一棵二叉排序树。 4 4 4 (h)插入4 (j)插入9 (i)插入1

  33. 第8章 查找 一个无序序列通过反复调用BSTInsert()函数,可以生成一棵二叉排序树;而再通过中序遍历这棵二叉排序树的结点,就可以得到一个有序序列。 显然,介于无序序列和有序序列之间的过程就是构造二叉排序树,这正是一种排序的方法。

  34. 第8章 查找 二叉排序树结点的删除 中序遍历二叉排序树上的结点,可以得到一个有序序列。在二叉排序树上删除一个结点之后,还继续让它保持二叉排序树的特性,则采用以下做法: (1)假如被删结点有左子树,那么这左子树的根结点顶替到被删结点的位置,而左子树中其余结点保持原来关系,右子树成为那棵左子树按中序遍历最后一个结点的右子树。 (2)假如被删结点无左子树,那么它的右子树的根结点顶替到被删结点的位置,而右子树中其余结点保持原来关系。

  35. 2 2 1 6 1 3 第8章 查找 3 8 5 5 7 9 8 (a)删除关键字为6的结点,运用第1种做法 7 9 8 8 2 9 5 9 5 4 7 (b) 删除关键字为2的结点,运用第2种做法 4 7

  36. 第8章 查找 #define TRUE 1 #define FALSE 0 int BSTDelete(NODE **proot, int a) // 删除a结点 { NODE *p, *q, *r; BSTSearch2(*proot, a, &p, &q); //找a,由q指向它;p指父结点 if (q==NULL) return FALSE; //关键字为a的结点不在树中 if (p==NULL) //a结点就是根结点 if (q->lchild==NULL) //根结点无左子树 *proot=q->rchild; //将右子树作为新的二叉排序树 else { r=q->lchild; //根结点有左子树 while (r->rchild!=NULL) //寻找左子树按中序最后结点 r=r->rchild; r->rchild=q->rchild; //将根的右子树作为最后结点的右子树 *proot=q->lchild; } //根的指针指向左子树的根结点

  37. else if (q->lchild==NULL) // a结点不是根结点,且无左子树 if (q==p->lchild) //a结点是父结点的左子结点 p->lchild=q->rchild; //a的右子树作为a的父结点的左子树 else //a结点是父结点的右子结点 p->rchild=q->rchild; //a的右子树作为a的父结点的右子树 else { r=q->lchild; //a结点有左子树 while (r->rchild!=NULL) //寻找左子树按中序遍历的最后结点 r=r->child; r->rchild=q->rchild; //将a的右子树作为最后结点的右子树 if (q==p->lchild) //a结点是父结点的左子结点 p->lchild=q->lchild; //a的左子结点作为父结点的左子结点 else //a结点是父结点的右子结点 p->rchild=q->lchild; //a的左子结点作为父结点的右子结点 } free(q); //释放关键字为a的被删结点 return TRUE; //删除操作成功, 返回TRUE }

  38. 第8章 查找 假若指针r指向一棵二叉排序树,a是所要删除的结点的关键字值,那么可使用下列语句实现删除操作: if (!BSTDelete(&r, a)) cout << “删除操作失败” << endl;

  39. 第8章 查找 4 二叉排序树平均查找长度的分析 平均查找长度为 对于满树a,平均查找长度为 对于类似线性表的树c,平均查找时间为

  40. 第8章 查找 8.3.2 平衡的二叉排序树 既要将结点完全靠近树根,又要在经常的插入和删除中树的形态仍保持丰满如初,这简直是不可能的事。 于是,Adelson Velskii和Landis发明了一种结点平衡地分布、尽可能地趋于树根,而且容易进行结点的插入和删除操作的代用品,那就是平衡二叉树(Balanced Binary Tree),也称AVL树。

  41. 第8章 查找 定义二叉树的高度 设T是一棵二叉树,记号h(T)表示树T的高度,且满足: (1)如果T是一棵空的二叉树,那么h(T)=-1; (2)如果T是一棵非空的二叉树,Tl和Tr分别是T 的根结点的左子树和右子树,那么 h(T)=max{h(Tl),h(Tr)}+1。

  42. 第8章 查找 定义树中结点的平衡因子(balance factor) 设 k 是二叉树 T 的结点,Tkl和Tkr分别是结点 k 的左子树和右子树,那么结点 k 的平衡因子 bf(k)= h(Tkr) - h(Tkl) 平衡二叉树的定义 如果二叉树T中任意结点k,都有 |bf(k)|≤1 即所有结点的右子树和左子树的高度最多相差1,那么称 T 是一棵平衡二叉树(Balanced Binary Tree)。 显然,一棵空二叉树是平衡二叉树。平衡二叉树的左、右子树也是平衡二叉树。

  43. 第8章 查找 -1 -1 -1 1 -2 0 -1 0 0 1 0 0 0 0 (a)平衡二叉树 (b)非平衡二叉树

  44. 第8章 查找 假如树T既是平衡二叉树,又是二叉排序树,那么树T是平衡的二叉排序树(Balanced Binary Search Tree,简记BBST)。 可以证明,n个结点的平衡二叉树,其高度不超过 在平衡的二叉排序树上进行查找,其时间复杂度为O(log2 n)。 由此推断,平衡的二叉排序树具有较小的平均查找长度。

  45. 第8章 查找 平衡的二叉排序树的结点的类型结构定义为: typedef struct BBSTnode { int key; int bf; // bf存放结点的平衡因子 char otherdata[80]; struct BBSTnode *lchild, *rchild; } BNODE; BNODE *root; // 指针root指向平衡的二叉排序树的根结点

  46. 第8章 查找 平衡的二叉排序树在进行插入操作后,虽然还是一棵二叉排序树,但是,它的平衡特性有可能被破坏了。 假如某结点的子树高度有差异,由于结点在平衡二叉树上,那么两棵子树的高度只能相差1。现将结点插在高子树的一侧,使其高度再增加1,那么该结点的平衡因子就会变成 2 或者 -2。此时,这棵树不再是一棵平衡二叉树了。

  47. 第8章 查找 8 0 8 1 -1 5 11 1 -1 5 11 2 2 0 15 0 2 0 -1 15 12 0 (a) (b) 在平衡的二叉排序树(a)中插入结点12后,结点11的平衡因子(在结点方格的旁边)由1变成为2,破坏了平衡的二叉排序树的特性(b)。

  48. -2 -2 -1 +1 归纳插入后破坏平衡的情况(四种) 第8章 查找 h h -1 h+1 h h (a)LL h h-1 +2 (b)LR -1 +2 h +1 +1 h h h-1 h h h+1 (c)RL (d)RR

  49. A -2 B 0 第8章 查找 B -1 A 0 h+1 h h+1 h T2 T0 h h (a)右旋 T0 T1 T1 T2 A +2 B 0 B +1 A 0 h h+1 h h+1 T0 h h T2 T1 T2 T0 T1 (d)左旋

  50. A -2 C 0 B +1 B 0 A +1 第8章 查找 h C -1 h T3 h h h-1 h A -2 h h-1 T0 T0 T1 T2 T3 C -2 T1 T2 h (c) B 0 (a) h-1 T3 A,C 右旋 h h T2 B,C 左旋 T0 T1 (b)

More Related