计算机算法基础实验一:分治与排序
实验指导书要求如下:
实验项目:排序方法的实验比较
排序方法是数据处理的最基本和最重要的操作。其目的是将一组“无序” 的记录序列调整为“有序”的记录序列。
实验题目:归并排序与快速排序算法的实现与实验比较实验内容:
实现归并排序与快速排序算法,通过实验数据的设计,考察不同规模、不同分布(不同初始状态)的数据对排序算法运行时间影响的规律,验证理论分析结果的正确性。实验要求:
1.实现归并排序的递归和非递归算法。
2.实现快速排序算法。划分方法要求:首元素划、三数取中划分法、随机划分法、“不同取大”划分法。实验方法 :
(1)生成不同规模的数据,分别对正序、反序和随机的初始序列进行排序, 考察算法对初始序列的敏感程度。
(2)采用不同的划分基准的选取方法,分别对对正序、反序和随机的初始序列进行排序,考察划分基准的选取对算法时间性能的影响。实验结果及分析: (1)所有实验结果汇集成下面的“实验结果汇总表”。 (2)给出实验结果、原因分析、结论等(算法的实际时间增长速度如何?
复杂性相当的算法之间快慢如何?基准的不同选取方法对不同初始状态数据进行快速排序有何影响,原因是什么?…等等)。
设计的基本思想和算法的原理
1.设计的基本思想
设置实验者选择数据规模、排序方式和选择不同划分基准的方式进行程序设计,设置计时器,运行程序得到实验结果填到表格中。
2.算法的基本原理
(1)归并排序
①归并:将两个或两个以上的有序序列合并成一个有序序列的过程
void Merge (int s , int m , int t , LIST A , LIST B)
/将有序序列A[s],…,A[m]和A[m+1],…,A[t]合并为一个有序序列 B[s],…,B[t]/
{
int i = s ;
int j = m+1 , k = s ;//置初值
/* 两个序列非空时,取小者输出到B[k]上 */
while ( i <= m && j<= t )
B[k++] = ( A[ i ].key <= A[ j ].key) ? A[i++] : A[j++] ; /*若第一个子序列非空(未处理完),则复制剩余部分到B */
while ( i <= m ) B[k++] = A[i++] ; /*若第二个子序列非空(未处理完),则复制剩余部分到B */
while ( j <= t ) B[k++] = A[j++] ;
}
②二路归并排序的基本思想(自底向上的非递归算法)
●将具有n个待排序记录的序列视为n个长度为1的有序序列;
●然后进行两两归并,得到n/2 个长度为2的有序序列;
●再进行两两归并,得到n/4个长度为4的有序序列;
●……,
●直至得到1个长度为n的有序序列为止。
③(二路)归并排序分治算法
●分解:将当前待排序的序列A[low], …, A[high]一分为二,即求分裂点 mid = ( low + high ) / 2 ;
●求解:递归地对序列 A[low], …, A[mid]和A[mid+1], …, A[high]进行归并排序;
●组合:将两个已排序子序列归并为一个有序
(2)快速排序
①算法的基本思想
通过一趟排序将待排序的记录分割成独立的两部分,其中一部分的所有记录关键字都比另外一部分的所有记录关键字都小(划分交换),然后再按此方法对这两部分分别进行快速排序,整个排序过程可以递归地进行,以达到整个记录序列变成有序序列。
②算法的实现步骤
●基准元素选取:选择其中的一个记录的关键字v 作为基准元素(控制关键字);(怎么选择?)
●划分:通过基准元素v
把无序区A[i],……,A[j]划分成左、右两部分,使得左边的各记录的关键字都小于v ;右边的各记录的关键字都大于等于v ;(如何划分?)
●递归求解:重复(1)~(2),分别对左边和右边部分递归地进行快速排序;
●组合:左、右两部分均有序,整个序列有序。
③基准元素的选取方法
基准元素的选取大致有四种方法:首元素划、三数取中划分法、随机划分法、“不同取大”划分法。
●首元素划分法:直接取待排序序列的第一个元素。
●三数取中法:取待排序序列的第一个元素,中间的一个元素,最后一个元素,取三个数的中位数作为基准元素。
●不同取大法:从一个元素开始向后遍历,直到遇到比前一个元素数值大的元素,将其作为基准元素。
实验结果
实验结果数据参数表
实验结果分析
(1)归并排序算法实际时间增长速度分析
由理论可知归并排序的时间复杂度和待排序序列的的状态关系不大(暂时忽略比较次数的不同),和递归和非递归实现也无关系,从实验数据中也可以看出。因此以非递归排序随机序列排序为例,分析算法实际时间与数据规模之间的关系。
由上图可以看出(由于数据量太少故不是很明显,可以结合理论得出结论),实际增长速度接近,当数据量较小时,函数增长速度较快,实际增长速度接近n方。当数据量较大时,函数增长速度较慢,实际增长速度接近n。
(2)归并排序中随机序列排序和正反序序列排序的时间复杂性比较
从实验数据中可以明显看出随机序列排序的实际排序时间明显大于正反序序列,有归并的原理可知,正反序序列归并时的比较次数是n/2,随机序列归并时的比较次数接近n。故随机序列排序时消耗的时间要多一些。
(3)快速排序算法的实际时间增长速度分析
①随机排序中,基准元素的选取方法对实际排序时间的影响并不明显,以“首元素法”为例,进行实际时间增长速度分析。如下图所示,随机序列的快速排序时间复杂度接近
②正反序序列快速排序中,有算法原理可知,如果采用“首元素法”或“不同取大”法来选取基准元素的话,递归树的深度为n,时间复杂度为。本实验采用递归方法实现快速排序,会直接导致栈溢出。以正序序列快速排序为例,分析一下实际时间增长速度,理论上应该是n*log(n)。如下图所示。
(4)快速排序与归并排序的时间复杂度相同,随着数据量增大,快速排序的实际用时明显少于归并排序,个人认为,随着数据量的增大,归并排序中归并的过程对实际排序时间的影响越来越明显,导致排序时间较大。
(5)基准元素的选取方法对实际排序时间的影响
这个问题在前面的分析中提到过,当随机序列进行快速排序时,基准元素的选取对实际排序时间的影响并不明显,当正反序序列进行快速排序时基准元素的排序方法对实际排序时间有着致命的影响,会直接导致最坏情况-------递归树深度为n,如“首元素法”和“取大法”。这个由实验数据可以明显看出。
源程序
归并排序非递归:
#include <iostream>
#include <stdlib.h>
#include <time.h>
using namespace std;
int maxn;
void Merge(int s,int m,int t,int A[],int B[])
{
int i=s,j=m+1,k=s;
while(i<=m&&j<=t)
{
if(A[i]<=A[j])
B[k++]=A[i++];
else
B[k++]=A[j++];
}
while(i<=m)
B[k++]=A[i++];
while(j<=t)
B[k++]=A[j++];
for(int i=s;i<=t;i++)
A[i]=B[i];
}
void MergePass(int n,int h,int A[],int B[])
{
int i;
for(i=0;i+2*h-1<n;i=i+2*h)
Merge(i,i+h-1,i+2*h-1,A,B);
if((i+h)<n)
Merge(i,i+h-1,n-1,A,B);
else
for(int j=i;j<n;j++)
B[j]=A[j];
}
void MergeSort(int n,int A[],int B[])
{
int h=1;
while(h<n)
{
MergePass(n,h,A,B);
h=2*h;
}
}
int main()
{
//生成待排序数组C
int sequence;
int *A,*B;
clock_t start,endn;
printf("请选择数据规模:\n");B = (int *)malloc(sizeof(int)*maxn);
scanf("%d",&maxn);
A = (int *)malloc(sizeof(int)*maxn);
B = (int *)malloc(sizeof(int)*maxn);
printf("请选择数据序列类型?1.有序2.无序3.倒序\n");
scanf("%d",&sequence);
printf("生成数据...\n");
if(sequence==1)
for(int i=0;i<maxn;i++)
A[i]=i+1;
else if(sequence==2)
for(int i=0;i<maxn;i++)
A[i]=rand()%100;
else
for(int i=maxn;i>=0;i--)
A[i]=maxn-i;
for(int i=0;i<maxn;i++)
B[i]=A[i];
printf("生成完毕\n");
/*for(int i=0;i<maxn;i++)
printf("%d ",A[i]);
printf("\n");*/
//进行二路归并排序
printf("开始排序并计时...\n");
start=clock();
MergeSort(maxn,A,B);
endn=clock();
printf("排序完成,计时结束\n");
//输出结果
printf("执行时间为:");
printf("%.3fs\n",(double)(endn-start)/CLK_TCK);
/*for(int i=0;i<maxn;i++)
printf("%d ",B[i]);*/
free(A),free(B);
return 0;
}
归并排序递归:
#include <iostream>
#include <stdlib.h>
#include <time.h>
using namespace std;
void Merge(int s,int m,int t,int A[],int B[])
{
int i=s,j=m+1,k=s;
while(i<=m&&j<=t)
{
if(A[i]<=A[j])
B[k++]=A[i++];
else
B[k++]=A[j++];
}
while(i<=m)
B[k++]=A[i++];
while(j<=t)
B[k++]=A[j++];
for(int i=s;i<=t;i++)
A[i]=B[i];
}
void MergeSort(int A[],int B[],int low,int high)
{
int mid = (low + high)/2;
if(low<high)
{
MergeSort(A,B,low,mid);
MergeSort(A,B,mid+1,high);
Merge(low,mid,high,A,B);
}
}
int main()
{
//生成待排序数组C
int sequence,maxn;
int *A,*B;
clock_t start,endn;
printf("请选择数据规模:\n");
scanf("%d",&maxn);
A = (int *)malloc(sizeof(int)*maxn);
B = (int *)malloc(sizeof(int)*maxn);
printf("请选择数据序列类型?1.有序2.无序3.倒序\n");
scanf("%d",&sequence);
printf("生成数据...\n");
if(sequence==1)
for(int i=0;i<maxn;i++)
A[i]=i+1;
else if(sequence==2)
for(int i=0;i<maxn;i++)
A[i]=rand()%100;
else
for(int i=maxn;i>=0;i--)
A[i]=maxn-i;
printf("生成完毕\n");
/*for(int i=0;i<maxn;i++)
printf("%d ",A[i]);
printf("\n");*/
//进行二路归并排序
printf("开始排序并计时...\n");
start=clock();
MergeSort(A,B,0,maxn-1);
endn=clock();
printf("排序完成,计时结束\n");
//输出结果
printf("执行时间为:");
printf("%.3f\n",(double)(endn-start)/CLK_TCK);
/*for(int i=0;i<maxn;i++)
printf("%d ",B[i]);*/
free(A),free(B);
return 0;
}
快速排序递归:
#include <iostream>
#include <stdlib.h>
#include <time.h>
using namespace std;
int maxn;
//首元素划分法
int FindPivort_1(int i,int j,int A[])
{
int key=A[i];
for(int k=i+1;k<=j;k++)
{
if(A[k]!=key)
return i;
}
return -1;
}
//三数取中划分法
int FindPivort_2(int i,int j,int A[])
{
int key=A[i];
for(int k=i+1;k<=j;k++)
{
if(A[k]!=key)
{
int mid =(i+j)/2;
int middle=A[i]>=A[mid]?(A[mid]>=A[j]?A[mid]:(A[i]>=A[j]?A[j]:A[i])):(A[i]>=A[j]?A[i]:(A[mid]>=A[j]?A[j]:A[mid]));
//printf("%d\n",middle);
if(middle==A[i])
return i;
else if(middle==A[mid])
return mid;
else
return j;
}
}
return -1;
}
//随机划分法
int FindPivort_3(int i,int j,int A[])
{
int key=A[i];
for(int k=i+1;k<=j;k++)
{
if(A[k]!=key)
return (rand()%(j-i)+i);
}
return -1;
}
//“不同取大”划分法
int FindPivort_4(int i,int j,int A[])
{
int key=A[i];
int k;
for(k=i+1;i<=j;i++)
if(A[k]>key)
return k;
else if(A[k]<key)
return i;
return -1; //数组元素全部相等
}
int Partition(int i,int j,int pivort,int A[])
{
int l=i,r=j;
while(1){
while(A[r]>=pivort&&r>l)
r--;
if(l<r)
{
A[l]=A[r];
}
/*for(int i=0;i<maxn;i++)
printf("%d ",A[i]);
printf("\n");
printf("%d %d\n",l,r);*/
while(A[l]<pivort&&r>l)
l++;
if(l<r)
{
A[r]=A[l];
}
/*for(int i=0;i<maxn;i++)
printf("%d ",A[i]);
printf("\n");
printf("%d %d\n",l,r);*/
if(r<=l)
break;
}
A[l]=pivort;
return l;
}
void QuickSort_1(int i, int j,int A[])
{
int pivort;
int k;
int pivortindex;
pivortindex=FindPivort_1(i,j,A);
//printf("1\n");
//printf("%d\n",pivortindex);
if(pivortindex!=-1&&i<j)
{
if(pivortindex!=i)
swap(A[i],A[pivortindex]);
//printf("2\n");
pivort = A[i];
k=Partition(i,j,pivort,A);
//printf("3\n");
/*for(int i=0;i<maxn;i++)
printf("%d ",A[i]);
printf("\n");*/
QuickSort_1(i,k-1,A);
QuickSort_1(k+1,j,A);
}
}
void QuickSort_2(int i, int j,int A[])
{
int pivort;
int k;
int pivortindex;
pivortindex=FindPivort_2(i,j,A);
if(pivortindex!=-1&&i<j)
{
if(pivortindex!=i)
swap(A[i],A[pivortindex]);
pivort = A[i];
k=Partition(i,j,pivort,A);
QuickSort_2(i,k-1,A);
QuickSort_2(k+1,j,A);
}
}
void QuickSort_3(int i, int j,int A[])
{
int pivort;
int k;
int pivortindex;
pivortindex=FindPivort_3(i,j,A);
if(pivortindex!=-1&&i<j)
{
if(pivortindex!=i)
swap(A[i],A[pivortindex]);
pivort = A[i];
k=Partition(i,j,pivort,A);
QuickSort_3(i,k-1,A);
QuickSort_3(k+1,j,A);
}
}
void QuickSort_4(int i, int j,int A[])
{
int pivort;
int k;
int pivortindex;
pivortindex=FindPivort_4(i,j,A);
if(pivortindex!=-1&&i<j)
{
if(pivortindex!=i)
swap(A[i],A[pivortindex]);
pivort = A[i];
k=Partition(i,j,pivort,A);
QuickSort_4(i,k-1,A);
QuickSort_4(k+1,j,A);
}
}
int main()
{
//生成待排序数组C
int sequence;
int *A;
clock_t start,endn;
printf("请选择数据规模:\n");
scanf("%d",&maxn);
A = (int *)malloc(sizeof(int)*maxn);
printf("请选择数据序列类型?1.有序2.无序3.倒序\n");
scanf("%d",&sequence);
printf("第一次生成数据...\n");
if(sequence==1)
for(int i=0;i<maxn;i++)
A[i]=i+1;
else if(sequence==2)
for(int i=0;i<maxn;i++)
A[i]=rand()%100;
else
for(int i=maxn;i>=0;i--)
A[i]=maxn-i;
printf("第一次生成完毕\n");
/* for(int i=0;i<maxn;i++)
printf("%d ",A[i]);
printf("\n");*/
//进行快速排序
printf("开始首元素划分排序并计时...\n");
start=clock();
QuickSort_1(0,maxn-1,A);
endn=clock();
printf("首元素划分排序完成,计时结束\n");
//输出结果
printf("首元素划分执行时间为:");
printf("%.3fs\n",(double)(endn-start)/CLK_TCK);
/*for(int i=0;i<maxn;i++)
printf("%d ",A[i]);
printf("\n");*/
//------------------------------------
printf("第二次生成数据...\n");
if(sequence==1)
for(int i=0;i<maxn;i++)
A[i]=i+1;
else if(sequence==2)
for(int i=0;i<maxn;i++)
A[i]=rand()%100;
else
for(int i=maxn;i>=0;i--)
A[i]=maxn-i;
printf("第二次生成完毕\n");
/*for(int i=0;i<maxn;i++)
printf("%d ",A[i]);
printf("\n");*/
printf("开始三数取中划分排序并计时...\n");
start=clock();
QuickSort_2(0,maxn-1,A);
endn=clock();
printf("三数取中划分排序完成,计时结束\n");
//输出结果
printf("三数取中划分执行时间为:");
printf("%.3fs\n",(double)(endn-start)/CLK_TCK);
/*for(int i=0;i<maxn;i++)
printf("%d ",A[i]);
printf("\n");*/
//-------------------------------------------
printf("第三次生成数据...\n");
if(sequence==1)
for(int i=0;i<maxn;i++)
A[i]=i+1;
else if(sequence==2)
for(int i=0;i<maxn;i++)
A[i]=rand()%100;
else
for(int i=maxn;i>=0;i--)
A[i]=maxn-i;
printf("第三次生成完毕\n");
/*for(int i=0;i<maxn;i++)
printf("%d ",A[i]);
printf("\n");*/
printf("随机划分排序并计时...\n");
start=clock();
QuickSort_3(0,maxn-1,A);
endn=clock();
printf("随机划分排序完成,计时结束\n");
//输出结果
printf("随机划分执行时间为:");
printf("%.3fs\n",(double)(endn-start)/CLK_TCK);
/*for(int i=0;i<maxn;i++)
printf("%d ",A[i]);
printf("\n");*/
//-------------------------------------------
printf("第四次生成数据...\n");
if(sequence==1)
for(int i=0;i<maxn;i++)
A[i]=i+1;
else if(sequence==2)
for(int i=0;i<maxn;i++)
A[i]=rand()%100;
else
for(int i=maxn;i>=0;i--)
A[i]=maxn-i;
printf("第四次生成完毕\n");
/*for(int i=0;i<maxn;i++)
printf("%d ",A[i]);
printf("\n");*/
printf("开始“不同取大”划分排序并计时...\n");
start=clock();
QuickSort_4(0,maxn-1,A);
endn=clock();
printf("“不同取大”划分排序完成,计时结束\n");
//输出结果
printf("“不同取大”划分执行时间为:");
printf("%.3fs\n",(double)(endn-start)/CLK_TCK);
/*for(int i=0;i<maxn;i++)
printf("%d ",A[i]);
printf("\n");*/
free(A);
return 0;
}