数据结构课程设计1.docx
《数据结构课程设计1.docx》由会员分享,可在线阅读,更多相关《数据结构课程设计1.docx(16页珍藏版)》请在冰点文库上搜索。
数据结构课程设计1
数据结构课程设计
题目利用随机函数产生30000个随机整数,利用插入排序、希尔排序、起泡排序、快速排序、选择排序、堆排序、归并排序等排序方法进行排序,统计每一种排序上机所花费的时间,并和理论上时间进行对比分析。
学生姓名
学号
学院
专业
指导教师
年月日
一、设计题目
利用随机函数产生30000个随机整数,利用插入排序、起泡排序、快速排序、选择排序、堆排序、归并排序等排序方法进行排序,统计每一种排序上机所花费的时间,并和理论上时间进行对比分析。
二、算法设计的思想
1.简单选择排序
操作方法:
第一趟,从n个记录中找出关键码最小的记录与第一个记录交换;第二趟,从第二个记录开始的n-1个记录中再选出关键码最小的记录与第二个记录交换;如此,第i趟,则从第i个记录开始的n-i+1个记录中选出关键码最小的记录与第i个记录交换,直到整个序列按关键码有序。
【效率分析】
空间效率:
仅用了一个辅助单元。
时间效率:
简单选择排序中,所需进行记录移动的操作次数较小,其最小值为0,最大值为3(n-1)。
然而,无论记录的初始排列如何,所需进行的关键字之间的比较次数相同,均为n(n-1)/2。
因此,总的时间复杂度也是O(n2)。
2.直接插入排序
设有n个记录,存放在数组r中,重新安排记录在数组中的存放顺序,使得按关键码有序。
即r[1].key≤r[2].key≤……≤r[n].key
先来看看向有序表中插入一个记录的方法:
设1<j≤n,r[1].key≤r[2].key≤……≤r[j-1].key,将r[j]插入,重新安排存放顺序,使得r[1].key≤r[2].key≤……≤r[j].key,得到新的有序表,记录数增1。
【效率分析】
空间效率:
仅用了一个辅助单元。
时间效率:
向有序表中逐个插入记录的操作,进行了n-1趟,每趟操作分为比较关键码和移动记录,而比较的次数和移动记录的次数取决于待排序列按关键码的初始排列。
最好情况下:
即待排序列已按关键码有序,每趟操作只需1次比较2次移动。
总比较次数=n-1次
总移动次数=2(n-1)次
最坏情况下:
即第j趟操作,插入记录需要同前面的j个记录进行j次关键码比较,移动记录的次数为j+2次。
平均情况下:
即第j趟操作,插入记录大约同前面的j/2个记录进行关键码比较,移动记录的次数为j/2+2次。
由此,直接插入排序的时间复杂度为O(n2)。
是一个稳定的排序方法
3.希尔排序(Shell’sSort)
直接插入排序算法简单,在n值较小时,效率比较高,在n值很大时,若序列按关键码基本有序,效率依然较高,其时间效率可提高到O(n)。
希尔排序即是从这两点出发,给出插入排序的改进方法。
希尔排序方法:
1.选择一个步长序列t1,t2,…,tk,其中ti>tj,tk=1;
2.按步长序列个数k,对序列进行k趟排序;
3.每趟排序,根据对应的步长ti,将待排序列分割成若干长度为m的子序列,分别对各子表进行直接插入排序。
仅步长因子为1时,整个序列作为一个表来处理,表长度即为整个序列的长度。
希尔排序时效分析很难,关键码的比较次数与记录移动次数依赖于步长因子序列的选取,特定情况下可以准确估算出关键码的比较次数和记录的移动次数。
目前还没有人给出选取最好的步长因子序列的方法。
步长因子序列可以有各种取法,有取奇数的,也有取质数的,但需要注意:
步长因子中除1外没有公因子,且最后一个步长因子必须为1。
希尔排序方法是一个不稳定的排序方法。
4.冒泡排序
冒泡排序方法:
对n个记录的表,第一趟冒泡得到一个关键码最大的记录r[n],第二趟冒泡对n-1个记录的表,再得到一个关键码最大的记录r[n-1],如此重复,直到n个记录按关键码有序的表。
【效率分析】
空间效率:
仅用了一个辅助单元。
时间效率:
总共要进行n-1趟冒泡,对j个记录的表进行一趟冒泡需要j-1次关键码比较。
移动次数:
最好情况下:
待排序列已有序,不需移动。
5.快速排序
快速排序是通过比较关键码、交换记录,以某个记录为界(该记录称为支点),将待排序列分成两部分。
其中,一部分所有记录的关键码大于等于支点记录的关键码,另一部分所有记录的关键码小于支点记录的关键码。
我们将待排序列按关键码以支点记录分成两部分的过程,称为一次划分。
对各部分不断划分,直到整个序列按关键码有序。
【效率分析】
空间效率:
快速排序是递归的,每层递归调用时的指针和参数均要用栈来存放,递归调用层次数与上述二叉树的深度一致。
因而,存储开销在理想情况下为O(log2n),即树的高度;在最坏情况下,即二叉树是一个单链,为O(n)。
时间效率:
在n个记录的待排序列中,一次划分需要约n次关键码比较,时效为O(n),若设T(n)为对n个记录的待排序列进行快速排序所需时间。
理想情况下:
每次划分,正好将分成两个等长的子序列,则
T(n)≤cn+2T(n/2)c是一个常数
≤cn+2(cn/2+2T(n/4))=2cn+4T(n/4)
≤2cn+4(cn/4+T(n/8))=3cn+8T(n/8)
······
≤cnlog2n+nT
(1)=O(nlog2n)
最坏情况下:
即每次划分,只得到一个子序列,时效为O(n2)。
快速排序是通常被认为在同数量级(O(nlog2n))的排序方法中平均性能最好的。
但若初始序列按关键码有序或基本有序时,快排序反而蜕化为冒泡排序。
为改进之,通常以“三者取中法”来选取支点记录,即将排序区间的两个端点与中点三个记录关键码居中的调整为支点记录。
快速排序是一个不稳定的排序方法。
6.堆排序
堆排序的时间,主要由建立初始]堆和反复重建堆这两部分的时间开销构成,它们均是通过调用Heapify实现的。
堆排序的最坏时间复杂度为O(nlogn)。
堆序的平均性能较接近于最坏性能。
由于建初始堆所需的比较次数较多,所以堆排序不适宜于记录数较少的文件。
堆排序是就地排序,辅助空间为O
(1),它是不稳定的排序方法。
7.归并排序
归并操作的工作原理如下:
申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列,设定两个指针,最初位置分别为两个已经排序序列的起始位置。
比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置。
重复步骤3直到某一指针达到序列尾,将另一序列剩下的所有元素直接复制到合并序列尾。
速度仅次于快速排序,但较稳定。
归并排序的时间复杂度为O(nlogn)。
三、算法设计分析
程序总体采用分块化设计,每种排序的函数都以其相应的名字保存,在主程序调用时用#include“**.cpp”调用。
这样的总体布局将各个功能隔离开来,每个模块负责每个模块的功能,使得程序的布局简单明了。
且子程序只有在被调用时才会运行大大节约系统资源减少了运算时间。
同时由于功能的隔离使得程序的扩展性大大提高,无论程序将要任何改动时,都会方便很多。
四、源代码
以“.cpp”保存,其他子函数分别保存,以下是主函数的代码。
#include
#include
#include
#include"快速排序.cpp"
#include"堆排序.cpp"
#include"直接插入排序.cpp"
#include"选择排序.cpp"
#include"冒泡排序.cpp"
#include"希尔排序.cpp"
#include"归并排序.cpp"
usingnamespacestd;
intmain()
{
clock_tstart,finish;
inti,time1,time2,time3,time4,time5,time6,time7;
inta[30000],b[30000];
srand(time(0));
for(i=0;i<30000;i++)
a[i]=rand();
cout<<"**************************\n";
cout<<"\t选择排序\n";
cout<<"**************************\n";
for(i=0;i<30000;i++)b[i]=a[i];
start=clock();
choices_sort(b);
finish=clock();
time1=finish-start;
printf("选择排序耗时%d毫秒!
\n\n\n",time1);
cout<<"**************************\n";
cout<<"\t直接插入排序\n";
cout<<"**************************\n";
for(i=0;i<30000;i++)b[i]=a[i];
start=clock();
direct(b);
finish=clock();
time2=finish-start;
printf("直接插入排序耗时%d毫秒!
\n\n\n",time2);
cout<<"**************************\n";
cout<<"\t堆排序\n";
cout<<"**************************\n";
for(i=0;i<30000;i++)b[i]=a[i];
start=clock();
heap_sort(b,30000);
finish=clock();
time3=finish-start;
printf("堆排序耗时%d毫秒!
\n\n\n",time3);
cout<<"**************************\n";
cout<<"\t快速排序\n";
cout<<"**************************\n";
for(i=0;i<30000;i++)b[i]=a[i];
start=clock();
sort(b,0,29999);
finish=clock();
time4=finish-start;
printf("快速排序耗时%d毫秒!
\n\n\n",time4);
cout<<"**************************\n";
cout<<"\t冒泡排序\n";
cout<<"**************************\n";
for(i=0;i<30000;i++)b[i]=a[i];
start=clock();
bubble_sort(b);
finish=clock();
time5=finish-start;
printf("冒泡排序耗时%d毫秒!
\n\n\n",time5);
cout<<"**************************\n";
cout<<"\t希尔排序\n";
cout<<"**************************\n";
for(i=0;i<30000;i++)b[i]=a[i];
start=clock();
shellsort(b,30000);
finish=clock();
time6=finish-start;
printf("希尔排序耗时%d毫秒!
\n\n\n",time6);
cout<<"**************************\n";
cout<<"\t归并排序\n";
cout<<"**************************\n";
for(i=0;i<30000;i++)b[i]=a[i];
start=clock();
MergeSort(b,0,29999);
finish=clock();
time7=finish-start;
printf("归并排序耗时%d毫秒!
\n\n\n",time7);
getch();
return0;
}
以下是直接插入排序的函数,以“直接插入排序.cpp”保存
#include
#include
usingnamespacestd;
voiddirect(inta[]){
inti,j,w;
for(i=0;i<30000;i++)
{
for(j=i;j>=0;j--)
{
if(a[j]>=a[j+1])
{
w=a[j];
a[j]=a[j+1];
a[j+1]=w;
}
}
}
}
以下是选择排序的函数,以“选择排序.cpp”保存
#include
#include
usingnamespacestd;
voidchoices_sort(inta[])
{
inti,j,k,t;
for(i=0;i<30000;i++)
{
k=i;
for(j=i+1;j<30000;j++)
if(a[k]>a[j])
k=j;
t=a[i];
a[i]=a[k];
a[k]=t;
}
}
以下是冒泡排序的函数,以“冒泡排序.cpp”保存
#include
#include
usingnamespacestd;
voidbubble_sort(inta[])
{
inti,j,w;
for(i=0;i<30000;i++)
for(j=0;j<30000-i;j++)
if(a[j]>a[j+1])
{
w=a[j];
a[j]=a[j+1];
a[j+1]=w;
}
}
以下是希尔排序的函数,以“希尔排序.cpp”保存
#include
#include
#include
usingnamespacestd;
voidshellsort(inta[],intn)
{
intd,i,j,temp;
for(d=n/2;d>=1;d=d/2)
{
for(i=d;i{
temp=a[i];
for(j=i-d;(j>=0)&&(a[j]>temp);j=j-d)
{
a[j+d]=a[j];
}
a[j+d]=temp;
}
}
}
以下是快速排序的函数,以“快速排序.cpp”保存
#include
#include
usingnamespacestd;
voidsort(inta[],intx,inty)
{
intxx=x,yy=y;
intk=a[x];
if(x>=y)return;
while(xx!
=yy)
{
while(xx=k)yy--;
a[xx]=a[yy];
while(xxa[yy]=a[xx];
}
a[xx]=k;
sort(a,x,xx-1);
sort(a,xx+1,y);
}
以下是堆排序的函数,以“堆排序.cpp”保存
#include
#include
#include
usingnamespacestd;
voidsift(int*x,intn,ints)
{
intt,k,j;
t=*(x+s);/*暂存开始元素*/
k=s;/*开始元素下标*/
j=2*k+1;/*右子树元素下标*/
while(j{
if(j满足就继续下一轮比较,否则调整。
*/
{
j++;
}
if(t<*(x+j))/*调整*/
{
*(x+k)=*(x+j);
k=j;/*调整后,开始元素也随之调整*/
j=2*k+1;
}
else/*没有需要调整了,已经是个堆了,退出循环。
*/
{
break;
}
}
*(x+k)=t;/*开始元素放到它正确位置*/
}
voidheap_sort(int*x,intn)
{
inti,k,t;
for(i=n/2-1;i>=0;i--)
{
sift(x,n,i);/*初始建堆*/
}
for(k=n-1;k>=1;k--)
{
t=*(x+0);/*堆顶放到最后*/
*(x+0)=*(x+k);
*(x+k)=t;
sift(x,k,0);/*剩下的数再建堆*/
}
}
以下是归并排序的函数,以“归并排序.cpp”保存
#include
#include
usingnamespacestd;
voidMerge(int*R,intlow,intm,inthigh)
{
inti=low,j=m+1,p=0;
int*R1;
R1=(int*)malloc((high-low+1)*sizeof(int));
if(!
R1)
return;
while(i<=m&&j<=high)
R1[p++]=(R[i]<=R[j])?
R[i++]:
R[j++];
while(i<=m)
R1[p++]=R[i++];
while(j<=high)
R1[p++]=R[j++];
for(p=0,i=low;i<=high;p++,i++)
R[i]=R1[p];
}
voidMergeSort(intR[],intlow,inthigh)
{
intmid;
if(low{
mid=(low+high)/2;
MergeSort(R,low,mid);
MergeSort(R,mid+1,high);
Merge(R,low,mid,high);
}
}
五、运行结果分析
直接排序排序所用时间
冒泡排序所用时间
选择排序所用时间
快速排序所用时间
堆排序所用时间
归并排序
希尔排序
直接排序
冒泡排序
选择排序
快速排序
堆排序
归并排序
希尔排序
理论时间
O(n×n)
O(n×n)
O(n×n)
O(nLogn)
O(nLogn)
O(nLogn)
O(nLogn)
上机时间
4563豪秒
4884豪秒
1953豪秒
6豪秒
9毫秒秒
16豪秒
12毫秒
通过实验数据,可以看出O(nLogn)的几种排序算法的确比O(n×n)的算法要快很多。
快速排序的确是最快的。
比同级的归并排序和堆排序都要快很多,比其它排序也要快。
而直接排序、冒泡排序最慢,选择排序次之。
因此快速排序在实践中应用十分广泛。
六、小结:
(1)若n较小(n<=50),则可以采用直接插入排序或直接选择排序。
由于直接插入排序所需的记录移动操作较直接选择排序多,因而当记录本身信息量较大时,用直接选择排序较好。
(2)若文件的初始状态已按关键字基本有序,则选用直接插入或冒泡排序为宜。
(3)若n较大,则应采用时间复杂度为O(nlog2n)的排序方法:
快速排序、堆排序或归并排序。
快速排序是目前基于比较的内部排序法中被认为是最好的方法。
七、附录
操作系统win7
运行环境Devc