数据结构实验:内部排序算法的性能分析


前言

记录下本学期的数据结构实验
本实验主要集中于比较几种内部排序算法


提示:以下是本篇文章正文内容,下面案例可供参考

一、问题描述

设计一个程序,对各种内部排序算法的关键字比较次数和移动次数进行比较。具体要求如下:
⑴ 编程实现内部排序算法:编程实现直接插入排序,希尔排序,冒泡排序,快速排序,简单选择排序,堆排序,归并排序算法。
⑵ 要求数据规模:待排序数据类型不限(整型或浮点型),读取自磁盘文件。需用多组、多规模数据进行测试并记录实验结果。例如,数据规模为:{50000,100000,250000,500000,750000};对每组数据规模,建议至少生成5组不同的数据进行测试,以5组数据的性能平均值作为测试结果。
⑶ 评价排序的指标有:在表长相同的情况下,各种排序算法的关键字比较次数、关键字移动次数(关键字交换记为3次移动)、排序时间、排序算法的稳定性;当改变表长时,各种排序算法的性能变化情况,
⑷ 结果记录及分析:在报告中以图和表的形式记录测试结果。例如(以下表格形式仅供参考)。对不同排序算法的性能差异进行分析;提出针对不同的数据集,应该如何选择内部排序算法的建议。
⑸ 建议测试你的系统所能实现内部排序算法的数据规模上限,并针对这一问题,在报告中提出如何解决超大规模数据排序问题的思路。

二、问题分析

1.数据存储结构
在这里插入图片描述

图1 结构体定义

如图1,定义结构体NODE,结构体内含有主关键字key,用于存储数据。

2.排序中可能面临的难点问题
(1)快速排序、归并排序的程序编写。归并排序使用的数组除了中间要合并用的是静态数组,其他都是动态数组创建的,堆创建数组本来是挺好的,可是这里却因为数据规模较大的时候,递归太深,只能用数组;快速排序对堆栈要求高,需要的堆栈太大,需要调整堆栈。
(2)交换次数的计数。题目中要求:关键字交换记为3次移动。由于没有清晰这个概念,导致后面的数据均有问题,重新编写代码并填写数据。其中,归并排序并没有交换元素,一开始我是置零处理的,但是后来发现了上述的乌龙,个人认为这里的交换次数其实是指移动次数,因此再次更改了代码和数据。

3.如何观察稳定性
本次实验的数据规模均较大,拟采用另外数据规模为20的数据组,通过debug的方式,进行分析各排序方法的稳定性。

4.数据规模的选择
在正式进行本实验之前,预先测试过不同数据规模下,各排序方法的性能情况。实验中发现,数据规模的选择不宜过小,否则排序时间太短,不利于分析比较各排序方法之间的差别;同时,数据规模的选择也不宜过大,否则排序时间太长,实验的时间会过于太长。

三、实验结果及分析

(1)实验数据描述

①数据规模:本实验的数据规模为1w、2.5w、5w、10w,为四组数据。
②磁盘文件存储格式:.txt格式。文件存储内容如图2所示。
在这里插入图片描述

图2 磁盘文件DataSet_50000.txt

③数据生成方法:
本实验采用随机数生成法生成数据集。首先利用rand()函数生成指定数目的随机数存入data数组,再将数组中的元素存储到DataSet_50000.txt文件中,再通过磁盘读入函数load()从文件中读入数据并存入data数组,最后进行实验测试。具体数据见图2所示。具体实现函数如图3、4所示。
在这里插入图片描述

图3 创建函数

在这里插入图片描述

图4 磁盘读入函数

(2)实验结果

实验结果如图5、6所示。
在这里插入图片描述

图5 生成的2.5w随机数存入磁盘文件

在这里插入图片描述

图6排序后的结果

本次实验测试了数据规模分别为1w、2.5w、5w、10w的排序情况,为了数据的稳定,每种数据规模各分为5组进行测试取平均值。
需要注意的是,归并排序原理上是将两个无序的数组合并成一个有序的数组的递归函数,并没有进行数据交换,这里的交换次数仅表示数据移动的概念。

表1 数据规模为10000时,五组数据组及其平均性能

在这里插入图片描述

表2 数据规模为25000时,五组数据组及其平均性能

在这里插入图片描述

