710 likes | 919 Views
第 6 单元 查找. 计算机软件基础 The software basic of computer 主讲:刘志强 西安交通大学 计算机教学实验中心. 教学目标. 了解有关查找的 基本概念 查找的主要算法. 教学要求. 通过本单元的学习,了解、掌握有关查找的: 基本概念 查找、平均查找长度 主要查找算法 顺序查找、折半查找、分块查找 树表查找、哈希查找. 本单元涉及的内容. 第 3 章 3.1 什么是查找 3.2 顺序表查找 3.3 树表查找 3.4 哈希查找 P90 ~P102. 一、基本概念. 查找 查找表 静态查找
E N D
第6单元 查找 计算机软件基础 The software basic of computer 主讲:刘志强 西安交通大学 计算机教学实验中心
教学目标 • 了解有关查找的 • 基本概念 • 查找的主要算法
教学要求 通过本单元的学习,了解、掌握有关查找的: • 基本概念 • 查找、平均查找长度 • 主要查找算法 • 顺序查找、折半查找、分块查找 • 树表查找、哈希查找
本单元涉及的内容 • 第3章 • 3.1 什么是查找 • 3.2 顺序表查找 • 3.3 树表查找 • 3.4 哈希查找 • P90~P102
一、基本概念 • 查找 • 查找表 • 静态查找 • 动态查找 • 平均查找长度
查找 • 查找就是在给定的DS中找出满足某种条件的结点;若存在这样的结点,查找成功;否则,查找失败。 • 查找表是一组待查数据元素的集合。 • 静态查找是仅仅进行查询和检索操作,不改变查找表中数据元素间的逻辑关系的查找。 • 动态查找 是除了进行查询和检索操作外,还对查找表进行插入、删除操作的查找,动态地改变查找表中数据元素之间的逻辑关系。
n ASL = Pi* Ci i=1 n Pi = 1 i=1 平均查找长度 • 平均查找长度 (ASL-Average Search Length) 在查找过程中,对每个结点记录中的关键字要进行反复比较,以确定其位置。因此,与关键字进行比较的平均次数,就成为平均查找长度。它是用来评价一个算法好坏的一个依据。 • 对含有n个数据元素的查找表,查找成功时的平均查找长度为: 其中:Pi 为查找表中第i个数据元素的概率,且 Ci为查找第i个数据元素时需比较的次数。 显然,Ci随查找过程及DS的不同而各异。
二、主要查找算法 • 顺序查找 • 折半查找 • 分块查找 • 树表查找 • 哈希查找
1、顺序查找 • 算法思想: 最古老的算法。从第1个元素到最后1个元素,逐个进行比较。 • 顺序查找是最简单、最普通的查找方法。 • 查找表的存储结构: • 既适用于顺序存储结构 • 也适用于链式存储结构
算法描述 • 查找操作步骤: • step1从第1个元素开始查找; • step2用待查关键字值与各结点(记录)的关键字值逐个进行比较;若找到相等的结点,则查找成功;否则,查找失败。
顺序查找算法框图 开始 seq_search(A,n,key) A 待查表 n 元素个数 key 要找的值 i=0 查找key的循环 i++ Y i<n&&A[i]!=key? N N Y A[i]==key? 显示“查找失败” 显示“查找成功” 返回
顺序查找算法3-1 seq_search(item , n , key ) int *item ,n , key ; { int i=0 ; while ( i < n && item[i] != key ) i++; /* 查找key的循环 */ if (item[i]= = key ) { printf(“查找成功 !\n”); return (i); } else { printf(“查找失败 !\n”); return (-1);} } 示例
改进顺序查找算法框图 开始 s_search_a(A,n,key) i=0 ; A[n]=key 设置哨兵 查找key的循环 i++ Y A[i]!=key? N N Y i < n? 显示“查找失败” 显示“查找成功” 返回
顺序查找算法3-2(改进算法) seq_search_adv(item , n , key ) int *item ,n , key ; { int i=0 ; item[n]=key ; /* 设置哨兵 */ while ( item[i]!= key ) i++; /* 查找key */ if ( i< n ) /* 如果 i = n,没找到 */ { printf(“查找成功 !\n”); return (i); } else { printf(“查找失败 !\n”); return (-1); } } 注: 顺序查找算法中,执行频率最高的是while语句,改进后,可以节省近一半的时间。
顺序查找算法主程序 #include “stdio.h” #define n 7 main() { int key,loc=0; int a[10]={43,15,21,37,2,5,82}; printf(“Input key:”); scanf(“%d”,&key); loc=s_search_adv(a,n,key); if (loc >0) printf(“loc=%d,key=%d\n”,loc,key); } 示例
n n n+1 1 ASL = Pi*Ci = (n-i+1) = n 2 i=1 i=1 n ASL 2 算法讨论 • 平均查找长度ASL在等概率的情况下 • 平均查找长度ASL在等概率的情况下 • 优点: • 对结点的逻辑次序(不必有序)和存储结构(顺序、链表均可)无要求; • 当序列中的记录“基本有序”或N值较小时,是较好的算法; • 缺点: • ASL较长 • 讨论:能否减少比较次数,以提高效率。 • 例如,…… 二分法等
2、折半查找 • 算法思想: 将有序数列的中点设置为比较对象,如果要找的元素值小于该中点元素,则将待查序列缩小为左半部分,否则为右半部分。 即通过一次比较,将查找区间缩小一半。 • 二分查找是一种高效的查找方法。它可以明显减少比较次数,提高查找效率。但是,二分查找的先决条件是查找表中的数据元素必须有序。
算法描述 • 算法步骤: • step1首先确定整个查找区间的中间位置, mid = ( left + right )/ 2 • step2用待查关键字值与中间位置的关键字值进行比较; • 若相等,则查找成功; • 若大于,则在后半区域继续进行二分查找; • 若小于,则在前半区域继续进行二分查找。 • Step3对确定的缩小区域再按二分公式,重复上述步骤; • 最后,得到结果: • 要么,查找成功, 要么,查找失败。 • 存储结构 • 用一维数组存放。
折半查找算法举例 • 对给定数列(有序){ 3,5,11,17,21,23,28,30,32,50},按折半查找算法,查找关键字值为30的数据元素。 第1次: { 3,5,11,17,21,23,28,30,32,50 } mid1= (1+10)/2 = 5 第2次: { 23,28,30,32,50 } mid2 = (6+10) /2 = 8 left mid right left right mid
折半查找算法3-3 bin_search ( item , n ,key ) int *item, n, key; { int left ,right , mid; left=0; right = n-1; while ( left right ) { mid = ( left + right )/2 ; /* 计算中点位置 */ if ( key < item[mid]) right = mid - 1; /* 待查区间在左部 */ else if (key > item[mid]) left = mid + 1; /* 待查区间在右部 */ else{ printf ( “ Successful search\n”); return mid ; /* 查找成功 */ } printf( “ Search failure \n”); return -1; /* 查找失败 */ }
折半查找算法3-3主程序 #define “stdio.h” int num; main( ) { int res, key ; int s[10]={1,3,5,7,9,11,13,15,17,19,21,23}; res=b_search(s,12,7); if(res>0) printf("res=%d , num=%d\n",res+1,num); else printf(“search failure\n”); } 示例
算法讨论 • 优点: ASL log2n;即每经过一次比较,查找范围就缩小一半。经log2n 次计较就可以完成查找过程。 • 缺点:因要求有序,所以对所有数据元素按大小排序是非常费时的操作。另外,顺序存储结构的插入、删除操作不大便利。 • 考虑: • 能否一次比较抛弃更多的部分(即一次比较,使查找范围缩得更小),以达到提高效率的目的; • ……? • 把两种方法(顺序查找和二分查找)结合起来,即取顺序查找简单和二分查找高效之所长,来达到提高效率的目的?
3、分块查找 • 分块查找又称索引顺序查找,这是顺序查找的一种改进方法。 • 方法描述:将n个数据元素“按块有序”划分为m块(m n)。每一块中的结点不必有序,但块与块之间必须“按块有序”;即第1快中任一元素的关键字都必须小于第2块中任一元素的关键字;而第2块中任一元素又都必须小于第3块中的任一元素,……。每个块中元素不一定是有序的。
分块查找算法描述 • step1 先选取各块中的最大关键字构成一个索引表; • step2 查找分两个部分: • 先对索引表进行二分查找或顺序查找,以确定待查记录在哪一块中; • 在已确定的块中用顺序法进行查找。
22 33 48 12 42 List[1] 22 60 13 44 58 44 List[2] 9 38 74 List[3] 74 24 8 47 分块查找举例 • 有数列如下: { 22,12,13,9,8,33,42,44,38,24,48,60,58,75,47} 按“块有序”分三块:(22,12,13,9,8),(33,42,44,38,24), (48,6,58,74,47)。选取每块中最大的关键字组成索引表[22,44,74],查找关键字值为60的元素。 • 用二分法,确定在索引表中的位置为 mid=2,key值60与a[2]比较,60>a[2],取第3个块;在第3块中用顺序法查找,比较两次,就可以找出60的元素来。
索引表结构定义 #include "stdio.h" typedef struct { int key; /* 块最大值 */ int link; /* 指向块入口地址 */ } index;
分块查找逻辑框图 i_s_search(ls[],s[],m,key,l) ls[] 索引表 S[] 待查表 M 块数 key 要找的值 l 块长度 开始 i=0 确定块的循环 i++ Y i<m 且 key>ls[i].key? N N Y i ≥ m? 块入口地址 j=ls[i].link 显示“查找失败” 返回 块内查找循环 j++ Y N key!=s[j]且j在范围内? N Y key=s[j]? 查找失败 查找成功 返回
index_seq_search.c子函数 index_seq_search(index ls[],int s[],int m,int key,int l) { int i,j; i=0; while(i<m && key>ls[i].key) i++; /* 确定在哪块查找 */ if(i>=m) { printf(“ Searching failure\n"); return(-1); } else /* 否则,查找成功处理 */
分块查找子函数(续) { j=ls[i].link; /* 块入口地址 */ while (key !=s[j] && (j-ls[i].link)<l) j++; if(key==s[j]&&(j-ls[i].link)<l)/* 查找成功*/ { printf("Successful search\n LOC(s[%2d])=%d\n",j,s[j]); return(j); } else /* 查找失败 */ { printf("Searching failure\n"); return(-1); } } } /* 结束 */
分块查找主函数 main() { index ls[5]={ 14,0,34,5 ,66,10,85,15,100,20} ; int a[]={8,4,3,2,14, 34,20,17,28,29 ,58,61,59,66,48, 81,80,79,83,69,89,100,96,86,87};int i,j=0,key; for(i=0;i<25;i++) { if (j==0) printf("{ "); if (j==5) { printf(" }"); j=0; printf(" {"); } printf("%4d",a[i]); j++; } printf(" } \n"); printf("Enter key: "); scanf("%d",&key); index_seq_search(ls,a,25,key,5); } 示例
算法讨论 • 设索引表使用二分法,则有: ASL log2(n/S +1)+ S/2 其中:n为表长,S为块长(假设各块长度相等)。 • 优点: 插入、删除操作方便;只要找到对应的块,在块中任意位置操作均可。 • 缺点: 索引表增加了辅助存储空间。 注: 索引表在数据库系统中广泛使用。 • 还有什么方法吗? 树表查找
4、树表(二叉排序树)查找 • 前三种查找方法各有千秋。二分法查找效率高,顺序法可以采用链表存储结构,操作灵活。 但最好是既有二分法的高效率,又有链表灵活性的查找方法。 • 二叉排序树实际上就是将数据元素组织成二叉树形式,以达到与二分法相同的查找效率,而又具有链表的插入、删除操作的灵活性。
二叉排序树查找(算法3-4) • step1首先建立起二叉排序树。 • step2将待查关键字值与树根结点进行比较,若相等,查找成功; • step3否则根据比较结果确定查找路径: • 若小于根结点的关键字值,则与左子树的根结点的关键字值进行比较; • 若大于等于根结点的关键字值,则与右子树的根结点的关键字值进行比较。 • step4重复上述步骤,直到要么找到,查找成功;要么找不到,查找失败。
存储结构描述 二叉排序树结点结构: struct tree { char info; struct tree *left; struct tree *right; }
主程序框图 开始 初始化 输入结点数据循环 !ROOT root=create_btee() create_btree() N 结束? Y Print_btree() 打印该树 Search_btree() 查找指定结点
生成二叉排序树程序框图 create_btree( root,r,info) root 根指针 r 插入的结点 info 要插入的结点值 开始 Y 申请结点空间 r = 0? 根结点的处理 N 非根结点 Y N Y info<r->info? r = 0? 显示“溢出” t=r->left N t=r->right 结束 r->left = 0; r->right = 0; r->info = info ; 调用本函数 Y N root? root->left=r 或 root->right=r r->left=0 r->right=0 返回
查询二叉排序树算法框图 Search_btree(root,key) root 根指针 key 要找的值 开始 Y 显示“空树” !root N root->info!=key循环 返回 key<root->info? root=root->left root=root->right N N Y root=0? 循环结束? 显示“失败” Y root!=0? 查找成功,显示 返回
打印算法框图 开始 print_btree(root,l ) root 根指针 l 起始点距离 Y r=0? N 调用自身打印左子 打印当前结点值 调用自身打印右子 返回
主程序Btree.C #include “stdio.h” struct tree { char info; struct tree *left,*right; } main ( ) { char *s,*c,key=‘’; struct tree *create_btree(),*search_btree(),*root=0; do { printf(“Enter a letter:”); gets(s); if (!root) root=create_btree(root,root,*s); else create_btrr(root,root,*s); } while (*s) ;
主程序Btree.C(续) print_btree(root,0); key=‘1’; while ( key) { printf(“Enter a key to find:”); scanf(“%s”,&c); key=search_btree(root,c); printf(“press to continue\n”); } } /* Btree.C 结束 */
生成二叉排序树程序 struct tree create_btree(root,r,info) struct tree *root,*r; char info; { if (r = = 0 ) { r=malloc(sizeof(struct tree)); if ( r = = 0) { printf(“Out of memory\n”); exit(0); } r->left= 0; r->right=0; r->info=info; if (root) { if(info<root->info) root -> left=r; else root->right=r; } else { r->right=0; r->left = 0; } return r; } /* if = = 0 接下页 */
生成二叉排序树程序(续) if (info < r->info) create_btree(r,r->left,info); if(info>=r->info) create_btree(r,r->right,info); } /* create_btree(root,r,info) */
打印二叉排序树程序 print_btree(r,l) struct tree *r; int l; { int i; if (r = = 0) return ; print_tree(r->left,l+1); for(i=0;i<l;i++) printf(““); printf(“%c\n”,r->info); print_btree(r->right,l+1); }
查询二叉排序树程序 struct tree *search_btree(root,key) struct tree *root; char key; { if (!root) { printf(“Emptu btree\n”);return root; } while(root->info!=key) /* 查找key 的循环 */ { if(key<root->info) root=root->left; /* 沿左路查找 */ else root=root->right; /* 沿右路查找 */ if(root= =0) /* 到叶结点也没找到 */ { printf(“Search Failure\n”); break ; } } if (root !=0) /* 查找成功 */ printf(“Successful search\n key=%c\n”,root->info); return root ; } 示例
程序输入 输入: 输出: h b d d p e r h b p e r
10 18 3 4 13 25 9 举例 • 对数列{10,18,3,4,9,13,25},生成二叉排序树如右;查找关键字值25。 • 25比较根结点值10,走右路,再与18进行比较,走右路,最后与25进行比较,相等,查找成功。比较了3次。 • 若查找35,则与10、18、25 分别进行比较后仍没找到相等 元素,查找失败,也比较了三次。
算法讨论 • 二叉排序树查找的 ASL log2n。若其平衡特性较好的话,ASL与折半查找相同。 • 二叉排序树是动态生成的,其特性随插入结点的先后次序的不同而改变,为防止其蜕化为单枝树,要进行平衡化处理。 • 二叉排序树因采用链表结构,需要辅助存储空间。 • 上述方法都是建立比较基础上的,还有其它类型的方法吗?
5、哈希(hash)查找 • 哈希查找也称为散列查找。它不同于前面介绍的几种查找方法。上述方法都是把查找建立在比较的基础上,而哈希查找则是通过计算存储地址的方法进行查找的。 • 计算是计算机的特点之一,因此,建立在计算基础上的哈希查找是一种快速查找方法。
哈希查找的基本概念 • 哈希表 由哈希函数的值组成的表。哈希查找是建立在哈希表的基础上,它是线性表的一种重要存储方式和检索方法。在哈希表中可以实现对数据元素的快速检索。 • 哈希函数 哈希表中元素是由哈希函数确定的。将数据元素的关键字K作为自变量,通过一定的函数关系(称为哈希函数),计算出的值,即为该元素的存储地址。表示为: Addr = H(key) • 建立哈希函数的原则 • 均匀性 H(key)的值均匀分布在哈希表中; • 简单 以提高地址计算的速度。