first commit

This commit is contained in:
2025-12-18 16:00:22 +08:00
commit 785f306726
69 changed files with 33171 additions and 0 deletions

View File

@@ -0,0 +1,98 @@
# 排序算法实验报告
## 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),不受输入数据初始顺序的影响。此外,归并排序是一种稳定的排序算法,而快速排序不是。
**最终结论**:对于通用场景下的内部排序任务,经过优化的快速排序(特别是双基准快速排序)是性能上的首选。而当需要排序稳定性或对最坏情况有严格要求时,归并排序是更可靠的选择。