写代码的时候,很多人只关心功能能不能跑通,却忽略了程序的效率。比如你写了个排序功能,处理100个数字很快,但换成1万个数据就卡得不行。这时候问题往往不在电脑配置,而是算法实现的方式出了问题,尤其是在C++这种贴近底层的语言里,写法不同,性能差十倍都不稀奇。
从一个简单的例子说起
假设你要在一个数组里找某个值。最直接的办法是挨个遍历:
int findValue(int arr[], int n, int target) {
for (int i = 0; i < n; i++) {
if (arr[i] == target) {
return i;
}
}
return -1;
}
这叫线性查找,时间复杂度是O(n)。如果数组已经排好序,换成二分查找,复杂度直接降到O(log n)。对于百万级数据,差距就是“瞬间出结果”和“等得想重启”的区别。
二分查找的C++实现
用循环实现比递归更省栈空间,也更适合大数组:
int binarySearch(int arr[], int n, int target) {
int left = 0, right = n - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (arr[mid] == target) {
return mid;
} else if (arr[mid] < target) {
left = mid + 1;
} else {
right = mid - 1;
}
}
return -1;
}
注意这里mid的计算方式,left + (right - left) / 2 能避免整数溢出,是个小细节,但在实际项目中容易踩坑。
STL不是万能钥匙
有人觉得反正有STL,自己写算法多此一举。但std::find底层还是线性扫描,而std::binary_search虽然快,但前提是数据有序。如果你频繁插入新数据又反复查找,光靠STL默认函数可能越用越慢。
这时候就得自己控制数据结构。比如用set或map,底层是红黑树,插入和查找都是O(log n),适合动态变化的数据集合。但如果数据基本不变,建一次查几千次,不如直接排序+二分,连额外内存都省了。
缓存友好也很关键
现代CPU有缓存机制,访问连续内存比跳着访问快得多。所以哪怕两个算法理论复杂度一样,实际跑起来可能差好几倍。比如遍历二维数组,按行访问就比按列快:
// 推荐:行优先,内存连续
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
process(matrix[i][j]);
}
}
// 不推荐:列优先,缓存不友好
for (int j = 0; j < m; j++) {
for (int i = 0; i < n; i++) {
process(matrix[i][j]);
}
}
这个细节在处理图像、矩阵运算时特别明显,改两行代码,速度可能翻倍。
小改动,大提升
有时候不需要换整个算法,加个剪枝就能让程序轻松提速。比如在暴力搜索中,提前判断不可能的情况并跳出,避免无效计算。这类优化在回溯、DFS类问题里很常见。
写C++算法别只盯着“能不能出结果”,多想想“能不能更快”。毕竟用户不会因为你用了高级语法而点赞,他们只关心程序够不够快,卡不卡。