第9章查找.docx
- 文档编号:25435442
- 上传时间:2023-06-08
- 格式:DOCX
- 页数:55
- 大小:136.77KB
第9章查找.docx
《第9章查找.docx》由会员分享,可在线阅读,更多相关《第9章查找.docx(55页珍藏版)》请在冰豆网上搜索。
第9章查找
第9章查找
在英汉字典中查找某个英文单词的中文解释;在新华字典中查找某个汉字的读音、含义;在对数表、平方根表中查找某个数的对数、平方根;邮递员送信件要按收件人的地址确定位置等等,可以说查找是为了得到某个信息而常常进行的工作。
计算机、计算机网络使信息查询更快捷、方便、准确。
要从计算机、计算机网络中查找特定的信息,就需要在计算机中存储包含该特定信息的表。
如要从计算机中查找英文单词的中文解释,就需要存储类似英汉字典这样的信息表,以及对该表进行的查找操作。
本章将讨论的问题即是“信息的存储和查找的方法”。
查找是许多程序中最消耗时间的一部分,因而,一个好的查找方法会大大提高运行速度。
9.1基本概念
⒈关键码
关键码是数据元素(或记录)中某个数据项的值,用它可以标识一个数据元素(或记录)。
能惟一确定一个数据元素(或记录)的关键码,称为主关键码;而不能惟一确定一个数据元素(或记录)的关键码,称为次关键码。
如“学号”可看成学生的主关键码,“姓名”则应视为次关键码。
⒉查找表
查找表是一种以集合为逻辑结构、以查找为核心的数据结构。
由于集合中的数据元素之间是没有“关系”的,因此在查找表的实现时就不受“关系”的约束,而是根据实际应用对查找的具体要求去组织查找表,以便实现高效率的查找。
对查找表中常做的运算有:
建表、查找、读表元、对表做修改操作(如插入和删除)。
若对查找表的操作不包括对表的修改操作,则此类查找表称为静态查找表,若在查找的同时插入表中不存在数据元素,或从查找表中删除已存在的指定元素,则此类查找表称为动态查找表。
简单地,静态查找表仅对查找表进行查找操作,而不能改变查找表;动态查找表对查找表除进行查找操作外,可能还要进行向表中插入数据元素或删除表中数据元素。
⒊平均查找长度
按给定的某个值kx,在查找表中查找关键码等于给定值kx的数据元素。
关键码是主关键码时,查找结果也是惟一的,一旦找到,称为查找成功,结束查找过程,并给出找到的数据元素的信息,或指示该数据元素的位置。
若是整个表检索完,还没有找到,称为查找失败,此时,查找结果应给出一个“空”记录或“空”指针。
关键码是次关键码时,查找结果可能不惟一,要想查得表中所有的相关数据元素需要查遍整个表,或在可以肯定查找失败时,才能结束查找过程。
由于查找运算的主要操作是关键字的比较,所以,通常把查找过程中对关键字的比较次数作为衡量一个查找算法效率优劣的标准,也称为平均查找长度,通常用ASL表示,ASL的定义为:
在查找成功时,平均查找长度ASL是指为确定数据元素在表中的位置所进行的关键码比较次数的期望值。
对一个含n个数据元素的表,查找成功时:
(9-1)
其中:
n是结点的个数,pi是查找第i个结点的概率,若不特别声明,均认为对每个数据元素的查找概率是相等的,即:
pi=1/n;ci是查找第i个数据元素所需要的比较次数。
本章以后讨论中,涉及的关键码类型和数据元素类型说明如下:
typedefstruct{
KeyTypekey;//关键码字段
……//其它信息
}DataType;
9.2静态查找表
9.2.1静态查找表结构
静态查找表通常是将数据元素组织为一个线性表,可以是基于数组的顺序存储或以线性链表存储。
静态查找表的顺序存储结构定义如下:
typedefstruct{
DataType*data;//查找表存储空间的基址
intlength;//表长度
}S_T;
静态查找表的链式存储结构其结点结构的类型定义如下:
typedefstructnode{
DataTypedata;//结点的值域
structnode*next;//下一个结点指针域
}NodeType;
9.2.2顺序查找
顺序查找又称线性查找,是最基本的查找方法之一。
其查找方法为:
从表的一端开始,向另一端逐个按给定值kx与关键码进行比较,若找到,查找成功,并给出数据元素在表中的位置;若整个表检索完之后,仍未找到与kx相同的关键码,则查找失败,给出失败信息。
顺序查找既适合于顺序存储的静态查找表,又适合于链式存储的静态查找表。
以顺序存储为例,设数据元素从下标为1的数组单元开始存放,0号单元留做监测哨,则算法如下:
intS_Search(S_T*t,KeyTypekx){
//在表t中查找关键码为kx的数据元素,若找到返回该元素在数组中的下标,否则返回0
inti;
t->data[0].key=kx;
//存放监测,这样在从后向前查找失败时,不必判表是否检测完,从而达到算法统一
for(i=t->length;t->data[i].key!
=kx;i--);//从标尾端向前查找
returni;
}
【算法9-1】顺序查找算法
性能分析:
就上述算法而言,对于n个数据元素的表,若给定值kx与表中第i个元素关键码相等,即定位第i个记录时,需进行n-i+1次关键码比较,即ci=n-i+1。
则查找成功时,顺序查找的平均查找长度为:
(9-2)
设每个数据元素的查找概率相等,即pi=1/n,则等概率情况下有:
(9-3)
即查找不成功时,每个关键码都比较一次,直到监测哨,因此关键码的比较次数总是n+1次。
算法中的基本工作就是关键码的比较,因此,查找长度的量级就是查找算法的时间复杂度,其为O(n)。
许多情况下,查找表中数据元素的查找概率是不相等的。
为了提高查找效率,查找表需依据查找概率越高,比较次数越少;查找概率越低,比较次数就较多的原则来存储数据元素。
顺序查找缺点是当n很大时,平均查找长度较大,效率低;优点是对表中数据元素的存储没有要求。
9.2.3有序表的查找
⒈折半查找
折半查找也称二分查找,它是一种效率较高的查找方法,但它要求查找表必须是按顺序结构存储且表中数据元素按关键码有序排列。
折半查找的思想为:
在有序表中,取中间元素作为比较对象,若给定值与中间元素的关键码相等,则查找成功;若给定值小于中间元素的关键码,则在中间元素的左半区继续查找;若给定值大于中间元素的关键码,则在中间元素的右半区继续查找。
不断重复上述查找过程,直到查找成功,或所查找的区域无数据元素,查找失败。
【例9-1】顺序存储的有序表关键码排列如下:
7,14,18,21,23,29,31,35,38,42,46,49,52
用折半查找在表中查找关键码为14和22的数据元素。
⑴查找关键码为14的过程如下
1
2
3
4
5
6
7
8
9
10
11
12
13
7
14
18
21
23
29
31
35
38
42
46
49
52
↑↑↑
low=1mid=7high=13
14<31,调整到左半区:
high=mid-1
1
2
3
4
5
6
7
8
9
10
11
12
13
7
14
18
21
23
29
31
35
38
42
46
49
52
↑↑↑
low=1mid=3high=6
14<18,调整到左半区:
high=mid-1
1
2
3
4
5
6
7
8
9
10
11
12
13
7
14
18
21
23
29
31
35
38
42
46
49
52
↑↑↑
low=1high=2
mid=1
14>7,调整到右半区:
low=mid+1
1
2
3
4
5
6
7
8
9
10
11
12
13
7
14
18
21
23
29
31
35
38
42
46
49
52
↑
low=mid=high=2
14与mid所指元素的关键码相等,查找成功,返回位置。
⑵查找关键码为22的过程如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
7
14
18
21
23
29
31
35
38
42
46
49
52
↑↑↑
low=1mid=7high=13
22<31,调整到左半区:
high=mid-1
1
2
3
4
5
6
7
8
9
10
11
12
13
7
14
18
21
23
29
31
35
38
42
46
49
52
↑↑↑
low=1mid=3high=6
22>18,调整到右半区:
low=mid+1
1
2
3
4
5
6
7
8
9
10
11
12
13
7
14
18
21
23
29
31
35
38
42
46
49
52
↑↑↑
low=4mid=5high=6
22<23,调整到左半区:
high=mid-1
1
2
3
4
5
6
7
8
9
10
11
12
13
7
14
18
21
23
29
31
35
38
42
46
49
52
↑↑↑
low=4mid=4high=4
22>21,调整到右半区:
low=mid+1
1
2
3
4
5
6
7
8
9
10
11
12
13
7
14
18
21
23
29
31
35
38
42
46
49
52
↑↑
high=4low=5
此时,low>high,即查找区间为空,说明查找失败,返回查找失败信息。
折半查找算法如下:
intBinary_Search(S_T*t,KeyTypekx){
//在表t中查找关键码为kx的数据元素,若找到返回该元素在表中的位置,否则,返回0
intlow,high,mid;
intflag=0;
low=1;
high=t->length;//设置初始区间
while(low<=high)//表空测试
{//非空,进行比较测试
mid=(low+high)/2;//得到中点
if(kx
high=mid-1;//调整到左半区
else
if(kx>t->data[mid].key)
low=mid+1;//调整到右半区
else
{flag=mid;
break;}//查找成功,元素位置设置到flag中
}
returnflag;
}
【算法9-2】折半查找算法
性能分析:
从折半查找过程看,以表的中点为比较对象,并以中点将表分割为两个子表,对定位到的子表继续这种操作。
所以,对表中每个数据元素的查找过程,可用二叉树来描述,称这个描述查找过程的二叉树为判定树。
21
图9-1例9-1描述折半查找过程的判定树
可以看到,查找表中任一元素的过程,即是判定树中从根到该元素结点路径上各结点关键码的比较次数,也即该元素结点在树中的层次数。
对于n个结点的判定树,树高为k,则有2k-1-1 因此,折半查找在查找成功时,所进行的关键码比较次数至多为log2(n+1)。 接下来讨论折半查找的平均查找长度。 为便于讨论,以树高为k的满二叉树(n=2k-1)为例。 假设表中每个元素的查找是等概率的,即pi=1/n,则树的第i层有2i-1个结点,因此,折半查找的平均查找长度为: =[1×20+2×21+…+k×2k-1]/n =(n+1)(log2(n+1)-1)/n≈log2(n+1)-1(9-4) 所以,折半查找的时间效率为O(log2n)。 ⒉插值查找 类似于平常查英文字典的方法,在查一个以字母“C”开头的英文单词时,决不会用二分查找,从字典的中间一页开始,因为知道它的大概位置是在字典的较前面的部分,因此可以从前面的某处查起,这就是插值查找的基本思想。 插值查找除要求查找表是顺序存储的有序表外,还要求数据元素的关键字在查找表中均匀分布,这样,就可以按比例插值。 插值查找通过下列公式 kx-t.data[low].key ──────────────── t.data[high].key-t.data[low].key mid=low+(high-low)(9-5) 求取中点,其中low和high分别为表的两个端点下标,kx为给定值。 若kx 若kx>t.data[mid].key,则low=mid+1,继续右半区查找; 若kx=t.data[mid].key,查找成功。 插值查找是平均性能最好的查找方法,但只适合于关键码均匀分布的表,其时间效率依然是O(log2n)。 ⒊斐波那契查找 二分查找每比较一次,把查找区间划分为两个相等的区间,再对其中一个区间继续查找。 可否在比较一次后,划分为不相等的两个区间呢? 斐波那契就是这样的一种划分方法。 斐波那契查找通过斐波那契数列对有序表进行分割,查找区间的两个端点和中点都与斐波那契数有关。 斐波那契数列定义如下: nn=0或n=1 F(n-1)+F(n-2)n≥2 F(n)= 设n个数据元素的有序表,且n正好是某个斐波那契数-1,即n=F(k)-1时,可用此查找方法。 斐波那契查找分割的思想为: 对于表长为F(i)-1的有序表,以相对low偏移量F(i-1)-1取中点,即mid=low+F(i-1)-1,对表进行分割,则左子表表长为F(i-1)-1,右子表表长为F(i)-1-[F(i-1)-1]-1=F(i-2)-1。 可见,两个子表表长也都是某个斐波那契数-1,因而,可以对子表继续分割。 斐波那契查找算法的思想为: ①low=1;high=F(k)-1;//设置初始区间 len=F(k)-1;f=F(k-1)-1;//len为表长,f为取中点的相对偏移量 ②当low>high时,返回查找失败信息//表空,查找失败 ③low≤high,mid=low+f;//取中点 a.若kx len=f;//调整表长 f=len-f-1;//计算取中点的相对偏移量 high=mid-1;转②//查找在左半区进 b.若kx>t.data[mid].key,则 len=len-f-1;//调整表长 f=f-len-1;//计算取中点的相对偏移量 low=mid+1;转②//查找在右半区进行 c.若kx=t.data[mid].key,则 返回数据元素在表中位置,查找成功 【算法9-3】斐波那契查找算法 当n很大时,该查找方法称为黄金分割法,其平均性能比折半查找好,但其时间效率仍为O(log2n);而且,在最坏情况下比折半查找差。 优点是计算中点时仅作加、减运算。 9.2.4分块查找 若查找表中的数据元素的关键字是按块有序的则可以做分块查找。 分块查找又称索引顺序查找,是对顺序查找的一种改进。 分块查找将查找表按块分成若干个子表,对每个子表建立一个索引项,再将这些索引项顺序存储,形成一个索引表。 每个索引项包括两个字段: 关键码字段(存放对应子表中的最大关键码值)和指针字段(存放指向对应子表的指针),这样索引表则是按关键码有序的。 查找时,分成两步进行: 先根据给定值kx在索引表中查找,以确定所要查找的数据元素属于查找表中的哪一块,由于索引表按关键码有序,因此可用顺序查找或折半查找,然后,再进行块内查找,因为块内无序,只能进行顺序查找。 【例9-2】设关键码集合为: 88,43,14,31,78,8,62,49,35,71,22,83,18,52 按关键码值31,62,88分为三块建立的查找表及其索引表如如9-2所示: 关键码字段 指针字段 索引表 31 62 88 查找表 1 6 11 14 31 8 22 18 43 62 49 35 52 88 78 71 83 1234567891011121314 图9-2分块查找示例 性能分析: 分块查找由索引表查找和子表查找两步完成。 设n个数据元素的查找表分为m个子表,且每个子表均为t个元素,则: 设在索引表上的检索也采用顺序查找,这样,分块查找的平均查找长度为: ASL=ASL索引表+ASL子表(9-6) ─ ─ 可见,平均查找长度不仅和表的总长度n有关,而且和所分的子表个数m有关。 对于表长n确定的情况下,m取时,ASL=+1达到最小值。 9.3动态查找表 9.3.1二叉排序树 ⒈二叉排序树定义 二叉排序树(BinarySortTree)或者是一棵空树,或者是具有下列性质的二叉树: 10 ⑴若左子树不空,则左子树上所有结点的值 均小于根结点的值;若右子树不空,则右子树上所 有结点的值均大于根结点的值。 ⑵左右子树也都是二叉排序树。 如图9-3就是一棵二叉排序树。 可以看出,对二叉排序树进行中序遍历, 得到一个按关键码有序的序列,图9-3一棵二叉排序树示例 ⒉二叉排序树的查找过程 若将查找表组织为一棵二叉排序树,则根据二叉排序树的特点,查找过程为: ⑴若查找树为空,查找失败。 ⑵查找树非空,将给定值kx与查找树的根结点关键码比较。 ⑶若相等,查找成功,结束查找过程,否则, a.当给定植kx小于根结点关键码,查找将在以左孩子为根的子树上继续进行,转⑴ b.当给定植kx大于根结点关键码,查找将在以右孩子为根的子树上继续进行,转⑴ 下面以二叉链表作为二叉排序树的存储结构,二叉链表结点的类型定义如下: typedefstructNode{ DataTypedata;//数据元素字段 structNode*lchild,*rchild;//左、右指针字段 }NodeType;//二叉树结点类型 查找算法描述如下: intSearchData(NodeType*t,NodeType**p,NodeType**q,KeyTypekx){ //在二叉排序树t上查找关键码为kx的元素,若找到,返回1,且*q指向该结点,*p指向其父结点; //否则,返回0,且p指向查找失败前的最后一个结点。 intflag=0; *q=t;while(*q)//从根结点开始查找{if(kx>(*q)->data.key)//kx大于当前结点*q的元素关键码 {*p=*q; *q=(*q)->rchild; }//将当前结点*q的右孩子置为新根 else {if(kx<(*q)->data.key)//kx小于当前结点*q的元素关键码 {*p=*q; *q=(*q)->lchild; }//将当前结点*q的左孩子置为新根 else {flag=1; break; }//查找成功,返回 } }//while returnflag; } 【算法9-4】二叉排序树查找算法 ⒊二叉排序树插入操作和构造一棵二叉排序树 首先讨论向二叉排序树中插入一个结点的过程: 设待插入结点的关键码为kx,为将其插入,先要在二叉排序树中进行查找,若查找成功,按二叉排序树定义,待插入结点已存在,不用插入;查找不成功时,则插入之。 因此,新插入结点一定是作为叶子结点添加上去的。 向二叉排序树中插入一个结点的算法如下: intInsertNode(NodeType**t,KeyTypekx){ //在二叉排序树*t上插入关键码为kx的结点 NodeType*p,*q,*s; intflag=0; p=t; if(! SearchData(*t,&p,&q,kx))//在*t为根的子树上查找 {s=(NodeType*)malloc(sizeof(NodeType));//申请结点,并赋值 s->data.key=kx; s->lchild=NULL; s->rchild=NULL; flag=1;//设置插入成功标志 if(! p) *t=s;//向空树中插入时 else {if(kx>p->data.key) p->rchild=s;//插入结点为p的右孩子 else p->lchild=s;//插入结点为p的左孩子 } } returnflag; } 【算法9-5】二叉排序树插入一个结点的算法 构造一棵二叉排序树则是逐个插入结点的过程。 【例9-3】设关键码序列为: 63,90,70,55,67,42,98,83,10,45,58,则构造一棵二叉排序树的过程如图9-4所示: 90 98 42 67 63 55 63 φ 10 10 42 42 图9-4从空树开始建立二叉排序树的过程 ⒋二叉排序树删除操作 从二叉排序树中删除一个结点之后,使其仍能保持二叉排序树的特性即可。 设待删结点为p(p为指向待删结点的指针),其双亲结点为f,以下分三种情况进行讨论。 ⑴若p结点为叶结点,由于删去叶结点后不影响整棵树的特性,所以,只需将被删结点的双亲结点相应指针域改为空指针。 如图9-5所示。 ⑵若p是单支接点,即p结点只有右子树PR 或只有左子树PL,此时,只需将PR或PL替换f结点的p子树即可。 如图9-6所示。 P PR PL f F 图9-6删除单支结点 图9-5删除叶结点 ⑶p结点既有左子树PL又有右子树PR,可按中序遍历保持有序进行调整。 设删除p结点前,中序遍历序列为: ①p为f的左孩子时有: …,PL子树,p,pr,SR子树,pj,SJ子树,…,p2,S2子树,p1,S1子树,f,…; ②p为f的右孩子时有: …,f,PL子树,p,pr,SR子树,pj,SJ子树,…,p2,S2子树,p1,S1子树,…。 则删除p结点后,中序遍历序列
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 第9章 查找