第9章排序算法.docx
- 文档编号:9157825
- 上传时间:2023-02-03
- 格式:DOCX
- 页数:24
- 大小:163.31KB
第9章排序算法.docx
《第9章排序算法.docx》由会员分享,可在线阅读,更多相关《第9章排序算法.docx(24页珍藏版)》请在冰豆网上搜索。
第9章排序算法
第9章排序算法
教学要点
有序的数据便于处理,如字典和电话簿一般都是按某种顺序排列它们所管理的数据。
排序是将某种数据结构按数据元素的关键字值的大小以递增或递减的次序排列的过程,它在计算机数据处理中有着广泛的应用。
本章介绍排序的基本概念,讨论多种经典排序算法,包括插入、交换、选择和归并等排序算法,并说明各种排序算法的运行效率。
本章在VisualStudio中用名为sort的类库型项目实现有关数据结构与算法的基础类定义,用名为sorttest的应用程序型项目实现测试和演示程序。
建议本章授课6学时,实验3学时。
9.1数据序列及其排序
9.1.1排序的基本概念
1.数据序列、关键字和排序
数据序列是特定数据结构中的一系列元素,它是待加工处理的数据元素的有限集合。
例如,每个学生的信息是待处理的数据元素,所有学生信息则组成一个待处理的数据序列(学生成绩表)。
数据序列的排序建立在数据元素间的比较操作基础之上。
一个数据元素可由多个数据项组成,以数据元素某个数据项作为比较和排序依据,则该数据项称为排序关键字。
例如,学生信息由学号、专业、姓名、成绩等多个数据项组成。
如果按学号进行排序,则学号就是排序关键字。
在数据序列中,如果某一关键字能唯一地标识一个数据元素,则称这样的关键字为主关键字(primarykey)。
主关键字的值不同,则数据元素也不同,因此用主关键字进行排序会得到唯一确定的结果。
例如,在一个学校,学生的学号是唯一的,可以作为学生信息的主关键字,而学生的姓名则可能有重复,姓名不是主关键字,依据姓名排序的结果不是唯一的。
排序是将某种数据结构按数据元素的关键字的值以递增或递减的次序排列的过程,它在计算机数据处理中有着广泛的应用。
2.内排序与外排序
根据排序过程中涉及的存储器的不同,排序问题一般可分为内排序和外排序两类:
●内排序:
如果待排序的数据序列的数据元素个数较少,在整个排序过程中,所有的数据元素可以同时保留在内存中。
●外排序:
待排序的数据元素非常多,它们必须存储在磁盘等外部存储介质上,在整个排序过程中,需要多次访问外存逐步完成数据的排序。
3.排序算法的性能评价
像评价其他算法一样,评价排序算法的性能指标主要是算法的时间复杂度和空间复杂度:
●时间复杂度:
数据排序的基本操作是数据的比较与移动,分析某个排序算法的时间复杂度,就是要确定该算法执行中的数据比较次数或数据移动次数与待排序数据序列的长度之间的关系。
●空间复杂度:
数据的排序过程需要一定的内存空间才能完成,这包括待排序数据序列本身所占用的内存空间,以及其他附加的内存空间。
分析某个排序算法的空间复杂度,就是要确定该算法执行中,所需附加内存空间与待排序数据序列的长度之间的关系。
一个好的排序算法具有相对低的时间复杂度和空间复杂度,即算法应该尽可能减少运行时间,占用较少的额外空间。
4.排序算法的稳定性
用主关键字进行排序会得到唯一的结果,而用可能有重复的非主关键字进行排序,结果不是唯一的。
在数据序列中,如果有两个数据元素ri和rj,它们的关键字ki等于kj,且在未排序时,ri位于rj之前。
如果排序后,元素ri仍在rj之前,则称这样的排序算法是稳定的(stable),否则是不稳定的排序算法。
为简单起见,本章仅讨论内排序问题。
如不作特别说明,则待排序数据保存在一个数组中,并假定每个数据元素只含关键字,关键字为某种可比较的泛型,排序一般都是按关键字值递增的次序对数据进行排列。
各种排序算法定义在Sort类中,以静态方法的形式提供。
它们具有如下形式:
publicclassSort
IComparable{
publicstaticvoidInsertSort(T[]items);
publicstaticvoidShellSort(T[]items);
publicstaticvoidBubbleSort(T[]items);
publicstaticvoidQuickSort(T[]items,intnLower,intnUpper);
publicstaticvoidSelectSort(T[]items);
publicstaticvoidHeapSort(T[]items);
publicstaticvoidMergeSort(T[]items);
}
9.1.2C#数组的排序操作
.NETFramework的类库中定义的System.Array类是C#中的各种数组的基类。
Array类提供了许多用于排序、查找和复制数组的方法,其中排序功能以具有多种重载形式的Sort方法提供。
它们使用QuickSort算法进行排序,该算法执行不稳定排序,亦即,如果两元素相等,则其原顺序在排序后可能会改变。
Array类的Sort方法都是静态方法,它们分别具有下列形式:
1)publicstaticvoidSort(Arrayarray);
array为要排序的一维数组。
它的元素类型要实现接口IComparable,该实现定义了元素间的比较协议,据此对整个一维数组中的元素进行排序。
2)publicstaticvoidSort(Arrayarray,intindex,intlength);
array为要排序的一维数组,index为排序范围的起始索引,length为排序范围内的元素数。
3)publicstaticvoidSort(Arrayarray,IComparercomparer);
array为要排序的一维数组,comparer为比较元素时要使用的“比较器”对象,它实现IComparer接口,在该接口规定的Compare方法中决定数据元素之间的比较规则。
下面的例子演示Sort方法和有关类与接口的使用方法。
【例9.1】学生信息表的定义与排序演示。
学生信息表的元素类型StudentInfo定义如下,该类对象之间是可以比较的,缺省的比较规则是通过实现由IComparable接口定义的CompareTo()方法确定的,在这里学生间的比较等价于比较学生的学号。
StudentInfo类的Sort方法直接调用Array.Sort方法,完成学生信息表的缺省排序方式。
publicclassStudentInfo:
IComparable{
privateintstudentID;
privatestringname;
privatedoublemark;
publicStudentInfo(intid,stringname,doublemark){
this.studentID=id;this.name=name;this.mark=mark;
}
publicintStudentID{
get{returnstudentID;}
set{studentID=value;}
}
publicstringName{
get{returnname;}
set{name=value;}
}
publicdoubleMark{
get{returnmark;}
set{mark=value;}
}
publicintCompareTo(objectobj){
if(objisStudentInfo){
StudentInfodi=(StudentInfo)obj;
returnstudentID.CompareTo(di.StudentID);
}
thrownewArgumentException(String.Format("objectisnotaStudentInfo"));
}
publicstaticvoidSort(StudentInfo[]items){Array.Sort(items);}
publicstaticvoidSort(StudentInfo[]items,CompareKeykey){
Array.Sort(items,newStudentComparer(key));
}
}
如果要按其他的方式比较StudentInfo类型的对象,则需向Array.Sort方法提供一个实现IComparer接口的“比较器”对象,以定义所需的比较规则。
下面定义的StudentComparer类,它在IComparer接口所规定的Compare方法中根据私有数据成员key的值决定StudentInfo对象之间的比较规则,而key的值由该“比较器”类的构造方法设置。
publicenumCompareKey{ID,Name,Mark,IDD,NameD,MarkD}
classStudentComparer:
IComparer{
privateCompareKeykey;
publicCompareKeyKey{get{returnkey;}set{key=value;}}
publicStudentComparer(CompareKeyk){key=k;}
intIComparer.Compare(Objectx,Objecty){
StudentInfoX=(StudentInfo)x;
StudentInfoY=(StudentInfo)y;
switch(key){
caseCompareKey.Name:
return(X.Name.CompareTo(Y.Name));
caseCompareKey.NameD:
return(Y.Name.CompareTo(X.Name));
caseCompareKey.Mark:
return(X.Mark.CompareTo(Y.Mark));
caseCompareKey.MarkD:
return(Y.Mark.CompareTo(X.Mark));
caseCompareKey.IDD:
return(Y.StudentID.CompareTo(X.StudentID));
default:
returnX.StudentID.CompareTo(Y.StudentID);
}
}
}
下面的程序分别按学号、成绩和姓名对学生信息表进行排序。
publicclassArraySortTest{
staticvoidMain(string[]args){
StudentInfo[]items=newStudentInfo[5];
SetData(items);
Show(items);
Console.WriteLine("按学号排序");
StudentInfo.Sort(items);
Show(items);
Console.WriteLine("按成绩排序(从高到低)");
StudentInfo.Sort(items,CompareKey.MarkD);
Show(items);
Console.WriteLine("按姓名排序");
StudentInfo.Sort(items,CompareKey.Name);
Show(items);
}
staticvoidShow(StudentInfo[]items){
Console.WriteLine("学号\t姓名\t成绩");
for(intj=0;j Console.WriteLine(items[j].StudentID+"\t"+items[j].Name+"\t"+items[j].Mark); } } staticvoidSetData(StudentInfo[]items){ items[0]=newStudentInfo(3016,"张超",89); items[1]=newStudentInfo(3053,"马飞",80); items[2]=newStudentInfo(3041,"刘羽",96); items[3]=newStudentInfo(3025,"赵备",79); items[4]=newStudentInfo(3039,"关云",85); } } 9.2插入排序 插入排序(insertionsort)的基本思想是: 每趟将一个待排序的数据元素,按其关键字大小,插入到已排序的有序序列中,从而得到一个新的、元素个数增1的有序序列,直到全部元素插入完毕。 下面介绍直接插入排序算法和希尔排序算法。 9.2.1直接插入排序 1.直接插入排序算法 直接插入排序(straightinsertionsort)的基本思想是: 在第m趟插入第m个数据元素k时,前m-1个数据元素已组成有序数据序列Sm-1,将k与Sm-1中各数据元素依次进行比较并插入到适当位置,得到新的序列Sm仍是有序的。 设有一个待排序的数据序列为{36,91,31,26,61},直接插入排序算法描述如下: 1)初始化: 以items[0]=36建立有序子序列S0={36},m=1。 2)在第m趟,欲插入元素值k=items[m],在Sm-1中进行顺序查找,找到k值应插入的位置i;从序列Sm-1末尾开始到i位置依次向后移动一位,空出位置i;将k值插入items[i],得到有序子序列Sm,m=m+1。 例如,当m=1时,k=91,i=1,S1={36,91}。 当m=2时,k=31,i=0,S2={31,36,91} 3)重复步骤2,依次将其他数据元素插入到已排序的子序列中。 图9.1显示了对上述数据序列的直接插入排序过程。 2.数组的直接插入排序算法实现 publicstaticvoidInsertSort(T[]items){ Tk; inti,j,m,n=items.Length; for(m=1;m k=items[m]; for(i=0;i if(k.CompareTo(items[i])<0){ for(j=m;j>i;j--) items[j]=items[j-1]; items[i]=k; break; } } Show(m,items); } } 图9.1序列的直接插入排序过程描述 Sort类中还定义了名为Show的帮助方法,用以显示数据序列。 publicstaticvoidShow(inti,T[]items){ if(i==0) Console.Write("数据序列: "); else Console.Write("第"+i+"趟排序后: "); for(intj=0;j Console.Write(items[j]+""); Console.WriteLine(); } 【例9.2】整型数组的直接插入排序算法测试。 staticvoidMain(string[]args){ int[]items={36,91,31,26,61,37}; Sort Sort Console.Write("排序后"); Sort } 程序运行结果如下: 数据序列: 369131266137 第1趟排序后: 369131266137 第2趟排序后: 313691266137 第3趟排序后: 263136916137 第4趟排序后: 263136619137 第5趟排序后: 263136376191 排序后数据序列: 263136376191 3.算法分析 很明显,对于关键字相同的元素,直接插入排序不会改变它们原有的次序。 所以,直接插入排序算法是稳定的。 下面来看看数据比较次数和数据移动次数与待排序数据序列的长度之间的关系。 由第8章查找算法可知,在具有m个数据元素的有序线性表中查找一个数据元素的平均比较次数为(m+1)/2。 所以,直接插入排序的平均比较次数为 由第2章线性表可知,在长度为m的顺序表中,在等概率条件下,插入一个数据元素的平均移动次数是m/2,即需要移动线性表全部数据元素的一半。 所以,直接插入排序的平均移动次数为: 由上可知,直接插入排序算法的时间复杂度为O(n2)。 思考题: 可以用二分查找算法代替顺序查找算法完成在有序表中查找一个数据元素的工作,这样可以降低平均比较次数,但并不能减少移动次数。 改进后的算法实现如下所示: publicstaticvoidInsertSortBS(T[]items){ Tk; inti,j,m,n=items.Length; for(m=1;m k=items[m]; i=Array.BinarySearch if(i<0) i=~i; else i++; for(j=m;j>i;j--) items[j]=items[j-1]; items[i]=k; Show(m,items); } } 9.2.2希尔排序 1.希尔排序算法的基本思想 希尔排序(Shellsort)又称缩小增量排序(diminishingincrementsort),它也属于插入排序类的方法。 它的基本思想是: 先将整个序列分割成若干子序列分别进行直接插入排序,待整个序列基本有序时,再进行全序列直接插入排序,这样可使排序过程加快。 直接插入排序每次比较的是相邻的数据元素,一趟排序后数据元素最多移动一个位置。 设待排序序列的数据元素个数为n,假定序列中第1个数据元素的数值最大,排序后的最终位置应该是序列的最后一个元素,则将它从头移动到尾需要n-1步。 如果该值一次就能跳到最后,排序的速度就快得多。 希尔排序算法在排序之初,进行比较的是相隔较远的数据元素,使得数据元素移动时能够跨越多个位置;然后逐渐减少被比较数据元素间的距离(缩小增量),直至距离为1时,各数据元素都已按序排好。 2.数组的希尔排序算法实现 publicstaticvoidShellSort(T[]items){ Tt; intn=items.Length,jump=n/2; inti,j,m=1; while(jump>0){ for(i=jump;i j=i-jump; while(j>=0){ if(items[j].CompareTo(items[j+jump])>0){ t=items[j]; items[j]=items[j+jump]; items[j+jump]=t; j-=jump; } else j=-1; } } Console.Write("jump="+jump+""); Show(m,items);m++; jump/=2; } } 在希尔排序算法的代码中有三重循环: ●最外层循环(while语句): 控制增量jump,其初值为数组长度n的一半,以后逐次减半缩小,直至增量为1。 整个序列分割成jump个子序列,分别进行直接插入排序。 ●中层循环(for语句): 相隔jump的元素进行比较、交换,完成一轮子序列的直接插入排序。 ●最内层循环(while语句): 将元素items[j]与相隔jump的元素items[j+jump]进行比较,如果两者是反序的,则执行交换。 重复往前(j-=jump)与相隔jump的元素再比较、交换;当items[j]<=items[j+jump]时,表示元素items[j]已在这趟排序后的位置,不需交换,则退出最内层循环。 例如,对于一个待排序的数据序列{36,91,31,26,61,37,97,1,93,71},数据序列长度n=10,初始增量jump=n/2。 希尔排序过程如下所叙: 1)jump=5,j从第0个位置元素开始,将相隔jump的元素items[j]与元素items[j+jump]进行比较。 如果反序,则交换,依次重复进行完一趟排序,得到序列{36,91,1,26,61,37,97,31,93,71}。 2)jump=2,相隔jump的元素组成子序列{36,1,61,97,93}和子序列{91,26,37,31,71}。 在子序列内比较元素items[j]与元素items[j+jump],如果反序,则交换,依次重复。 得到序列{1,26,36,31,61,37,93,71,97,91}。 3)jump=1,在全序列内比较元素items[j]与元素items[j+jump],如果反序,则交换;得到序列{1,26,31,36,37,61,71,91,93,97}。 【例9.3】整型数组的希尔排序算法测试。 int[]items={36,91,31,26,61,37,97,1,93,71}; Sort Console.Write("排序后"); Sort 程序运行结果如下: 数据序列: 3691312661379719371 jump=5第1趟排序后: 3691126613797319371 jump=2第2趟排序后: 1263631613793719791 jump=1第3趟排序后: 1263136376171919397 排序后数据序列: 1263136376171919397 3.算法分析 希尔
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 排序 算法
![提示](https://static.bdocx.com/images/bang_tan.gif)