一、实验目的和要求
收集至少两位同学的的 作业-01 的代码,做代码比较分析。
1.事先设计比较方案。比较内容要涉及到排序中数据的 存储结构、数量、排序方法、代码之间的时间性能、空间性能等指标(但不局限于以上指标)的不同。做出比较方案的预分析结果。
2. 改造代码,保证代码比较的公平公正可比性。
3. 按比较方案实际运行代码,记录实验结果,和预分析结果做对比,再次进行分析总结,对存在的问题尝试提出解决方案。
4. 其它要求同作业-01要求。
二、实验环境
编译器:Vscode
系统:Windows10
CPU:i5-8265U@1.60GHz
数据:int范围
对比代码来源:15-wpf,26-lhy,86-ssh
三、实验内容
综合时间复杂度,空间复杂度,排序算法的稳定性,复杂度对多个代码进行优劣分析并改进
四、实验过程
4.1 任务定义和问题分析
问题分析:
- 四份代码之间代码风格不同,阅读起来会带来混乱感
- 四份代码统计的时间范畴不同
- 数据集大小,内容不同,生成方式不同
- 数据集读取方式不同
- 是否输出最后排序结果
任务定义:
- 仅对比算法运行时间 不考虑读入输出的耗时
- 统一代码风格
- 统一统计时间范畴
- 统一选择快读 缩减不必要时间
- 小数据(小于100000)输出排序结果,大数据情况下不输出排序结果,防止占用不必要内存(太大的数据结果也打不开)
4.2 数据结构的选择和概要设计
其实本次实验根本上就是对朴素冒泡,优化冒泡,插入排序,归并排序的优劣情况做对比(其实这些工作在我的第一次实验报告里已经粗略完成,下面给出之前做的比较,不止这三个算法(朴素冒泡和优化冒泡可以看作一个))
以下为个人实际测试:(针对三个大小为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 |
这次实验的比较准备以100000为数据集大小 三个数据集来进行对比
预计结果:
时间优劣方面上:归并排序最优,插入排序次之,冒泡排序最差
空间优劣方面上:冒泡和插入都差不多(用时间换空间) 归并排序较弱
稳定性上:三个算法都能保证排序过程中的稳定
复杂度上:冒泡最简单,插入次之,归并排序最复杂
4.3 详细设计
收集了三位同学的排序代码
用的算法分别是 朴素冒泡排序,插入排序,优化后的冒泡排序。
加上我自己用的是归并排序
| 所用算法 | 时间复杂度 | 空间复杂度 | 稳定性 | 复杂性 | ||
平均情况 | 最好情况 | 最坏情况 | 辅助存储 | ||||
Nice_try | 归并排序 | O(nlogn) | O(nlogn) | O(nlogn) | O(n) | 稳定 | 复杂 |
wpf | 朴素冒泡 | O(n^2) | O(n^2) | O(n^2) | O(1) | 稳定 | 简单 |
lhy | 插入排序 | O(n^2) | O(n) | O(n^2) | O(1) | 稳定 | 简单 |
ssh | 优化冒泡 | O(n^2) | O(n) | O(n^2) | O(1) | 稳定 | 简单 |
时间复杂度中的最好情况是指整个序列已处于有序情况下去跑
最坏情况是指整个序列处于倒序情况下去跑
关于排序稳定性的定义:
通俗地讲就是能保证排序前两个相等的数其在序列的前后位置顺序和排序后它们两个的前后位置顺序相同。在简单形式化一下,如果Ai = Aj,Ai原来在位置前,排序后Ai还是要在Aj位置前。
在拿到三位同学代码后 对比了一下我们几个的实验一的结果
| 数据集最大值 | 完成排序所需时间 | 是否统计除排序以外的时间 | 如何读入数据集 |
Nice_try | 498844223 | 106s | 否 | 文件流 |
wpf | 5000 | 1.115s | 是 | 主函数内随机生成 |
lhy | 100000 | 27s | 是 | 主函数内随机生成 |
ssh | 400000 | 未记录成功 | 是 | 主函数内随机生成 |
不难发现 我们之间代码风格 统计等等方面存在着差异
首先初步对比了wpf和ssh的代码,可以判定 ssh算法是wpf算法的升级优化版本
下面给出佐证:
for (int j = 0; j < 4000000; j++)
{
for (int t = j + 1; t < 4000000; t++)
for (int i = 0; i < n; i++)
{
for (int j = 0; j < n; j++)
这是两份代码中唯一有区别的地方 不难看出第一个代码比第二个代码在第二层循环中优化了一个常数
所以可以暂时丢弃弱版本的冒泡排序
接下来仅进行对冒泡(优化),插入,归并排序的实验记录
首先第一步 在不改变代码运算效率的前提下统一三份代码格式
(比较看出冒泡排序可以用上次实验使用的版本 大同小异)
算法 | The first data | The second data | The third data |
归并 | 16ms | 15ms | 17ms |
冒泡 | 28952ms | 29014ms | 29169ms |
插入 | 6323ms | 6374ms | 6379ms |
做比较到这里 能够看出时间复杂度上 归并最优 插入次之 冒泡最弱
再看空间花费 冒泡和插入一般无二 都是开了一个数组来存储数据
然而归并却申请了动态数组将花费提到了O(n).
复杂程度上 归并操作较多 本人代码将近100行 而另外两种算法代码不过50行 归并还有分治思想需要去领会 较难上手
下面给出输出界面示例(仅输出了前100位的元素)
做比较到这里 能够看出时间复杂度上 归并最优 插入次之 冒泡最弱
再看空间花费 冒泡和插入一般无二 都是开了一个数组来存储数据
然而归并却申请了动态数组将花费提到了O(n).
复杂程度上 归并操作较多 本人代码将近100行 而另外两种算法代码不过50行 归并还有分治思想需要去领会 较难上手
算法名称 |
所能承受的数组大小 |
归并排序 |
498844223 |
冒泡排序 |
498846271 |
插入排序 |
498846271 |
这个结果也可以佐证三者之间空间花费比较 冒泡和插入都差不多(用时间换空间) 归并排序花费较多
实际测试了一番发现 会出现这种报错
terminate called after throwing an instance of 'std::bad_alloc'
what(): std::bad_alloc
超出了内存 运行程序过程中 cpu和内存都占到了98%以上 差点宕机
内存泄漏 没有及时释放内存
(看来可能是硬件性能问题)
等到虚拟机对cpu占用结束后 我又重新运行代码 得到了结果
| 数据集大小 | 所耗时间 |
归并 |
498844223 |
106s |
插入 |
超时未测出 | |
冒泡 |
超时未测出 |
占用内存一直不发生变化(暂定卡死)
五、测试及结果分析
5.1 实验数据
数据还是使用上次实验使用的小数据100000大小
已上传至本人gitee仓库
5.2 结果及分析
和预想的结果一般无二
时间优劣方面上:归并排序最优,插入排序次之,冒泡排序最差
空间优劣方面上:冒泡和插入都差不多(用时间换空间) 归并排序较弱
稳定性上:三个算法都能保证排序过程中的稳定
复杂度上:冒泡最简单,插入次之,归并排序最复杂
至于要将插入排序和冒泡排序进一步优化 我认为并不可行
毕竟他们的先天条件就在这(时间复杂度)优化的话就是换算法 (牺牲空间来换取时间)
六、实验收获
做好实验记录(数据要上传至云端)
小细节:
将所有代码用文件流输入 要想在vscode中显示出
单纯的system(“pause”);已经无法停留界面 于是我在代码最后追加了一个死循环 成功记录下了代码运行成功的界面
七、参考文献
八、附录(源代码)
仅提供代码主干;
插入排序主干
void InsertSort(int R[], int n)
{
int i, j;
int tmp;
for (i = 1; i < n; i++)
{
if (R[i] < R[i - 1])
{
tmp = R[i];
j = i - 1;
do
{
R[j + 1] = R[j];
j--;
} while (j >= 0 && R[j] > tmp);
R[j + 1] = tmp;
}
}
}
冒泡排序主要内容
for (int j = 0; j < n; j++)
for (int t = j + 1; t < n; t++)
if (s[j] > s[t])
swap(s[j], s[t]);