Files
algo2025/sort_closet/code-sorting/REPORT.md
2025-12-18 16:00:22 +08:00

6.7 KiB
Raw Blame History

排序算法实验报告

1. 解题思路

本实验旨在深入理解并分析多种经典排序算法的性能。为了达成此目标,实验遵循了以下设计思路:

  1. 模块化实现:将每种排序算法(插入、冒泡、希尔、归并、快速排序及其变体)分别实现在独立的 .cpp 文件中,并通过一个统一的 algorithm.h 头文件进行声明。这种结构使得代码清晰、易于扩展和维护。

  2. 量化性能指标:为了客观评估算法性能,除了记录运行时间外,还在算法实现中进行“插桩”,精确统计了两个关键操作:

    • 比较次数 (Comparisons):元素之间的比较操作,是决定算法时间复杂度的核心因素之一。
    • 移动次数 (Moves):元素的赋值或交换操作,反映了算法的数据搬运成本。
  3. 自动化测试框架:设计了一个灵活的测试框架 (main.cpp),能够:

    • 自动化地对所有已实现的算法进行测试。
    • 支持对多种不同的输入规模(例如 100, 1000, ..., 500,000进行测试。
    • 通过多次重复实验并取平均值的方式,消除单次运行的随机误差,保证结果的可靠性。
    • 在每次排序后进行正确性校验,确保算法实现无误。
    • 以格式化的表格输出结果,便于阅读和后续分析。
  4. 迭代优化与对比:重点对快速排序进行了深度探索,实现了从基础版本到优化版本(三数取中、三路快排、双基准快排)的演进。通过将这些版本置于同一测试框架下进行性能对比,可以直观地展示不同优化策略带来的效果。

2. 算法复杂度分析与实验数据验证

理论分析

算法 平均时间复杂度 最好情况 最坏情况 空间复杂度
InsertSort O(n²) O(n) O(n²) O(1)
BubbleSort O(n²) O(n) O(n²) O(1)
ShellSort O(n log n) ~ O(n²) O(n log n) O(n²) O(1)
MergeSort O(n log n) O(n log n) O(n log n) O(n)
QuickSort O(n log n) O(n log n) O(n²) O(log n)
QuickSortOpt O(n log n) O(n log n) O(n²) O(log n)
QuickSort3Way O(n log n) O(n) O(n²) O(log n)
DualPivotSort O(n log n) O(n log n) O(n²) O(log n)

实验数据验证

通过运行实验程序,可以得到不同算法在不同规模下的平均运行时间、比较次数和移动次数。这些数据可以用来验证上述理论复杂度。

  • O(n²) 算法 (InsertSort, BubbleSort):

    • 预期:当输入规模 n 增大10倍时运行时间、比较和移动次数大约会增大100倍。
    • 观察:从实验数据中可以看到,当 n 从 1000 增加到 10000 时,InsertSortBubbleSort 的运行时间急剧增加,远超线性增长,这与 O(n²) 的特征相符。由于性能问题,测试框架在 n >= 50000 时自动跳过了这些算法。
  • O(n log n) 算法 (MergeSort, QuickSort 变体):

    • 预期:当输入规模 n 增大时,性能增长平缓。比较次数大致在 n * log(n) 的量级。
    • 观察MergeSort 和各种 QuickSort 的运行时间随 n 的增长远比 O(n²) 算法要慢。例如,从 n=10000n=10000010倍它们的运行时间增长远小于100倍符合 n log n 的趋势。MergeSort 的比较次数非常稳定,接近理论值。
  • 快速排序变体对比:

    • QuickSort (基础版) 在随机数据下表现良好,但如果输入数据有序,其性能会退化到 O(n²)。
    • QuickSortOpt (三数取中) 通过改进基准点选择,显著提高了在非完全随机数据下的稳定性,其比较和移动次数通常略优于基础版。
    • QuickSort3Way 在处理含大量重复元素的数组时优势最大(本次实验为随机数据,优势不明显),其在最好情况下(所有元素相同)可达 O(n)。
    • DualPivotSort (双基准) 在理论上可以减少比较次数。从实验数据看,在较大规模的数据集上(如 n=100000 及以上),它通常比单基准的快速排序更快,显示出其优化效果。

3. 程序运行指导

编译

所有相关的 .cpp 源文件需要一起编译。可以使用 g++ 编译器,命令如下:

g++ main.cpp InsertSort.cpp BubbleSort.cpp ShellSort.cpp MergeSort.cpp QuickSort.cpp QuickSortOptimized.cpp QuickSort3Way.cpp DualPivotQuickSort.cpp out.cpp -o sorting_experiment

此命令会生成一个名为 sorting_experiment 的可执行文件。

运行

直接在终端中运行生成的可执行文件:

./sorting_experiment

输出结果说明

程序会输出一个性能分析表格,每一行代表一个算法在一个特定输入规模下的测试结果。

说明
Algorithm 被测试的排序算法名称。
Size 输入数组的元素个数(即规模 n)。
Avg Time (s) 多次重复测试的平均运行时间,单位为秒。
Avg Comparisons 平均比较次数。
Avg Moves 平均移动(赋值/交换)次数。
Correct? 排序结果是否正确,"Yes" 表示正确。

4. 性能对比与结论

  1. 算法类别差异O(n²) 级别的算法插入排序、冒泡排序仅适用于小规模数据。当数据规模超过一万时其性能急剧下降无法在实际应用中使用。相比之下O(n log n) 级别的算法(归并、希尔、快速排序)则表现出卓越的性能和可扩展性。

  2. 快速排序的优势与优化:在所有 O(n log n) 算法中,快速排序及其变体通常在平均情况下的性能最好,这得益于其更少的常量因子和高效的缓存利用率。

    • 基准点选择至关重要:基础的快速排序在特定数据模式下存在性能退化的风险,而“三数取中”等优化策略能有效缓解此问题,增强算法的稳定性。
    • 双基准快排的威力DualPivotQuickSort 在大规模随机数据上展现了最佳性能,验证了其在现代计算环境下的理论优势。
  3. 归并排序的稳定性:虽然 MergeSort 在本次测试中的原始速度略逊于最优的快速排序,但它具有一个重要优点:其性能是稳定的 O(n log n),不受输入数据初始顺序的影响。此外,归并排序是一种稳定的排序算法,而快速排序不是。

最终结论:对于通用场景下的内部排序任务,经过优化的快速排序(特别是双基准快速排序)是性能上的首选。而当需要排序稳定性或对最坏情况有严格要求时,归并排序是更可靠的选择。