最近在算法训练营中遇到一个经典的排序问题,原代码实现存在一些需要修正的地方。我们先来看这段C++代码的核心逻辑:
cpp复制#include <iostream>
using namespace std;
int a[30000001];
int main() {
int n;
cin >> n;
for(int i = 0; i < n; i++) {
for(int j = 0; j < n - i; j++) {
if (a[j] > a[j + 1]) {
int b = a[j];
a[j] = a[j + 1];
a[j + 1] = b;
}
for(int k = 0; k < n; k++) {
cout << a[k];
}
cout << " ";
}
}
return 0;
}
这段代码本意是实现冒泡排序算法,但存在几个关键问题需要修正:
原代码直接使用未初始化的数组a进行排序操作,这是严重的逻辑错误。在实际应用中,我们需要先输入数组元素:
cpp复制for(int i = 0; i < n; i++) {
cin >> a[i]; // 必须先输入数组元素
}
内层循环的边界条件j < n - i可能导致数组越界,当j = n - i - 1时,a[j + 1]会访问a[n - i],这在最后一次外循环时可能越界。正确的边界应该是:
cpp复制for(int j = 0; j < n - i - 1; j++) {
// 比较和交换操作
}
原代码在内层循环中频繁输出整个数组,这会导致输出过于冗长。通常我们只需要在排序完成后输出一次结果:
cpp复制// 排序完成后输出
for(int i = 0; i < n; i++) {
cout << a[i] << " ";
}
cout << endl;
基于上述分析,修正后的完整冒泡排序实现如下:
cpp复制#include <iostream>
using namespace std;
const int MAX_SIZE = 30000001;
int a[MAX_SIZE];
int main() {
int n;
cout << "请输入数组大小n (1 <= n <= 30000000): ";
cin >> n;
if(n <= 0 || n > MAX_SIZE - 1) {
cout << "输入大小不合法!" << endl;
return 1;
}
cout << "请输入" << n << "个整数: ";
for(int i = 0; i < n; i++) {
cin >> a[i];
}
// 冒泡排序核心算法
for(int i = 0; i < n - 1; i++) {
bool swapped = false; // 优化:记录是否发生交换
for(int j = 0; j < n - i - 1; j++) {
if(a[j] > a[j + 1]) {
swap(a[j], a[j + 1]);
swapped = true;
}
}
if(!swapped) break; // 未发生交换说明已有序
}
cout << "排序结果: ";
for(int i = 0; i < n; i++) {
cout << a[i] << " ";
}
cout << endl;
return 0;
}
提前终止优化:通过swapped标志位,当某一轮未发生交换时提前终止排序,减少不必要的比较。
使用标准库函数:用swap()函数替代手动交换,代码更简洁安全。
输入验证:添加了对数组大小的合法性检查,防止越界。
常量定义:使用MAX_SIZE常量而非魔数,提高代码可读性。
冒泡排序是一种简单的比较排序算法,其基本思想是:
冒泡排序是原地排序算法,只需要常数级别的额外空间(用于交换),空间复杂度为O(1)。
冒泡排序虽然简单,但在实际应用中效率较低,通常:
与其他排序算法相比:
改进的冒泡排序,交替方向进行扫描:
cpp复制void cocktailSort(int a[], int n) {
bool swapped = true;
int start = 0, end = n - 1;
while(swapped) {
swapped = false;
// 从左到右
for(int i = start; i < end; i++) {
if(a[i] > a[i + 1]) {
swap(a[i], a[i + 1]);
swapped = true;
}
}
if(!swapped) break;
end--;
// 从右到左
for(int i = end - 1; i >= start; i--) {
if(a[i] > a[i + 1]) {
swap(a[i], a[i + 1]);
swapped = true;
}
}
start++;
}
}
通过调整比较间隔来提高效率:
cpp复制void combSort(int a[], int n) {
int gap = n;
bool swapped = true;
while(gap != 1 || swapped) {
gap = max(1, gap * 10 / 13); // 收缩因子
swapped = false;
for(int i = 0; i < n - gap; i++) {
if(a[i] > a[i + gap]) {
swap(a[i], a[i + gap]);
swapped = true;
}
}
}
}
下面是一个简单的性能测试框架:
cpp复制#include <chrono>
#include <random>
#include <algorithm>
void testSort(void (*sortFunc)(int[], int), const string& name) {
const int SIZE = 10000;
int arr[SIZE];
// 生成随机数据
random_device rd;
mt19937 gen(rd());
uniform_int_distribution<> dis(1, 10000);
for(int i = 0; i < SIZE; i++) {
arr[i] = dis(gen);
}
auto start = chrono::high_resolution_clock::now();
sortFunc(arr, SIZE);
auto end = chrono::high_resolution_clock::now();
auto duration = chrono::duration_cast<chrono::milliseconds>(end - start);
cout << name << "耗时: " << duration.count() << "ms" << endl;
}
int main() {
testSort(bubbleSort, "标准冒泡排序");
testSort(optimizedBubbleSort, "优化冒泡排序");
testSort(cocktailSort, "鸡尾酒排序");
testSort(combSort, "梳排序");
testSort(sort, "STL sort"); // 作为基准
return 0;
}
典型测试结果可能如下:
优先使用标准库:在实际项目中应优先使用<algorithm>中的sort()函数,它基于快速排序实现,效率高且稳定。
理解原理更重要:虽然冒泡排序效率不高,但理解其原理有助于掌握更复杂的算法。
注意边界条件:编写排序算法时要特别注意数组边界,避免越界错误。
添加断言检查:在开发过程中可以使用assert()验证算法正确性。
考虑稳定性:冒泡排序是稳定排序,这在某些场景下很重要。
我在实际项目中使用排序算法时,发现理解算法特性比单纯记住实现更重要。比如当需要稳定排序且数据量不大时,冒泡排序可能比快速排序更合适。另外,调试排序算法时,可以添加中间输出帮助理解算法执行过程,但记得在最终版本中移除这些调试代码。