快速排序的设计.docx

上传人:b****8 文档编号:9178830 上传时间:2023-02-03 格式:DOCX 页数:21 大小:1.21MB
下载 相关 举报
快速排序的设计.docx_第1页
第1页 / 共21页
快速排序的设计.docx_第2页
第2页 / 共21页
快速排序的设计.docx_第3页
第3页 / 共21页
快速排序的设计.docx_第4页
第4页 / 共21页
快速排序的设计.docx_第5页
第5页 / 共21页
点击查看更多>>
下载资源
资源描述

快速排序的设计.docx

《快速排序的设计.docx》由会员分享,可在线阅读,更多相关《快速排序的设计.docx(21页珍藏版)》请在冰豆网上搜索。

快速排序的设计.docx

快速排序的设计

 

数据结构课程设计报告

快速排序的设计

目录

1设计内容1

1.1设计目的1

1.2运行环境1

1.3排序及排序算法类型1

2设计分析2

2.1快速排序算法介绍3

2.2快速排序分析3

2.3算法与时间分析设计5

3设计实践5

4测试方法6

5程序运行效果7

6设计心得9

7附录10

快速排序的设计

1设计内容

1.1设计目的

1.要求每个学生掌握数据结构的基本知识和技能。

2.学会自己分析和设计自己的课程,并能掌握其方法。

3.培养自己的算法能力,以及各种的排序,能够灵活运行每个排序去编程,并且遇到错误的时候,能够通过自己的能力找出代码中的错误并加以修改。

4.在学会使用软件的情况下,结合课外阅读能够使用多程序的调用问题等。

5.通过自己的课程设计能够巩固自己的知识,并加以运用。

1.2运行环境

MicrosoftVisualC++6.0

VisualC++系列产品是微软公司推出的一款优秀的C++集成开发环境,其产品定位为Windows95/98、NT、2000系列Win32系统程序开发,由于其良好的界面和可操作性,被广泛应用。

由于2000年以后,微软全面转向NET平台,VisualC++6.0成为支持标准C/C++规范的最后版本。

VC++是Windows平台上的C++编成环境,学习VC要了解很多Windows平台的特性并且还要掌握MFC、ATL、COM等的知识,难度比较大。

Windows下编成需要了解Windows的消息机制以及回调函数的原理;MFC是Win32API的包装类,需要理解文档视图类的结构,窗口类的结构,消息流向等等;COM是代码共享的二进制标准,需要掌握其基本原理等等。

VC作为一个主流的开发平台一直深受编成爱好者的喜爱,但是很多人却对它的入门感到难于上青天,究竟原因主要是大家对他错误的认识造成的,严格的来说VC++不是一门语言,虽然它和C++之间有密切的关系,如果形象点比喻的话,可以C++看作为一种“工业标准”是在遵循“工业标准”的前提下扩展而来的。

VC++应用程序的开发主要有两种模式,一种是WINAPI方式,另一种则是MFC方式,传统的WINAPI再次封装,所以MFC相对于WINAPI开发更具备效率优势,但为了对WINDOWS开发有一个较为全面细致的认识,笔者在这里还是以讲解WINAPI的相关内容为主线。

1.3排序及排序算法类型

排序和查找是计算机程序设计中两种常用的数据处理操作。

排序是数据处理领域和软件设计领域中一种常见而且重要的运算,排序就是把一组记录的任意序列按照某个域的值递增或递减的次序重新排列的过程。

排序的目的是为了方便查找。

排序分为内部排序和外部排序。

在内存中进行的排序叫做内部排序,利用到外部存储的排序叫外部排序。

按照其策略,内部排序分类:

插入排序,交换排序,选择排序,归并排序,基数排序。

插入排序:

依次将无序序列中的一个待排序的记录,按其关键字值的大小插入已排好序的序列中的适当位置,直到整个序列有序为止。

该类排序方法典型的有直接插入排序和基于分组方法的Shell排序。

交换排序:

两两比较序列中记录关键字值,并将次序相反的两个记录进行交换,直到整个序列中没有反序的记录偶为止。

该类排序方法典型的有简单的冒泡排序和应用分治法的快速排序。

选择排序:

不断从待排序的记录序列中,选取关键字最小(或最大)的记录,放到它的目标位置,直到所有记录都没选完为止。

该类排序方法典型的有简单选择排序和基于堆结构的堆排序。

归并排序:

利用归并技术不断把待序列中的有序子序列进行合并,直到合并为一个有序序列为止。

所谓归并是指两个或两个以上的有序序列合成一个有序序列。

基数排序:

按待排序记录的关键字的组成成分(或者叫:

“位”)来进行排序的方法。

基本思想是每次按记录关键字某一位的值将所有记录分配到相应编号的桶中,再按桶的编号依次将记录收集。

这样按记录关键字从低位到高位,将记录不断地分配和收集,从而得到一个有序的记录序列。

2设计分析

程序主要由6部分组成,分别是:

(1)程序入口main函数;

(2)Quicksort,快速排序算法的实现部分;

(3)MedianOf3,选择三个值的中值作为支点;

(4)MedianOf5,选择五个值的中值作为支点;

(5)Swap,简单地交换两个元素的值;

(6)InsertionSort,在数组长度小于cutoff值时使用插入排序来代替快速排序。

下面描述main和Quicksort两个函数的基本算法的运算过程。

main函数:

打开input.txt和output.txt文件;

读入数的个数n;

从文件中顺序读入n个数,并放到数组中;

应用Quicksort对该数组排序;

将排序后的数输出到文件output.txt中;

读入下一组数的个数,继续上述过程;

关闭文件。

Quicksort函数:

参数:

待排数组,待排段的起点位置,待排段的终点位置,cutoff值,支点选择方法

If数组是空的

Exit

If待排数段的元素个数大于等于cutoff值,且元素个数大于等于支点选择方法所要求的元素个数

根据支点选择方法选择一个元素作为支点

设置low为起点值、high为终点值

Whilelow<=high{

Whilelow位置的值小于支点值

low++

Whilehigh位置的值大于支点值

high--

Iflow

交换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;i

fscanf(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[

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > IT计算机 > 互联网

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1