交换low、high两个元素
}
将low位置的元素与支点元素交换
Quicksort递归调用左半段
Quicksort递归调用右半段
Else
应用直接插入排序法对数组元素排序
2.1快速排序算法介绍
快速排序就像它的名称所暗示的,是一种快速的分而治之的算法,平均时间复杂度为O(NLOG2N).它的速度主要归功于一个非常紧凑而且高度优化的内部循环。
其基本算法由以下步骤组成:
(1)如果S中的元素的数目为0或1,就返回。
(2)选择S中的任意一个元素V,V叫做支点。
(3)将S-{V}(剩下的元素在S中)分成两个分开的部分。
L={X属于S-{V}|X<=V}和R={X属于S-{V}|X>=V}。
(4)依次返回Quicksort(L)、v和Quicksort(R)的结果。
基本的快速排序算法可以应用递归实现。
关键的细节包括指点的选择和如何分组。
该算法允许把任何元素作为支点。
支点把数组分为两组:
小于支点的元素集和大于支点的元素集。
2.2快速排序分析
(1)最好情况:
快速排序的最好情况是支点把集合分成两个同等大小的子集,并且在递归的每个阶段都这样划分。
然后就有了两个一半大小的递归调用和线性的分组开销。
在这中情况下运行的时间复杂度就是O(n
)。
(2)最坏情况:
假设在每一步的递归调用中,支点都恰好是最小的元素。
这样小元素的集合L就是空的,而大元素集合R拥有除了支点以外的所有元素。
设T(N)是对N各元素惊醒快速排序所需的运行时间按,并假设对0或1各元素排序的时间刚好是1个时间单位。
那么对于N>1,当每次都运气很差地最小的元素作为支点,得到的原型时间满足T(N)=T(N-1)+N.即对N各项进行排序的时间等于递归排序大元素子集中的N-1各项所需要的时间加上进行分组的N个单位的开销。
最终得出:
T(N)=T
(1)+2+3+…+N=N(N+1)/2=O(
)
(3)错误方式:
比较常见的不明智的选择就是把第一个元素作为支点。
但如果输入是已经预先排过序的,或者是倒序的,该支点给出的分组就很糟糕,因为它是一个末端的元素;而且这种情况会在迭代中继续出现,会以O(
)的时间复杂度而告终,所以选择第一个元素作为支点不是好的策略。
中位选择:
把中间元素,即待排序序列中间位置的元素,作为支点是合理的选择。
当输入已经排过序时,这种选择在每次递归调用中都会给出理想的支点。
中值划分:
在上诉选择中使用中间值作为支点可以消除非随机输入时出现的退化情况。
但这是一种消极的选择,就是说仅仅是试图避免一个坏的支点而并没有去尝试选择一个更好的支点。
中值划分是一种选择比平均情况更好的支点的尝试。
在中值划分中,一种比较简单而有效的策略是选择待排序列的第一个、中间一个以及最好一个记录这3个值的中值作为支点。
同样道理,也可以从待排序列中等距离地选取5各值,并将这5各值的中值作为支点。
选择支点
分组
Quicksort
Quicksort
图2.2-1快速排序的基本过程
2.3算法与时间分析设计
(1)快速排序:
如果每一次分划操作后,左、右两个子序列的长度基本相等,则快速排序的效率最高,其最好情况时间复杂度为O(nlog2n);反之,如果每次分划操作所产生的两个子序列,其中之一为空序列,此时,快速排序效率最低,其最坏情况时间复杂度为O(n2)。
如果选择左边第一个元素为主元,则快速排序的最坏情况发生在原始序列正向有序或反向有序时。
快速排序的平均情况时间复杂度为O(nlog2n)。
(2)冒泡排序:
当原始数据正向有序时,冒泡排序出现最好情况。
此时,只需进行一趟排序,作n-1次关键字比较,因此最好情况下的时间复杂度是O(n)。
当原始数据反向有序时,冒泡排序出现最坏情况。
此时,需进行n-1趟排序,第i趟作(n-i)次关键字间的比较,并且需执行(n-i)次元素交换,所以,比较次数为:
因此,最坏情况下的时间复杂度为O(n2)。
3设计实践
实验目的是实现快速排序的优化版本,并且比较在下列情况下的算法性能:
(1)cutoff值从0~10。
cutoff值的作用是只有当数组长度小于等于这个值时才使用另一种简单排序方法对其排序,否则使用快速排序算法排序。
(2)选定支点的方法分别是“第一个元素”,“第三个元素中值”,“五个元素的中值”。
输入文件中测试数组大小可以从100个数到10000个数不等。
程序从input.txt文件读入,输出到output.txt文件。
程序的重点在于对每种组合情况下算法性能的比较。
不同的运行时间要用图表标示出来,在图表中要区分由于文件大小不同而产生的差别。
设计分析:
主函数分析:
从input.txt文件中读取数据,放入Array数组中,在执行QuickSort函数之前用clock函数获取系统时间放入start变量中。
QuickSort函数执行完了之后再次使用clock函数获取系统时间放入stop变量中。
使用stop-start获得QuickSort函数的执行时间。
将运行时间和排序后的数组一起输入到output.txt文件中。
关闭input.txt文件,关闭output.txt文件Quicksort函数分析:
Quicksort函数包括五个参数分别是数组的地址Array,待排序的第一个元素在数组中的下标,待排序的最后一个元素在数组中的下标,cutoff(小于cutoff时使用另一种简单的方法进行排序),支点median。
Quicksort函数分为以下几个步骤:
1.确定数组的大小是否为0或1,若为0或1则直接退出函数排序为完成。
2.判断支点median取值是否为1,3或5,否则返回错误。
3.判断cutoff取值是否恰当。
4.用median函数获得支点。
5.在分别设定指针指向low和high数组第一个和最后一个元素。
6.通过low++和--high的方式让low和high向中间移动直到Array[low]大于median或Array[high]小于median。
7.判断low是否小于high,若不小于,则交换Array[low]与Array[high]的值,否则跳出循环排序完成。
8.将原数组分成两部分后分别用Quicksort函数处理两个数组。
Quicksort函数排序是一个将数组以median为分界分成一个元素都小于median的数组和一个元素都大于median的数组。
再将这两个数组用Quicksort函数处理的递归调用过程。
4测试方法
本次测试的目的是分析支点(median的值)和数组小于多少时使用简单排序算法(cutoff的值)值选择和待排序数据的情况(顺序、逆序、随机)对快速排序算法效率的影响。
测试输入待排序的不同数字。
Cutoff值从0~20。
Cutoff值的作用是只有当数组长度小于等于这个值时才使用另一种简单排序方法对其排序,否则使用快速排序算法排序。
对本程序的测试不仅是测试程序是否正常运行,是否排序正确,更重要的是记录不同情况下的运行时间(runningtime)来比较在不同的支点算法中的运行效率。
根据题目要求生成不同的测试用例。
需要3种类型的测试用例。
(1)顺序(Sorted)的测试用例;
(2)逆序(Reverse-Ordered)的测试用例;
(3)随机(Random)的测试用例。
提示:
对于第三种测试用例,可使用rand()函数生成。
如果每次运行时间太小难以记录,可以重复运行100次,甚至更多来求平均值。
注:
每个测试用例中都包含不同Size(待排序列大小)的测试。
Size一般不要大于20000。
建议使用Size分别为1000、2000、3000、4000、5000、6000、7000、8000、9000、10000等10种类型。
使用测试文件对采用三种不同支点选择方法的算法进行测试,每次测试可以使用不同的cutoff值(cutoff从0~20,选择其中的0、5、10、15、20,共5个)。
综上所述,一般来说较为全面的做法是做出3(3种支点算法)×5(5种cutoff值)×3(3种顺序的测试用例)×10(10种Size),共450组测试,分别记录它们的运行时间,再使用图表加以形象化的显示。
表4-1支点为MedianOf3情况下的runningtime(单位:
秒)
Combination
Size
InputType
Sorted
Reverse-Ordered
Random
MedianOf3
Pivot/cutoff
At0
1000
0.00016
0.00015
0.00031
2000
0.00047
0.00031
0.00078
3000
0.00062
0.00047
0.00094
4000
0.00078
0.00079
0.00156
5000
0.00109
0.00110
0.00203
6000
0.00125
0.00110
0.00250
7000
0.00140
0.00140
0.00266
8000
0.00156
0.00187
0.00328
9000
0.00171
0.00203
0.00343
10000
0.00187
0.00219
0.00422
说明:
此表只是测试中的其中一部分数据,具体数据应依据实际上机运行结果,可参见书上79页。
5程序运行效果
测试输入:
intput.txt文件内容
图5-1录入数据界面
说明:
第一行是待排序的数字个数,下面的是要进行排序的数字,由于测试的数字较多,所以只截取了其中的一个作为参考。
图5-2程序运行后的界面
说明:
运行程序所得到的结果初始界面,每次测试可以使用不同的cutoff值(cutoff从0~20,选择其中的0、5、10、15、20,共5个)。
图5-3输入数字显示时间界面
说明:
按要求输入cutoff和选择的支点,输出的结果如上图。
测试输出:
output.txt文件内容
图5-4运行结果界面
说明:
casenumber表示测试的第几组数据,numberofelements表示数组的长度,然后就是数组中元素有序排列。
6设计心得
为期一周的课程设计接近尾声了,在这整整五天里,我学到很多的东西,不仅可以巩固以前所学过的知识,还可以学到很多在书本上所没有学到过的知识。
还有我意识到以前学习的Java还有C++这些语言都是很有必要的,因为在这次的课程设计中,运用到很多的以前知识,做到编程的时候,有些内容感觉似曾相识但又感觉没有教过一样。
由此可见,以前的基础牢固是非常的重要。
在这次课程设计中,最重要的是程序的调试。
对于我做的快速排序的基本功能都已实现,然而在进行了排序的比较哪个执行的快,效率又高,刚开始的时候,数据是没什么变化,但通过我列举出很长的数据时,终于有了本质的变化。
在这次课程设计当中,我了解到了我的不足,如算法的不完善、不细心和耐心不是很好等等。
不细心的我在调试程序时,老是因为某个书写错误导致错误,对这些错误,我不得不花大量的时间去更正,并且还要重复检查是否出现雷同的错误而导致程序不能运行。
但是通过这次课程设计,我的这些缺点有些改善。
我在写新的程序时,首先要考虑的深入一点、仔细一点,这样要修改程序的时间就会少很多。
并且也不会因为自己不细心而导致的浪费时间的情况出现。
我觉得做任何事情都应该有个规划。
之后的工作按照规划逐步展开完成。
对于一个完整的程序设计,首先需要总体规划写程序的步骤,分块写分函数写,然后写完一部分马上纠错调试。
而不是像我第一个程序,一口气写完,然后再花几倍的时间调试。
一步步来,走好一步再走下一步。
虽然在做的过程中,遇到了很多的问题,并且问题很难解决,但是最后通过自己的努力也成功的运行出来了。
通过这次快速排序的设计让我明白了理论与实践相结合的重要性了,只有理论知识是远远不够的,只有把所学的理论知识与实践结合起来,才能更好的发挥出自己的能力.也让我知道不仅会做,也要把课程设计中所学到的东西用到未来的学习中去。
总之,这次课程设计让我受益匪浅,使我更加深入地理解了数据结构的原理,同时加深了对于主要知识的应用的认识,同时也更加清楚了编程的编写过程和运行过程。
这不仅加深和巩固了我们的课本知识,而且增强了我们自己动脑,自己动手的能力。
7附录
程序代码如下:
#include
#include
#include
#include
#defineCLOCKS_PER_SEC((clock_t)1000)
#defineSIZE10000/*数组最大的容量*/
intMedianOf3(inta[],intlow,inthigh);
intMedianOf5(inta[],intlow,inthigh);
voidSwap(int*a,int*b);
voidQuickSort(inta[],intleft,intright,intcutoff,intmedian);
voidInsertionSort(inta[],intlow,inthigh);
intmain()
{
inti,group=0,numbOFelements,elements,Amount;
intArray[10000];
intcutoff=0,median=3;
clock_tstart,stop;
FILE*InputPTR,*OutputPTR;/*input和output文件指针*/
InputPTR=fopen("C:
\\Users\\Administrator\\Desktop\\源源\\Bliss\\input.txt","r+");
OutputPTR=fopen("C:
\\Users\\Administrator\\Desktop\\源源\\Bliss\\output.txt","w+");
if(InputPTR==NULL)/*检查input文件是否存在*/
{
printf("Cannotopenfile!
");
exit(0);
}
printf("PleaseInputthevalueofcutoff(0~20):
");
scanf("%d",&cutoff);
printf("PleaseInputthevalueofmedian(1or3or5):
");
scanf("%d",&median);
Amount=fscanf(InputPTR,"%d",&numbOFelements);/*读取每次迭代的元素的个数*/
while(Amount!
=EOF)/*当读到的不是文件的末尾*/
{
group++;
fprintf(OutputPTR,"Casenumber:
%d\nNumberofelements:
%d\n",group,numbOFelements);/*输出的格式*/
for(i=0;ifscanf(InputPTR,"%d",&Array[i]);/*将输入读入到数组中*/
fgetc(InputPTR);
}
fgetc(InputPTR);
start=clock();
QuickSort(Array,0,numbOFelements-1,cutoff,median);/*给数组排序*/
stop=clock();
printf("Thetimeis%lfseconds!
\n",(double)(stop-start)/CLOCKS_PER_SEC);
for(i=0;i{
fprintf(OutputPTR,"%d",Array[i]);/*打印排序后的数组*/
}
Amount=fscanf(InputPTR,"%d",&numbOFelements);
fprintf(OutputPTR,"\n\n");
}
fclose(InputPTR);
fclose(OutputPTR);
return0;
}
/******支点(pivot)选择三个值的中值的算法******/
intMedianOf3(inta[],intlow,inthigh)
{
intmid=(low+high)/2;/*确定中间位置*/
if(a[low]>a[mid]){
Swap(&a[low],&a[mid]);
}if(a[low]>a[high]){
Swap(&a[low],&a[high]);
}if(a[mid]>a[high]){
Swap(&a[mid],&a[high]);
}
Swap(&a[mid],&a[high]);/*交换排序后的中间元素和最后元素的值*/
returna[high];
}
/******支点(pivot)选择五个值的中值的算法******/
intMedianOf5(inta[],intlow,inthigh)
{
inti,temp,j,largest;
intStep;
Step=(high-low)/4;/*设定步长为四分之一,这样便能选出五个元素*/
for(j=0;j<4;++j)
{
largest=high-j*Step;/*每次迭代选择不同的值作为最大值*/
for(i=j+1;i<5;++i)
{/*比较每次选中的值,用来找到最大值*/
if(a[