表3 数据规模为50000时,五组数据组及其平均性能

在这里插入图片描述

表4 数据规模为100000时,五组数据组及其平均性能

在这里插入图片描述

(3)性能分析

①表格分析:
(1)由上面4张表格可以看出,随着数据规模增大,各种排序方法耗时均相应增加,交换次数和比较次数也逐渐增加。
(2)由表格数据可以看出,较快的排序算法有快速排序、堆排序、归并排序、希尔排序;较慢的排序有冒泡排序、直接插入排序、选择排序。
(3)其中我对直接插入算法中,带监视哨和不带监视哨的情况进行了测试,发现带监视哨比不带监视哨的排序时间普遍较短,比较次数也相对较少。
(4)相同数据规模下,选择排序法的比较次数相同,归并排序算法的交换次数相同。
②实验数据统计图表分析
A.比较不同排序方法的比较次数和交换次数之间的关系
在这里插入图片描述

图7 数据规模为10000下,比较不同排序方法的比较次数和交换次数之间的关系

ⅰ.由上图图7可知,不同排序方法,比较次数和交换次数有较大的差别。
ⅱ.其中,选择排序、直接选择排序、冒泡排序、不带监视哨的直接插入排序这四种排序方法,他们的比较次数均大于交换次数。而带监视哨的直接插入排序的比较次数和交换次数相差较小,可以忽略不计。
ⅲ.但是,快速排序、堆排序、归并排序和希尔排序此处仅能看出与其他排序方法的比较次数和交换次数的差别,即远小于其他排序方法的比较次数和交换次数,不能够比较内部的比较次数和交换次数之间的关系。因此,在去掉选择排序、直接选择排序、冒泡排序、不带监视哨的直接插入排序、带监视哨的直接插入排序这五种排序方法的干扰后,重新比较了快速排序、堆排序、归并排序和希尔排序这四种排序方法的比较次数和交换次数之间的关系。具体如下图图8所示。

在这里插入图片描述

图8 数据规模为10000下,快速排序、堆排序、归并排序和希尔排序的比较次数和交换次数之间的关系

ⅳ.由图8可以看出,快速排序、归并排序和希尔排序,他们的比较次数均大于交换次数,而堆排序的比较次数和归并次数相差较小。
ⅴ.由以上分析可以得知,不同排序方法在相同数据测试的情况下,得到的数据相差较大,在使用图表进行分析时,会导致部分数据被较大的掩盖了其本身的重要关系,进而影响分析的准确性和合理性。因此,根据分析之前得到的图表数据,我决定将这九组分为复杂排序和简单排序两类,即为
复杂排序类:选择排序、直接选择排序、冒泡排序、
不带监视哨的直接插入排序、带监视哨的直接插入排序
简单排序类:快速排序、堆排序、归并排序、希尔排序

B.比较不同数据规模下,不同排序方法之间的比较次数关系
在这里插入图片描述

图9 在数据规模为1w、2.5w、5w、10w下,简单排序类的比较次数之间的关系

在这里插入图片描述

图10 在数据规模为1w、2.5w、5w、10w下,复杂排序类的比较次数之间的关系

在这里插入图片描述

图11 在数据规模为1w、2.5w、5w、10w下,简单排序类的交换次数之间的关系

在这里插入图片描述

图12 在数据规模为1w、2.5w、5w、10w下,复杂排序类的交换次数之间的关系

在这里插入图片描述

图13 在数据规模为1w、2.5w、5w、10w下,简单排序类的排序时间之间的关系

在这里插入图片描述

