第9章排序算法Word文档下载推荐.docx
《第9章排序算法Word文档下载推荐.docx》由会员分享,可在线阅读,更多相关《第9章排序算法Word文档下载推荐.docx(24页珍藏版)》请在冰豆网上搜索。
一个好的排序算法具有相对低的时间复杂度和空间复杂度,即算法应该尽可能减少运行时间,占用较少的额外空间。
4.排序算法的稳定性
用主关键字进行排序会得到唯一的结果,而用可能有重复的非主关键字进行排序,结果不是唯一的。
在数据序列中,如果有两个数据元素ri和rj,它们的关键字ki等于kj,且在未排序时,ri位于rj之前。
如果排序后,元素ri仍在rj之前,则称这样的排序算法是稳定的(stable),否则是不稳定的排序算法。
为简单起见,本章仅讨论内排序问题。
如不作特别说明,则待排序数据保存在一个数组中,并假定每个数据元素只含关键字,关键字为某种可比较的泛型,排序一般都是按关键字值递增的次序对数据进行排列。
各种排序算法定义在Sort类中,以静态方法的形式提供。
它们具有如下形式:
publicclassSort<
T>
whereT:
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);
按成绩排序(从高到低)"
StudentInfo.Sort(items,CompareKey.MarkD);
按姓名排序"
StudentInfo.Sort(items,CompareKey.Name);
staticvoidShow(StudentInfo[]items){
学号\t姓名\t成绩"
for(intj=0;
j<
items.Length;
j++){
Console.WriteLine(items[j].StudentID+"
\t"
+items[j].Name+"
+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<
n;
m++){
k=items[m];
for(i=0;
i<
m;
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+"
趟排序后:
j++)
Console.Write(items[j]+"
Console.WriteLine();
【例9.2】整型数组的直接插入排序算法测试。
int[]items={36,91,31,26,61,37};
Sort<
int>
.Show(0,items);
Sort<
.InsertSort(items);
Console.Write("
排序后"
.Show(0,items);
程序运行结果如下:
369131266137
第1趟排序后:
第2趟排序后:
313691266137
第3趟排序后:
263136916137
第4趟排序后:
263136619137
第5趟排序后:
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<
n;
m++){
i=Array.BinarySearch<
(items,0,m,k);
if(i<
0)
i=~i;
else
i++;
for(j=m;
j>
i;
j--)
items[j]=items[j-1];
items[i]=k;
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<
i++){
j=i-jump;
while(j>
=0){
if(items[j].CompareTo(items[j+jump])>
t=items[j];
items[j]=items[j+jump];
items[j+jump]=t;
j-=jump;
}
else
j=-1;
Console.Write("
jump="
+jump+"
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};
.ShellSort(items);
3691312661379719371
jump=5第1趟排序后:
3691126613797319371
jump=2第2趟排序后:
1263631613793719791
jump=1第3趟排序后:
1263136376171919397
希尔