第6章 查找与排序.docx
- 文档编号:9667630
- 上传时间:2023-02-05
- 格式:DOCX
- 页数:49
- 大小:85.72KB
第6章 查找与排序.docx
《第6章 查找与排序.docx》由会员分享,可在线阅读,更多相关《第6章 查找与排序.docx(49页珍藏版)》请在冰豆网上搜索。
第6章查找与排序
第6章查找与排序
本书的前几章1介绍了各种线性的的数据结构,讨论了它们的逻辑结构、存储结构和相关的算法。
在本章中将讨论另一种数据结构——查找表(SearchTable)。
为了查找方便,通常希望计算机中的表是按关键字有序的。
因为有序的顺序表可以采用查找效率较高的各种查找算法。
因此,学习和研究各种查找和排序方法是计算机工作者的重要课题之一。
6.1 常用数据查找算法
下面,首先介绍几个有关查找的基本概念:
查找表——由同一类型的数据元素(或记录)构成的集合称为查找表。
如图表9.1所示的学生招生录取登记表。
图表6.1学生招生录取登记表
学号
姓名
性别
入学总分
录取专业
┊
┊
┊
┊
┊
20010983
张三
女
438
计算机
20010984
李四
男
430
计算机
20010985
王五
女
445
计算机
┊
┊
┊
┊
┊
20010998
张三
男
458
计算机
┊
┊
┊
┊
┊
静态查找(StaticSearchTable)——在查找过程中仅查找某个特定元素是否存在或它的属性的,称为静态查找。
动态查找(DynamicSearchTable)——在查找过程中对查找表进行插入元素或删除元素操作的,称为动态查找。
关键字(Key)——数据元素(或记录)中某个数据项的值,用它可以标识数据元素(或记录)。
主关键字(PrimaryKey)——可以唯一地标识一个记录的关键字称为主关键字。
如图8-1的“学号”。
次关键字(SecondaryKey)——可以标识若干个记录的关键字称为次关键字。
如图8-1的“姓名”,其中张三就有两位。
查找(Searching)——在查找表中确定是否存在一个数据元素的关键字等于给定值的操作,称为查找(也称为检索)。
若表中存在这样一个数据元素(或记录),则查找成功;否则,查找失败。
内查找和外查找——若整个查找过程全部在内存进行,则称为内查找;若在查找过程中还需要访问外存,则称为外查找。
本书仅介绍内查找。
平均查找长度ASL——查找算法的效率,主要是看要查找的值与关键字的比较次数,通常用平均查找长度来衡量。
对一个含n个数据元素的表,查找成功时:
其中:
Pi为找到表中第i个数据元素的概率,且有:
Ci为查找表中第i个数据元素所用到的比较次数。
不同的查找方法有不同的Ci。
查找是许多程序中最消耗时间的一部分。
因而,一个好的查找方法会大大提高运行速度。
静态查找表是数据元素的线性表,可以是基于数组的顺序存储或者以链表存储。
6.1.1顺序查找
顺序查找又称线性查找,是最基本的查找方法之一。
顺序查找既适用于顺序表,也适用于链表。
1.基本思想
从表的一端开始,顺序扫描线性表,依次按给定值kx与关键字(Key)进行比较,若相等,则查找成功,并给出数据元素在表中的位置;若整个表查找完毕,仍未找到与kx相同的关键字,则查找失败,给出失败信息。
2.算法的实现
现以顺序存储为例,数据元素从下标为1的数组单元开始存放,0号单元作为监测哨,用来存放待找的值kx。
例6.1顺序查找算法。
voidSeqSearch()/*顺序查找*/
{
inta[N],i,x,y;
printf("\n\t\t建立一个整数的顺序表(以回车为间隔,以-1结束):
\n");
for(i=1;i<=MAXLEN;i++)
{
scanf("%d",&a[i]);
if(a[i]==-1)
{
y=i;break;
}
}
printf("请输入要查找的数据:
");
scanf("%d",&x);a[0]=x;
i=y-1;
while(i>=0&&a[i]!
=x)
i--;
if(i==0)
printf("没有找到\n");
else
printf("已找到,在第%d的位置上\n",i);
}
监测哨的作用:
(1)省去判定循环中下标越界的条件,从而节约比较时间;(2)保存查找值的副本,查找时若遇到它,则表示查找不成功。
这样在从后向前查找失败时,不必判别查找表是否检测完,从而达到算法统一。
3.顺序查找性能分析
对一个含n个数据元素的表,查找成功时
就上述算法而言,对于n个数据元素的表,给定值kx与表中第i个元素关键字相等,即定位第i个记录时,需进行n-i+1次关键字比较,即Ci=n-i+1。
则查找成功时,顺序查找的平均查找长度为:
设每个数据元素的查找概率相等,即Pi=
,则等概率情况下有
查找不成功时,关键字的比较次数总是n+1次。
算法中的基本工作就是关键字的比较,因此,查找长度的量级就是查找算法的时间复杂度,其为O(n)。
顺序查找缺点是当n很大时,平均查找长度较大,效率低;优点是对表中数据元素的存储没有要求。
另外,对于线性链表,只能进行顺序查找。
4.改写后的顺序查找算法
(1)数据元素类型定义
Typedefstruct
{keytypekey;/*关键字域*/
…/*其他域*/
}elemtype;
(2)顺序表的存储结构定义
#defineMax100
Typedefstruct
{elemtypeelem[Max];/*0号单元留作监视哨*/
Intlength;
}SSTable;
(3)顺序查找算法
Intsearch_seq(SSTablest,keytypekey)
{/*在顺序表st中查找其关键字等于key的数据元素。
若找到,则函数值为该元素在表中的位置;否则,为0。
*/
st.elem[0].key=key;/*哨兵*/
For(i=st.length;st.elem[i].key!
=key;--i);
Returni;
}
实训:
编程实现顺序表的顺序查找算法。
/*顺序查找*/
#include"stdio.h"
#include"stdlib.h"
#include"string.h"
/*数据元素类型定义*/
typedefstruct{
intkey;/*学号,关键字域*/
charname[10];/*姓名*/
intscore;/*成绩*/
}elemtype;
/*静态查找表的顺序存储结构类型定义*/
#defineMax100/*顺序表最大的存储长度*/
typedefstruct{
elemtypeelem[Max];/*用一维数组存储顺序表各元素,0号单元留作监视哨*/
intlength;
}sstable;
/*顺序表的查找算法----顺序查找算法*/
intsearch_seq(sstablest,intkey)
{inti;
st.elem[0].key=key;/*哨兵*/
for(i=st.length;st.elem[i].key!
=key;--i);
returni;
}
/*主程序*/
voidmain()
{sstablest;
inti,searchnum;
printf("\n请输入3个学生的信息:
");
for(i=1;i<=3;i++)
{
printf("\n请输入学号:
");
scanf("%d",&st.elem[i].key);
getchar();/*读取成绩后的回车符号,否则将被姓名读取*/
printf("\n请输入姓名:
");
gets(st.elem[i].name);
printf("\n请输入成绩:
");
scanf("%d",&st.elem[i].score);
}
st.length=i-1;
printf("\n请输入要查找的学生学号:
");
scanf("%d",&searchnum);
i=search_seq(st,searchnum);
if(i==0)
printf("\n没有找到!
");
else
printf("\n已找到,在第%d的位置上。
\n",i);
}
6.1.2折半查找
折半查找也叫二分查找,是一种效率较高的查找方法,但前提是表中元素必须按关键字有序(按关键字递增或递减)排列。
1.折半查找的基本思想
在有序表中,取中间元素作为比较对象,若给定值与中间元素的关键字相等,则查找成功;若给定值小于中间元素的关键字,则在中间元素的左半区继续查找;若给定值大于中间元素的关键字,则在中间元素的右半区继续查找。
不断重复上述查找过程,直到查找成功;或所查找的区域无数据元素,则查找失败。
2.查找的步骤
举例讲解:
例6.2有序表按关键字排列如下:
5,14,18,21,23,29,31,35,38,42,46,49,52
在表中查找关键字为14和22的数据元素。
⑴查找关键字为14的过程
⑵查找关键字为22的过程
具体算法如下:
voidBinSearch()/*二分查找*/
{
intR[MAXLEN],i,k,low,mid,high,m,nn;
charch;
printf("建立递增有序的查找顺序表(以回车间隔,以-1结束):
\n");
for(i=0;i { scanf("%d",&R[i]); if(R[i]==-1) { nn=i;break; } } printf("请输入要查找的数据: "); scanf("%d",&k); low=0;high=nn-1;m=0; while(low<=high) { mid=(low+high)/2; m++; if(R[mid]>k)high=mid-1; elseif(R[mid] elsebreak; } if(low>high) { printf("没有找到\n"); printf("共进行%d次比较。 \n",m); if(R[mid] mid++; printf("可将此数插入到%d的位置上。 \n",mid+1); } else { printf("要找的数据%d在第%d的位置上。 \n",k,mid+1); printf("共进行%d次比较。 \n",m); } } 3.二分查找性能分析 从二分查找的过程看,每次查找都是以表的中点为比较对象,并以中点将表分割为两个子表,对定位到的子表继续作同样的操作。 二分查找的时间复杂度为: O(log2n)。 二分查找的优点是效率高;其缺点是必须按关键字排序、只适用顺序存储结构,所以进行插入、删除操作必须移动大量的结点。 二分查找适用于那种一经建立就很少改动,而又经常需要查找的线性表。 对于那些经常需要改动的线性表,可以采用链表存储结构,进行顺序查找。 4.改写后的折半查找算法 Intsearch_bin(SSTablest,keytypekey) {/*在有序表st中折半查找其关键字等于key的数据元素,若找到,则函数的值为该元素在表中的位置,否则为0*/ Low=1;high=st.length; While(low<=high) {mid=(low+high)/2; If(key=st.elem[mid].key) Returnmid; Elseif(key High=mid-1; Elselow=mid+1; } Return0; } 实训: 编程实现顺序表的折半查找算法。 /*有序表的折半查找*/ #defineMax100 typedefstruct{ intelem[Max]; intlength; }sstable; intsearch_bin(sstablest,intkey) {intlow,high,mid; low=1;high=st.length; while(low<=high) {mid=(low+high)/2; if(key==st.elem[mid]) returnmid; elseif(key high=mid-1; elselow=mid+1; } return0; } voidmain() { sstablest;inti=1,x; printf("\n建立递增有序的查找顺序表(以-1结束): \n"); scanf("%d",&st.elem[i]); while(st.elem[i]! =-1) {i++; scanf("%d",&st.elem[i]); } st.length=i-1; printf("\n请输入要查找的数据: "); scanf("%d",&x); i=search_bin(st,x); if(i==0) printf("\n没有找到! "); else printf("\n已找到,在第%d的位置上。 \n",i); } 6.2常用数据排序算法 6.2.1基本概念 排序(Sorting)——将数据元素(或记录)的任意序列,重新排列成一个按关键字有序(递增或递减)的序列的过程称为排序。 排序方法的稳定和不稳定——若对任意的数据元素序列,使用某个排序方法,对它按关键字进行排序,若对原先具有相同键值元素间的位置关系,排序前与排序后保持一致,称此排序方法是稳定的;反之,则称为不稳定的。 例如: 对数据键值为: 5,3,8,3,6,6,排序。 若排序后的序列为: 3,3,5,6,6,8,其相同键值的元素位置依旧是3在3前,6在6前,与排序前保持一致,则表示这种排序法是稳定的;若排序后的序列为: 3,3,5,6,6,8,则表示这种排序法是不稳定的。 内排序——整个排序过程都在内存进行的排序称为内排序。 外排序——待排序的数据元素量大,以致内存一次不能容纳全部记录,在排序过程中需要对外存进行访问的排序称为外排序。 限于篇幅,本书仅讨论内排序。 关于外排序的内容可参考其它有关的数据结构教材。 另外,为了便于描述,假设本章所有算法均按递增次序排列。 6.2.2插入排序 1. 直接插入排序 (1)基本思想 直接插入排序(StraightInsertionSort)是一种最简单的排序方法,它的基本操作是将一个记录插到已排序好的有序表中,从而得到一个新的,记录数增1的有序表。 插入前: (1358)[27496] 有序无序 插入后: (12358)[7496] 有序无序 (2)排序举例 例6.3 输入元素序列为: 39,28,55,80,75,6,17,45,28按从小到大的序列排序。 第一个取39,作为第一个假设有序的记录,第二个取28,28<39,则交换。 此后,每取来一个记录就与有序表最后一个关键字比较,若大于或等于最后一个关键字,则插入在其后;若小于最后一个关键字,则把取来的记录再与前一个关键字比较……,其过程如图6.2所示。 初始关键字: (39)[285580756174528] i=1 i=2,取出28(2839)[5580756174528] i=3,取出55(283955)[80756174528] i=4,取出80(28395580)[756174528] i=5,取出75(2839557580)[6174528] i=6,取出6(62839557580)[174528] i=7,取出17(6172839557580)[4528] i=8,取出45(617283945557580)[28] i=9,取出28(61728283945557580) 监视哨r[0] 图6.2直接插入排序过程 排序以后,相同关键字元素的28和28与排序前的位置保持一致,即28仍然在28之前,所以直接插入排序方法是稳定的。 具体算法如下: voidInsertsort() {for(i=2;i<=L;i++)/*依次插入R[2],R[3],……R[n]*/ if(R[i].key {R[0]=R[i];/*置监视哨*/ j=i-1; while(R[0].key {R[j+1]=R[j];/*向后移动记录*/ j--; } R[j+1]=R[0];/*插入r[i]*/ } } 举例: 模拟运行 R[0]R[1]R[2]R[3] 8106i=3 (3)效率分析 直接插入排序的时间复杂度为O(n2),辅助空间为O (1);直接插入排序是稳定的排序方法;直接插入排序最适合待排序关键字基本有序的序列。 编程实现对顺序表的直接插入排序: #defineMax100 typedefstruct{ intelem[Max]; intlength; }sstable; voidinsertsort(sstable*st) {inti,j; for(i=2;i<=st->length;i++) {st->elem[0]=st->elem[i];/*置监视哨*/ j=i-1; while(st->elem[0] {st->elem[j+1]=st->elem[j]; j--; } st->elem[j+1]=st->elem[0]; } } voidmain() {sstable*st;inti=1; printf("\n建立整数顺序表(以-1结束): \n"); scanf("%d",&st->elem[i]); while(st->elem[i]! =-1) {i++; scanf("%d",&st->elem[i]); } st->length=i-1; insertsort(st); printf("\n排序后的顺序表为: \n"); for(i=1;i<=st->length;i++) printf("%d",st->elem[i]); } 2. 二分插入排序(BinaryInsertongSort) (1)基本思想 直接插入算法虽然简单,但当记录数量n很大时,则比较次数将大大增加,对于有序表(限于顺序存储结构),为了减少关键字的比较次数,可采用二分插入排序。 二分插入排序的基本思想是: 用二分查找法在有序表中找到正确的插入位置,然后移动记录,空出插入位置,再进行插入。 (2)排序举例 例6.4 设有8个记录已排序,现插入新的关键字为653,请使用二分插入排序方法使之成为一个含有9个记录的新序列。 序列: 12345678 6087170275503512897908 low=1①②③high=8 m=(low+high)/2=(1+8)/2=4 第一步: 取关键字653,与序列中间位置①的关键字比较,653>275,在后半区继续查找; 第二步: 与后半区中间位置②的关键字比较,653>512,再继续在后半区找; 第三步: 与后半区中间位置③的关键字比较,653<897,经三次比较找到插入位置③,然后插入653。 具体算法如下: voidBInsSort() {for(i=2;i<=n;i++) {r[0]=r[i];/*将r[i]暂存到r[0]*/ while(low<=high)/*在r[low..high]中折半查找有序插入的位置*/ {m=(low+high)/2;/*折半*/ if(r[0].key high=m-1;/*插入点在低半区*/ else low=m+1;/*插入点在高半区*/ } for(j=i-1;j>=high+1;--j) r[j+1]=r[j];/*记录后移*/ r[high+1]=r[0]; }/*插入*/ } 二分插入排序辅助空间和直接插入相同,为O (1)。 从时间上比较,二分插入仅减少了比较次数,而记录的移动次数不变,时间复杂度仍为O(n2)。 二分插入排序是稳定的排序方法。 编程实现对顺序表的二分插入排序: #defineMax100 typedefstruct{ intelem[Max]; intlength; }sstable; voidBInsSort(sstable*st) {inti,j,m,low,high; for(i=2;i<=st->length;i++) {st->elem[0]=st->elem[i]; low=1;high=i-1; while(low<=high) {m=(low+high)/2; if(st->elem[0] high=m-1; else low=m+1; } for(j=i-1;j>=high+1;--j) st->elem[j+1]=st->elem[j]; st->elem[high+1]=st->elem[0]; } } voidmain() {sstable*st;inti=1; printf("\n建立整数顺序表(以-1结束): \n"); scanf("%d",&st->elem[i]); while(st->elem[i]! =-1) {i++; scanf("%d",&st->elem[i]); } st->length=i-1; BInsSort(st); printf("\n排序后的顺序表为: \n"); for(i=1;i<=st->length;i++) printf("%d",st->elem[i]); } 3. 希尔排序(Shell’sSort) 希尔排序又称“缩小增量排序”,它也是一种插入排序的方法,但在时间上较前两种排序方法有较大的改进。 (1)基本思想 先将整个待排序记录序列分割成若干子序列分别进行直接插入排序,待整个序列中的记录“基本有序时”,再对全体记录进行一次直接插入排序。 其特点是子序列不是简单的逐段分割,而是将相隔某个“增量”的记录组成一个子序列,所以关键字较小的记录不是一步一步地前移,而是跳跃式前移,从而使得在进行最后一趟增量为1的插入排序时,序列已基本有序,只要做少量比较和移动即可完成排序,时间复杂度较低。 (2)排序举例 例6.5 使用希尔排序方法把下面的待排序列变成一个从小到大的有序序列。 待排序列为: 40,30,60,80,70,10,20,4
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 第6章 查找与排序 查找 排序