660 likes | 727 Views
第8章 查找. 8.1 查找的基本概念 查找( Search ),就是在数据集合中寻找满足某种条件的数据对象。 数据集合,是由同一类型的数据对象构成。每个数据对象,都有若干属性,其中,有一个受到关注的属性,它可以唯一地标识每个对象,那么我们将这个属性称为关键字( Key )。 查找的概念可简化为:根据给定的某个值,在数据集合中查找一个关键字等于给定值的数据对象。查找的结果就是确定数据对象在数据集合的结构中的位置。. 第8章 查找. 通常有两种可能:
E N D
第8章 查找 8.1 查找的基本概念 查找(Search),就是在数据集合中寻找满足某种条件的数据对象。 数据集合,是由同一类型的数据对象构成。每个数据对象,都有若干属性,其中,有一个受到关注的属性,它可以唯一地标识每个对象,那么我们将这个属性称为关键字(Key)。 查找的概念可简化为:根据给定的某个值,在数据集合中查找一个关键字等于给定值的数据对象。查找的结果就是确定数据对象在数据集合的结构中的位置。
第8章 查找 通常有两种可能: 一种可能是查找成功,即找到符合条件的数据对象,作为结果,这时我们可指出该对象在数据集合的结构中所处的位置,进一步可获得对象的全部信息; 另一种可能是数据集合中不存在符合条件的数据对象,则查找失败,作为结果,这时返回的将是一个“空”对象,或者失败位置。
第8章 查找 对于不同的基本数据结构(表、树、图),需要设计和选择不同的查找方法。而查找的算法很大程度上决定了查找的效率。 查找算法的基本操作是将数据对象的关键字与给定的值进行比较,因此,可定义关键字与给定值进行比较的数量的平均值,作为算法度量的依据,这就是平均查找长度(Average Search Length,简记ASL),即 其中,Pi为查找第i个数据对象(即给定值与这结构中第i个数据对象的关键字相等)的概率,Ci为找到第i个数据对象所需的关键字与给定值的比较次数。
第8章 查找 8.2 线性表的查找 基于线性表的查找是最简单的查找。查找算法就是根据给定值,在线性表中进行查找,直到找到键值(key)等于给定值的那个结点并指出其存放位置,或者确定在线性表中找不到这样的结点。
第8章 查找 8.2.1 顺序查找 顺序查找是一种最基本且最直观的查找方法。它的算法思想是:对于线性表中的所有结点,逐个将其关键字与给定值进行比较,若某个结点的关键字与给定值相等,则查找成功,返回所找的结点的位置;若将所有结点的关键字与给定值作比较后仍未有相等者,则查找失败。 对于这种查找方法,结点在线性表中可任意排列,不必考虑它们关键字的大小。
第8章 查找 #define MAXARRAY 100 typedef struct node { int key; // 结点的关键字 char otherdata[80]; // 结点的其他字段 } NODE; NODE a[MAXARRAY]; intn;
第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章 查找 算法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; }
第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; }
第8章 查找 顺序查找的平均查找长度有 假定线性表中所有结点都有相同的查找概率, 即对于一切i都有 ,那么
第8章 查找 8.2.2 二分查找 对于顺序存储的有序表,通常可用二分查找(Binary Search)的方法来操作。即考察线性表中间的结点,其关键字若与给定值相等,则查找成功;若大于给定值,则扔掉后半个线性表,在前半个线性表中再实施二分查找;若小于给定值,则下一步二分查找在后半个线性表中进行。一直到查找成功,或确定关键字等于给定值的结点不存在。
第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)继续。
第8章 查找 #define MAXARRAY 100 typedef struct node { int key; // 结点的关键字 char otherdata[80]; // 结点的其他字段 } NODE; NODE a[MAXARRAY]; int n;
第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; }
第8章 查找 假设各结点的查找概率相同,且线性表中的结点数为 (对于 的情况,不妨将n放大到 ),有,则平均查找长度为
第8章 查找 当 n很大时,平均 另外,我们知道,假若在顺序存储的具有n 个结点的有序表中使用二分查找方法,最长查找长度为j,而 n < 那么,最长查找长度 j 就是 因此,二分查找方法的平均查找长度与其最大查找长度相接近,
第8章 查找 8.2.3 分块查找 分块查找的前提,就是要将线性表分成若干块,处于前、后不同两个块的结点,其关键字须存在递增(或递减)的关系。也就是说,在某个递增(或递减)的分块线性表中,第一块中任一结点的关键字小于(或大于)第二块中所有结点的关键字,第二块任一结点的关键字小于(或大于)第三块中所有结点的关键字,……以此类推。 显然,每块的结点有自己的起始位置,另外,在这块存放的结点中,应有一个最大关键字的结点。
第8章 查找 将每块的起始位置(下标或指针)和最大的关键字构成的二元组,按分块排列的次序建立一个线性表,称为索引表(index list)。由于块间的结点是有序的,所以索引表是一个递增(或递减)的有序表。(详见pp.175-176) 可以看出,结点在块间排列有序。现将每块的起始位置下标和块内结点的最大关键字构成的二元组,按块的排列次序组成一个索引表。
第8章 查找 对于给定值 v ,使用分块查找的方法找出关键字与 v 相等的结点,要经过两个阶段: (1)在索引表中查找该结点所在的块; (2)在块内查找满足条件的结点。 具体地说,首先可使用二分查找方法或者顺序查找方法,在索引表中确定如满足条件的结点存在时它应该在哪一块中,由此获得该块在线性表中的起始位置,然后再在相应的块中顺序查找,便可得到查找结果。
第8章 查找 平均查找长度为 假若具有 n 个结点的线性表被均分为m块,那么每块有t=n/m个结点,若查找概率相同,分块查找的平均查找长度为
第8章 查找 假若具有 n 个结点的线性表被均分为 m块,那么每块有 t=n/m 个结点,设查找概率相同,现在第一阶段使用二分查找,那么平均需要比较log2(m+1)-1个结点;而在第二阶段使用顺序查找,那平均需要比较 ,这种分块查找的平均查找长度为
第8章 查找 8.3 树结构的查找 8.3.1 二叉排序树(Binary Search Tree, 简称BST) 二叉排序树与二叉查找法 二叉排序树是一棵二叉树,且需同时满足: (1)如果根结点的左子树非空,那么左子树中所有结点的关键字都小于根结点的关键字; (2)如果根结点的右子树非空,那么右子树中所有结点的关键字都大于根结点的关键字; (3)根结点的左、右子树也都是二叉排序树。 (p. 177 图8-6)
第8章 查找 二叉排序树的结点的类型结构及结点指针说明如下: typedef struct node { int key; // 结点的关键字 char otherdata[80]; // 结点的其他字段 struct node *lchild, *rchild; } NODE; NODE *root; // 二叉排序树根结点的指针 NODE *p; // 二叉排序树一般结点的指针
第8章 查找 二叉查找算法的基本步骤为: (1)令p=root。 (2)如果p为空;那么查找失败,返回;否则,执行下一步。 (3)如果p->key等于v,那么查找成功,返回;否则,执行下一步。 (4)如果v小于p->key,那么,令p=p->lchild;否则,令p=p->rchild。 (5)转到(2)。
第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)); }
第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; } }
第8章 查找 二叉排序树的插入 往二叉排序树中插入一个新结点,必须保证完成插入操作后该树仍是一棵二叉排序树。具体做法为:首先调用函数BSTSearch2(),如果关键字为 a 的结点已经在树 root 中,则不能再插入;否则,插在适当的位置上。插入成功与否,返回一个状态值。
第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; }
第8章 查找 如果指针 r 指向一棵二叉排序树,变量 a 存放插入结点的关键字值,那么,可使用下面语句来实现插入 if (!BSTInsert(&r, a)) cout << “插入失败!” << endl;
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},生成一棵二叉排序树。
第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
第8章 查找 一个无序序列通过反复调用BSTInsert()函数,可以生成一棵二叉排序树;而再通过中序遍历这棵二叉排序树的结点,就可以得到一个有序序列。 显然,介于无序序列和有序序列之间的过程就是构造二叉排序树,这正是一种排序的方法。
第8章 查找 二叉排序树结点的删除 中序遍历二叉排序树上的结点,可以得到一个有序序列。在二叉排序树上删除一个结点之后,还继续让它保持二叉排序树的特性,则采用以下做法: (1)假如被删结点有左子树,那么这左子树的根结点顶替到被删结点的位置,而左子树中其余结点保持原来关系,右子树成为那棵左子树按中序遍历最后一个结点的右子树。 (2)假如被删结点无左子树,那么它的右子树的根结点顶替到被删结点的位置,而右子树中其余结点保持原来关系。
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
第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; } //根的指针指向左子树的根结点
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 }
第8章 查找 假若指针r指向一棵二叉排序树,a是所要删除的结点的关键字值,那么可使用下列语句实现删除操作: if (!BSTDelete(&r, a)) cout << “删除操作失败” << endl;
第8章 查找 4 二叉排序树平均查找长度的分析 平均查找长度为 对于满树a,平均查找长度为 对于类似线性表的树c,平均查找时间为
第8章 查找 8.3.2 平衡的二叉排序树 既要将结点完全靠近树根,又要在经常的插入和删除中树的形态仍保持丰满如初,这简直是不可能的事。 于是,Adelson Velskii和Landis发明了一种结点平衡地分布、尽可能地趋于树根,而且容易进行结点的插入和删除操作的代用品,那就是平衡二叉树(Balanced Binary Tree),也称AVL树。
第8章 查找 定义二叉树的高度 设T是一棵二叉树,记号h(T)表示树T的高度,且满足: (1)如果T是一棵空的二叉树,那么h(T)=-1; (2)如果T是一棵非空的二叉树,Tl和Tr分别是T 的根结点的左子树和右子树,那么 h(T)=max{h(Tl),h(Tr)}+1。
第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)。 显然,一棵空二叉树是平衡二叉树。平衡二叉树的左、右子树也是平衡二叉树。
第8章 查找 -1 -1 -1 1 -2 0 -1 0 0 1 0 0 0 0 (a)平衡二叉树 (b)非平衡二叉树
第8章 查找 假如树T既是平衡二叉树,又是二叉排序树,那么树T是平衡的二叉排序树(Balanced Binary Search Tree,简记BBST)。 可以证明,n个结点的平衡二叉树,其高度不超过 在平衡的二叉排序树上进行查找,其时间复杂度为O(log2 n)。 由此推断,平衡的二叉排序树具有较小的平均查找长度。
第8章 查找 平衡的二叉排序树的结点的类型结构定义为: typedef struct BBSTnode { int key; int bf; // bf存放结点的平衡因子 char otherdata[80]; struct BBSTnode *lchild, *rchild; } BNODE; BNODE *root; // 指针root指向平衡的二叉排序树的根结点
第8章 查找 平衡的二叉排序树在进行插入操作后,虽然还是一棵二叉排序树,但是,它的平衡特性有可能被破坏了。 假如某结点的子树高度有差异,由于结点在平衡二叉树上,那么两棵子树的高度只能相差1。现将结点插在高子树的一侧,使其高度再增加1,那么该结点的平衡因子就会变成 2 或者 -2。此时,这棵树不再是一棵平衡二叉树了。
第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)。
-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
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)左旋
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)