简单插入排序、合并排序-递归、合并排序-非递归、快速排序 总结

算法设计与分析实验报告——排序

一、实验步骤和要求

1. 算法和代码 的设计与 实现分别设计并实现插入排序、合并排序、快速排序的算法;

2. 测试: 设计 测试数据集, 编写测试程序, 用于测试

a) 正确性: 所实现的 三种 算法的正确性;

b) 算法复杂性: 三种排序算法中,设计测试数据集,评价各个算法在算法
复杂性上的表现;(最好情况、最差情况、平均情况)
c) 效率: 在三种排序算法中,设计测试数据集,评价各个算法中比较的频
率,腾挪的频率。

3. 撰写 评价 报告
a) 结合第二步的测试和实验 结果,在理论 上给予总结和评价三种排序算法
在算法复杂性和效率上的表现。形成电子版实验报告。

二、实验源代码

1、插入排序

void insert_sort(int arr[])
{
    int i,j,temp;
    for(i=2;i<=n;i++)
    {
        temp=arr[i];
        moveNum++; //首先把标杆元素挪动到一个交换空间,挪动一次
        j=i-1;
        while( j>=0 && temp<arr[j] )
        {
            moveNum++;
            cmpNum+=2;
            arr[j+1] = arr[j];
            j--;
        }
        cmpNum++; //最后跳出循环的比较

        arr[j+1]=temp;
        moveNum++;
    }
}

2、归并排序——递归

void Merge(int arr[],int l,int m,int r) //合并操作
{
    int i,j,k;
    i=l,j=m+1,k=0;

    while(i<=m&&j<=r) //归并的过程
    {
        cmpNum+=2; //进来就完成一次比较

        if(arr[i]<=arr[j])
            A[k++]=arr[i++];
        else
            A[k++]=arr[j++];
        cmpNum++;
    }
    cmpNum++;

    if(i<=m)
    {
        while(i<=m)
        {
            A[k++]=arr[i++];
            cmpNum++;
        }

    }
    else{
        while(j<=r)
        {
            A[k++]=arr[j++];
            cmpNum++;
        }
    }
    cmpNum++;

    moveNum += r-l+1; //挪腾次数就是需要合并的大小

    for(i=l,k=0;i<=r;i++,k++)
        arr[i]=A[k];
    cmpNum += r-l+1;
    moveNum += r-l+1; //挪腾次数就是需要合并的大小
}
void mergeSort(int arr[],int l,int r) //递归合并排序
{
    if(l<r)
    {
        int m=(l+r)/2;
        mergeSort(arr,l,m);
        mergeSort(arr,m+1,r);
        Merge(arr,l,m,r);
    }
    cmpNum++;
}

3、归并排序——非递归

void Merge(int c[],int d[],int l,int m,int r)
{
    int i=l,j=m+1,k=l;
    while( i<=m && j<=r )
    {
        cmpNum += 2;
        if(c[i]<=c[j])
            d[k++]=c[i++];
        else
            d[k++]=c[j++];
        cmpNum++;
    }
    cmpNum++;

    if(i>m){
        for(int q=j;q<=r;q++)
            d[k++]=c[q];
    }
    else{
        for(int q=i;q<=m;q++)
            d[k++]=c[q];
    }
    cmpNum++;

    moveNum += r-l+1;
}
void MergePass(int x[],int y[],int s,int r) //合并大小为s的相邻子数组
{
    int i=1;
    while(i+2*s-1<=r)
    {
        cmpNum++;
        Merge(x,y,i,i+s-1,i+2*s-1);
        i=i+2*s;
    }
    cmpNum++;
    if(i+s-1<r) //剩余的超过一半
        Merge(x,y,i,i+s-1,r);
    else{
        for(int j=i;j<=r;j++)
             y[j]=x[j];
        cmpNum += r-i;
        moveNum += r-i;
    }
    cmpNum++;
}
void mergeSort_n(int arr[],int r) //非递归的归并排序
{
    int s=1;
    while(s<r){
        cmpNum++;
        MergePass(arr,b,s,r);
        s += s;
        MergePass(b,arr,s,r);
        s += s;
    }
    cmpNum++;
}

4、快速排序

int Partition(int arr[],int left,int right)
{
    int pivot = arr[left];
    moveNum++;

    while(left<right) //只要left与right不相遇
    {
        cmpNum++;
        while(left<right&&arr[right]>pivot)
        {
             cmpNum+=2; //每次进行了两个比较
             right--;
        }
        arr[left]=arr[right];
        moveNum++;

        while(left<right&&arr[left]<=pivot)
        {
             cmpNum+=2;
             left++;
        }
        arr[right]=arr[left];
        moveNum++;
    }
    cmpNum++;

    arr[left]=pivot;
    moveNum++;
    return left;
}
void quickSort(int arr[],int l,int r)
{
    if(l<r)
    {
        int i = Partition(arr,l,r);
        quickSort(arr,l,i-1);
        quickSort(arr,i+1,r);
    }
    cmpNum++;
}

三、算法正确性确定

使用函数 isCorrect(),判断排序后的数组是否是非降序来确定算法的正确性。

void isCorrect()
{
    int i=0;
    for(i=2;i<=n;i++)
        if(medium[i]<medium[i-1])
        {
            printf("error\n");
            return;
        }
    printf("correct\n");
}

经验证,算法正确性可以保证。

以上是简单的方法,我觉得不是很够严谨,有可能在排序的过程中存在一些丢失,因此有以下第二种方法。

