一、实验目的和要求
完成尽可能多的数据排序,并显示运行时间。
二、实验环境
编译器:Vscode DevC++
系统:Windows10
CPU:i5-8265U@1.60GHz
数据:int范围(排除桶排序,基数排序,计数排序)
三、实验内容
1. 选择合适的排序算法
2. 确定可排序数据长度最大值
3. 固定可排序数据内容
4. 显示运行时间
四、实验过程
4.1 任务定义和问题分析
当前任务分析:
1. 从什么方面去选择排序算法
2. 如何确定可排序数据大小的上限
3. 不同变量对实验的影响
问题分析:
- 对于排序算法的选择应从实验目的出发
为了处理尽可能多的数据 要求我们考虑数据很多时的情况
应选择时间,空间复杂度较小,足够稳定,能够在处理大数据时拥有较好的性能的算法
- 根据经验,首先确定一个大致范围,给设定上界(足够大,大到使算法崩溃)与下界(足够小,小到算法无障碍运行),然后利用二分来确定合适的边界
- 本实验的变量有数据集的大小,编译环境(不同编译器),不同排序算法,相同大小数据集的复杂程度(小数据可以进行设计,数据过大时无能为力,这就需要考虑算法的稳定性)。
4.2 数据结构的选择和概要设计
选择归并排序进行实验(内部实现还是依靠数组)
利用二分法来寻找数据上限(一步步逼近极限)
寻找足够多的变量进行对照实验 以期实验可靠性
4.3 详细设计
首先是进行对排序算法的选择 可以根据排序算法的时间复杂度和空间复杂来进行挑选
因为各种排序算法的对数据的存储处理手段大都为数组 所以空间复杂度均为O(n),故算法的挑选在于时间复杂度的不同。
下面是一个总的表格,大致总结了我们常见的所有的排序算法的特点。
排序法 | 平均时间 | 最差情形 | 稳定度 | 额外空间 | 备注 |
冒泡 | O(n2) | O(n2) | 稳定 | O(1) | n小时较好 |
交换 | O(n2) | O(n2) | 不稳定 | O(1) | n小时较好 |
选择 | O(n2) | O(n2) | 不稳定 | O(1) | n小时较好 |
插入 | O(n2) | O(n2) | 稳定 | O(1) | 大部分已排序时较好 |
基数 | O(nlogRB) | O(nlogRB) | 稳定 | O(n) | B是真数(0-9), R是基数(个十百) |
Shell | O(nlogn) | O(ns) 1<s<2 | 不稳定 | O(1) | s是所选分组 |
快速 | O(nlogn) | O(n2) | 不稳定 | O(nlogn) | n大时较好 |
归并 | O(nlogn) | O(nlogn) | 稳定 | O(1) | n大时较好 |
堆 | O(nlogn) | O(nlogn) | 不稳定 | O(1) | n大时较好 |
(表格取自网络)
以下为个人实际测试:(针对三个大小为100000的固定的随机数集)
(已忽略数据的读写时间)
排序算法 | 第一个数据集所花费时间 | 第二个数据集所花费时间 | 第三个数据集所花费时间 |
Bubblesort(冒泡排序) | 54317ms | 58288ms | 61413ms |
Insertsort(插入排序) | 32999ms | 28839ms | 29197ms |
Shellsort(希尔排序) | 53ms | 77ms | 73ms |
Heapsort(堆排序) | 46ms | 38ms | 36ms |
Quicksort(快速排序) | 24ms | 27ms | 28ms |
Mergesort(归并排序) | 21ms | 21ms | 25ms |
依据以上两个表格,可以得知最优选择应为归并排序。
原因有以下几点:
- 时间复杂度小
- 算法足够稳定
- 对于n较大的情况更优
敲定排序算法后将进行对可排序数据大小的测试
测试思路:使用二分法来寻找极限值
目前以100000(10^5)为较小值,2147483647(2^31-1)为较大值进行二分
针对同样的数据集将进行六次统计时间 并求出平均值
实验细节:
考虑到实际等待算法运行完毕的时间还包括数据的读入和输出,因此对于数据的读入使用快速读入,输出则使用printf 来减少等待时间(1亿规模下,算法仅25s左右,而实际需要等待近1分钟)
由于数据过于庞大(且电脑还有其他占内存东西在运作),所以在打开输入输出文件时容易崩溃,解决方案有三个:1. 关闭其他程序 2.仅统计时间,不对处理后的数据进行输出 3.使用电脑自带的程序来打开文件并等待
当数据过于庞大时,随机输出数据也是十分耗时,于是要使用输出优化。
上面是我尝试造2147483647(2^31-1)大小的数据集 结果内存险些炸了(本人电脑办公本,内存较少的那种)这还是程序没有跑完 所以换了小数据10^9
结果打不开 无法检验 先行默认正确(毕竟多少给点面子,不过有待验证,因为2*10^9(2^31-1)比110GB要大,但缩减到原来的1/20 却仅剩下4.43GB)
Dev中具有报错,但程序依旧运行(不知道是卡死了 还是在运行)
(经过时间的检验 大概可以判定是卡死)
网上搜索后得知
意思就是int数组开大了,开int数组特别是二维数组需要注意不超过10^8.
经过逼近测试 边界值为498844223
(这个数加一数组就爆了)
带上数据读入所用的时间 比较可观:3分钟左右
五、测试及结果分析
对各种数据运行程序和算法的结果记录和分析,并对错误所作的修改和结果。
针对不同算法 同样大小(100000)的数据集 进行时间测定
排序算法 | 第一个数据集所花费时间 | 第二个数据集所花费时间 | 第三个数据集所花费时间 |
Bubblesort(冒泡排序) | 54317ms | 58288ms | 61413ms |
Insertsort(插入排序) | 32999ms | 28839ms | 29197ms |
Shellsort(希尔排序) | 53ms | 77ms | 73ms |
Heapsort(堆排序) | 46ms | 38ms | 36ms |
Quicksort(快速排序) | 24ms | 27ms | 28ms |
Mergesort(归并排序) | 21ms | 21ms | 25ms |
针对同样的数据集将进行六次统计时间 并求出平均值
(编译器为vscode)
数据集大小 | 10^8 (560MB) | 2^31-1 | 10^9 | 5*10^8 | …… | 498844223 (2GB) |
1 | 25960 | 失败(造数据阶段就失败了) | 失败(申请数组阶段失败) | 失败(申请数组阶段失败) | 失败(申请数组阶段失败) | 111234 |
2 | 27346 | 104743 | ||||
3 | 29321 | 105939 | ||||
4 | 27045 | 104062 | ||||
5 | 27274 | 105212 | ||||
6 | 28546 | 104762 | ||||
平均 | 27582 | 105992 |
(编译器为dev)各个数据集同上
数据集大小 | 10^8 560(MB) | 2^31-1 | 10^9 | 5*10^8 | …… | 498844223 (2GB) |
1 | 29098 | 失败(造数据阶段就失败了) | 失败(申请数组阶段失败) | 失败(申请数组阶段失败) | 失败(申请数组阶段失败) | 108798 |
2 | 29098 | 106985 | ||||
3 | 27878 | 109460 | ||||
4 | 25959 | 107411 | ||||
5 | 25737 | 105682 | ||||
6 | 27930 | 104540 | ||||
平均 | 27617 | 107162.7 |
实验的最终结果是
归并排序所能排序的数据最大个数是498844223
所花费时间为:106s
5.1 实验数据
三个100000大小的数据在下面网站data文件内,各排序算法源码在source code 文件夹下
https://gitee.com/Nicet/Data-Structure/tree/master/The%20First%20Test%20
由于10^8的数据文件大小有539MB 暂无法上传
(当然了,大于10^8的498844223的数据文件有2.16GB 所以更是无法上传)
5.2 结果及分析
我认为,实际算法是可以处理数据的最多不止到498844223 只不过本笔记本或编译器设置内存上限小。
不过就实验过程来看 可以说结果就是498844223
就和实验前面提到的这是数组在本机上可以申请到的最大值(int)
六、实验收获
分析数组大小时的收获
1. 函数内申请的变量,数组,是在栈(stack)中申请的一段连续的空间。栈的默认大小为2M或1M,开的比较小。
2. 全局变量,全局数组,静态数组(static)则是开在全局区(静态区)(static)。大小为2G,所以能够开的很大。
3. 而malloc、new出的空间,则是开在堆(heap)的一段不连续的空间。理论上则是硬盘大小。
实验过程有一个长达一天半的小插曲:
在认为printf过慢是我调用了输出优化来生成数据 结果忘了输出元素之间的空格 导致程序卡死
下面当时我对这种奇异现象的实验报告描述
甚至我都连实验结果写了
这个是因为10^8之后的数据都忘了生成空格了
而且由于数据庞大 无法打开检验
直到看到10^8+1的数据大小比10^8的还要小的时候,我意识到了不对
打开看了看 发现没有空格 才恍然大悟
翻工重做
边界数据:498844223
这才对------之前一直没有占据很大内存
不过 没多大会 数据变动成了
这下好了 cpu都不占用了
不过耐心的我还是等了下去
后来我把应用程序打开一看:
结束了已经跑出来结果了
七、参考文献
超详细十大经典排序算法总结(java代码)c或者cpp的也可以明白
八、附录(源代码)
下面将贴上主要代码:
void Merge(int arr[], int aux[], int left, int mid, int right)
{
int i = left;
int j = mid + 1;
int k = left;
while (i <= mid && j <= right)
{
if (arr[i] > arr[j])
{
aux[k++] = arr[j++];
}
else
{
aux[k++] = arr[i++];
}
}
while (i <= mid)
{
aux[k++] = arr[i++];
}
while (j <= right)
{
aux[k++] = arr[j++];
}
for (int i = left; i <= right; i++)
{
arr[i] = aux[i];
}
}
void MergeSort(int arr[], int aux[], int left, int right)
{
if (left < right)
{
int mid = left + (right - left) / 2;
MergeSort(arr, aux, left, mid);
MergeSort(arr, aux, mid + 1, right);
Merge(arr, aux, left, mid, right);
}
}
void MergeSort(int arr[], int length)
{
clock_t start, end;
start = clock();
int *aux = new int[length];
MergeSort(arr, aux, 0, length - 1);
delete[] aux;
end = clock();
cout << end - start << "\n";
// for (int i = 0; i < length; i++)
// printf("%d ", arr[i]);
}
以上是本实验所选算法--------归并排序
下面这个函数是本实验所选读入优化
void read(int &x)
{
int f = 1;
x = 0;
char s = getchar();
while (s < '0' || s > '9')
{
if (s == '-')
f = -1;
s = getchar();
}
while (s >= '0' && s <= '9')
{
x = x * 10 + s - '0';
s = getchar();
}
x *= f;
}
下面这个函数是本实验为减少造数据时间所选的输出优化
void print(int x)
{
if (x > 9)
print(x / 10);
putchar(x % 10 + '0');
}
其余算法的code请移步到gitee上clone
很多图片上传失败 如果认为阅读有障碍 请移步到gitee上下载观看