图14 在数据规模为1w、2.5w、5w、10w下,复杂排序类的排序时间之间的关系 以上数据都是分别在1w、2.5w、5w、10w的数据规模下,测试了5组不同的数据,从而运算得到的平均值。 ⅰ.从上述图表中可以看出,从比较次数、交换次数、排序时间上进行分析,简单排序的效率要远远高于复杂算法。 ⅱ.同时,随着数据规模的增大,所有的排序方法的比较次数、交换次数和排序时间都随之增大。 ⅲ.复杂排序中,冒泡排序的各项性能均差于其他的排序算法,且随着数据规模的增大,冒泡排序的比较次数、交换次数和排序时间几乎呈指数型增长,增长幅度相对最大。简单排序中,快速排序的各项性能均优于其他的排序算法,增长幅度相对最小。 ⅳ.根据以上图表信息,当数据规模相同时,分析各排序算法的效率如下: 在数据基本无序、数据较少的情况下,快速排序>希尔排序>归并排序>堆排序>直接插入排序>直接选择排序>冒泡排序>选择排序; 在数据基本无序、数据较多的情况下,快速排序>堆排序>归并排序>希尔排序>直接插入排序>直接选择排序>冒泡排序>选择排序。 ③稳定性分析 (1)快速排序 快速排序有两个方向,左边的i下标一直往右走,当a[i] <= a[center_index],其中center_index是中枢元素的数组下标,一般取为数组第0个元素。而右边的j下标一直往左走,当a[j] > a[center_index]。如果i和j都走不动了,且i <= j,交换a[i]和a[j],重复上面的过程,直到i > j。 交换a[j]和a[center_index],完成一趟快速排序。在中枢元素和a[j]交换的时候,很有可能把前面的元素的稳定性打乱,所以快速排序是一个不稳定的排序算法,不稳定发生在中枢元素和a[j] 交换的时刻。 (2)选择排序 选择排序是给每个位置选择当前元素最小的。那么,在一趟选择,如果当前元素比一个元素小,而该小的元素又出现在一个和当前元素相等的元素后面,那么交换后稳定性就被破坏了,所以选择排序不是一个稳定的排序算法。 (3)直接选择排序 有一个序列:8* 3 5 4 8 3 1 9 用直接选择排序对它进行操作时,第一次交换就会把8*与1交换,这样序列变为了:1 3 5 4 8 3 8* 9, 8*就跑到8后面去了,而且最终排序完的序列为1 3 3 4 5 8 8* 9,可以看出8* 最终是在8后面,不满足定义里的“多个具有相同的关键字的记录相对次序保持不变”。所以直接选择排序是不稳定的。 (4)冒泡排序 冒泡排序就是把小的元素往前调或者把大的元素往后调。比较是相邻的两个元素比较,交换也发生在这两个元素之间。如果两个元素相等,不需要进行交换;如果两个相等的元素没有相邻,那么即使通过前面的两两交换把两个相邻起来,这时候也不会交换,相同元素的前后顺序并没有改变,所以冒泡排序是一种稳定排序算法。 (5)堆排序 堆的结构是节点i的孩子为2 * i和2 * i + 1节点,大顶堆要求父节点大于等于其2个子节点,小顶堆要求父节点小于等于其2个子节点。在一个长为n 的序列,堆排序的过程是从第n / 2开始和其子节点共3个值选择最大(大顶堆)或者最小(小顶堆),这3个元素之间的选择不会破坏稳定性。但当为n / 2 - 1, n / 2 - 2, ... 1这些个父节点选择元素时,就会破坏稳定性。有可能第n / 2个父节点交换把后面一个元素交换过去了,而第n / 2 - 1个父节点把后面一个相同的元素没有交换,那么这2个相同的元素之间的稳定性就被破坏了。所以,堆排序不是稳定的排序算法。 (6)归并排序 归并排序是把序列递归地分成短序列,递归出口是短序列只有1个元素(认为直接有序)或者2个序列(1次比较和交换),然后把各个有序的段序列合并成一个有序的长序列,不断合并直到原序列全部排好序。可以发现,在1个或2个元素时,1个元素不会交换,2个元素如果大小相等也没有交换,并且合并过程中可以保证如果两个当前元素相等时,把处在前面的序列的元素保存在结果序列的前面,这样就保证了稳定性。所以,归并排序也是稳定的排序算法。 (7)直接插入排序 插入排序是在一个已经有序的小序列的基础上,一次插入一个元素。刚开始这个有序的小序列只有1个元素,就是第一个元素。比较是从有序序列的末尾开始,也就是想要插入的元素和已经有序的最大者开始比起,如果比它大则直接插入在其后面,否则一直往前找直到找到它该插入的位置。如果碰见一个和插入元素相等的,那么插入元素把想插入的元素放在相等元素的后面。所以,相等元素的前后顺序没有改变,从原无序序列出去的顺序就是排好序后的顺序,所以插入排序是稳定的。 (8)希尔排序 希尔排序是按照不同步长对元素进行插入排序,一次插入排序是稳定的,不会改变相同元素的相对顺序,但在不同的插入排序过程中,相同的元素可能在各自的插入排序中移动,最后其稳定性就会被打乱,所以希尔排序是不稳定的。 综上所述,稳定的排序算法有:冒泡排序、归并排序、直接插入排序; 不稳定的排序算法有:快速排序、选择排序、直接选择排序、堆排序、希尔排序。 ④实验总结 通过本次实验,深刻感受到了各类排序方法的性能差别。 综合来看,快速排序算法的性能最好,缺点是不稳定,堆排序、归并排序、希尔排序表现均较为良好。在数据规模较小的时候,排序的效率为:快速排序>希尔排序>归并排序>堆排序。在数据规模较大的时候,排序的效率为:快速排序>堆排序>归并排序>希尔排序。在数据规模较大的时候,希尔排序表现相对一般,性能介于简单排序和复杂排序之间。其中,简单排序的性能都较为优越,但是基本都不稳定,除了归并排序是稳定的,在数据规模较大的情况下,且考虑稳定性,只能使用归并排序。若不考虑稳定性,且数据规模较大时,快速排序是最好的选择。 在数据规模较小的时候,排序的效率为:直接插入排序>直接选择排序>冒泡排序>选择排序。在数据规模较大的时候,排序的效率为:直接插入排序>直接选择排序>冒泡排序>选择排序。当数据规模较小的时候,复杂排序的性能与简单排序的性能相差并不大,此时若考虑稳定性的因素,可以选择使用冒泡排序和直接插入排序。当数据规模逐渐变大时,不考虑使用复杂排序。但是如果考虑稳定性因素,可以考虑使用直接插入排序,但无论数据规模大小,性能是不如归并排序的。而不考虑冒泡排序,性能相对太差,不考虑使用。 代码如下(示例):