使用c++提供的库函数sort,对初始的数列进行排序,获得一个标准的模板数组,然后每次将调用的结果和这个数组对比,可以确保算法正确性,代码如下,时间复杂度上仅比上式多了复制函数sort函数的时间,isCorrect()函数内的时间是一样的。

#include<algoritm>
int corr_arr[maxn];

sort(corr_arr+1,corr_arr+n+1); //使用库函数对arr进行排序,然后得到标准的模板

void isCorrect()
{
    int i=1;
    for(i=1;i<=n;i++)
        if(medium[i]!=corr_arr[i])
        {
            printf("error\n");
            return;
        }
    printf("correct\n");
}

四、测试数据集构造

归并排序三种情况合一,因为使用的是一个辅助空间,比较次数和移动次数都不会改变。

平均情况下不需要构建数据集,直接使用srand()函数,随机生成待排数组就可以。

最优情况下,快速排序,每次找到中枢结点,左右部分都是平均分配,可以使用一个算法,实现数据集构造,算法如下:

void fastSet(int arr[],int left,int right,int low,int high)
// left,right指插入到数组中的位置,low和high指插入数的区间,最小值和最大值
{
    if(left<=right)
    {
        int mid=(left+right)/2; //这个位置放置arr[left]
        int mid_num=(low+high)/2;
        arr[left]=mid_num;
        fastSet(arr,left+1,mid,low,mid_num-1);
        fastSet(arr,mid+1,right,mid_num+1,high);
    }
}

最优情况下,插入排序的数据集就是有序的,也不需要特别构造。

最坏情况下,快速排序集是有序的。

最坏情况下,插入排序的数据集是倒序的。

因此数据集,除了快速排序的最优集需要使用算法构造外,均不需要特别构造。

五、算法复杂性

计算程序运行时间函数

    double run_time;
	_LARGE_INTEGER time_start;	//开始时间
	_LARGE_INTEGER time_over;	//结束时间
	double dqFreq;		//计时器频率
	LARGE_INTEGER f;	//计时器频率

	QueryPerformanceFrequency(&f);
	dqFreq=(double)f.QuadPart;

	QueryPerformanceCounter(&time_start);	//计时开始

    // do something

    QueryPerformanceCounter(&time_over);	//计时结束

    isCorrect();
    run_time=1000000*(time_over.QuadPart-time_start.QuadPart)/dqFreq;

平均效率

计算100次求均值

计算规模\算法策略(us) 简单插入排序 递归-归并排序 非递归-归并排序 快速排序
10 0.164 0.369 0.311 0.160
100 11.820 6.771 4.972 8.465
1000 843.611 134.320 94.833 73.213
5000 21766.059 793.927 621.811 613.983
10000 - 1856.142 1316.877 1324.300
100000 - 16315.375 21803.295 20831.238
- 45007.401 34868.027 34002.188

最优效率

计算规模\算法策略(us) 简单插入排序 快速排序
10 0.008 0.130
100 0.103 2.541
1000 5.251 70.514
5000 102.245 489.251
10000 15025.15 902.152
100000 - 15015.255

最低效率

计算规模\算法策略(us) 简单插入排序 快速排序
10 1.250 1.632
100 12.256 15.241
1000 902.155 1020.241
5000 1952.451 2041.514

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ehswrSEi-1572329458711)(C:\Users\XiaoLin\AppData\Roaming\Typora\typora-user-images\image-20191028131306945.png)]

六、效率(比较频数,腾挪频数)

平均效率

计算规模\算法策略 简单插入排序 递归-归并排序 非递归-归并排序 快速排序
10 34、41 68、143 37、113 28、67
100 2482、4667 1344、2453 794、2120 450 1620
1000 256566、510135 19952、34037 9995、29228 6194、25740
5000 6349483、12683969 123616 207621 69990 192244 36084 163225
10000 - 267232 547965 139990 404523 76522 354090
100000 - 3337856 5445661 1799987 5106689 939116 4633551
- 7075712 14420653 3599987 10613257 2014658 10111398

最优效率

计算规模\算法策略 简单插入排序 快速排序
10 15、21 15、43
100 1025、2515 205、1511
1000 1506211、321551 4152、18521
5000 5415262、10235521 28654、105256
10000 - 52652、285256

最低效率

计算规模\算法策略 简单插入排序 快速排序
10 60、105 48、95
100 4152、7511 3510、6520
1000 415202、658014 315452、523651
5000 8514256、15235214 3056585、10556254

七、算法评价

插入排序的时间复杂度最大,比其他三个算法多了许多,其他三个理论实践复杂度都是O(NlogN),具体运行环境中,数量级基本一致,但是仍然有差别,在数据量较小的情况下差别不大,我在网上搜索得知当数据足够大的情况下,快速排序的效率是归并排序的两倍。值得注意的是,当数据量很大的时候,调用递归归并排序,会栈溢出。归并排序需要使用大小为n的物理空间,需要比较和移动的次数都明显多于快速排序。快速排序,速度最快,但是缺点是不是稳定的排序,并且在最坏情况下,退化为O(NlogN)。

在使用辅助空间的时候,需要在堆区声明一个空间,如果在子函数内,即栈区声明超过100,000以上的数组,就会出现爆栈的风险,影响程序的速度和正常运行。

在总体上不要求排序稳定性的条件下,可以优先考虑快速排序。当然即使考虑稳定性,也可使用快速排序,增加第二键值即可。

发布了174 篇原创文章 · 获赞 18 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/qq_41173604/article/details/102798507