# 排序算法实验报告

## 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 时，`InsertSort` 和 `BubbleSort` 的运行时间急剧增加，远超线性增长，这与 O(n²) 的特征相符。由于性能问题，测试框架在 `n >= 50000` 时自动跳过了这些算法。

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

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

## 3. 程序运行指导

### 编译

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

```bash
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` 的可执行文件。

### 运行

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

```bash
./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)，不受输入数据初始顺序的影响。此外，归并排序是一种稳定的排序算法，而快速排序不是。

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