四、源代码

#include <stdio.h>
#include  <stdlib.h>
#include  <time.h>

#define N 25000 //元素最大个数
typedef struct {
   
    int key;  //主关键字
} NODE;

void creatfile(NODE data[], int *n);//创建磁盘文件

void load(NODE data[], int n);//导入磁盘文件中的信息

void prn(NODE data[], int begin, int n);//输出随机数组begin..n-1

int partition(NODE data[], int low, int high);

void quicksort(NODE data[], int low, int high);//快速排序算法

void selesort(NODE data[], int n);//选择排序

void dselesort(NODE data[], int n);//直接选择排序

void pasort(NODE data[], int n);//冒泡排序

void dpasort(NODE data[], int n);//改进的冒泡排序

void sift(NODE data[], int i, int j);

void creatheap(NODE data[], int n);//堆排序,建立大根堆

void heapsortt(NODE *data, int n);//堆排序算法

void merge(NODE data[], int low, int m, int high);//归并

void mergepass(NODE data[], int n, int length);//一趟归并算法

void mergesortt(NODE *data, int n);//自底向上的二路归并排序算法

void inser(NODE data[], int n);//直接插入排序(不带监视哨)

void sinser(NODE data[], int n);//直接插入排序(带监视哨)

void shellsort(NODE data[], int n);//希尔排序算法

void Menu(void);

long exchange_time, compare_time;

void main() {
   
    NODE DATA[N];
    int n, op;

    clock_t start, finish;
    double total_time;
    Menu();
    scanf("%d", &op);
    while (op != 20) {
   
        switch (op) {
   
            case 1:
                creatfile(DATA, &n);
                break;
            case 2:
                load(DATA, n);
                break;
            case 3:
                prn(DATA, 0, n);
                break;
            case 4:
                exchange_time = 0;
                compare_time = 0;
                printf("\t\t4.快速排序算法\n");
                load(DATA, n);
                start = clock();
                quicksort(DATA, 0, n - 1);
                finish = clock();
                prn(DATA, 0, n);
                total_time = (double) (finish - start) / CLOCKS_PER_SEC;
                printf("比较次数:%ld\n", compare_time);printf("交换次数:%ld\t", exchange_time);
                printf("运行时间:%lf s\n", total_time);
                break;
            case 5:
                exchange_time = 0;
  
  • 20
    点赞
  • 112
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值