什么数据量适合用什么算法

什么数据量适合用什么算法

水文,图一乐就行,仅供参考。

什么数据量适合用什么算法

算法竞赛虽然不需要工业开发高性能软件一样做到对时间和内存的极值把控,但把握大致的数量级有助于理解和分析问题。

时间复杂度

对于 C++ 而言,当语句运行次数超过 5 × 10 8 5\times 10^8 5×108 时,代码很有可能将不能在 1s 内完成,所以计算时一般不包含系数,而是数量级 10 8 10^8 108 。所以以这个数据量为分水岭。运行次数的大头主要看代码中的循环的长度,例如
for(int i=1;i<=n;i++) 的循环长度就是 n n n 。

若时间可再多一些,限制可稍微放宽。

通常认为:

  • 时间复杂度为 O ( n ! ) \text{O}(n!) O(n!) 的算法,用 C++ 实现,可在 1s 内支持 n ≤ 12 n\le 12 n≤12 数据量的代码语句运行。

    • O ( n × n ! ) \text{O}(n\times n!) O(n×n!) 的算法例如枚举全排列的 dfs ,用 C++ 实现,可在 1s 内支持 n ≤ 10 n\le 10 n≤10 数据量的代码语句运行。
    • 这类算法往往通过 dfs 实现,需要根据剪枝情况进行判断。
  • 时间复杂度为 O ( k n ) \text{O}(k^n) O(kn) 的算法,用 C++ 实现,当 k = 2 k=2 k=2 时可在 1s 内支持 n = 27 n=27 n=27 数据量的代码语句运行,但通常为了和别的算法配合,一般 n ≤ 23 n\le 23 n≤23 ,例如折半搜索算法。 k k k 越往上支持的数量级越小。

  • 时间复杂度为 O ( n 2 ) \text{O}(n^2) O(n2) 的算法,用 C++ 实现可在 1s 内支持 10 4 10^4 104 次代码语句运行。 k k k 是常数。

  • 时间复杂度为 O ( k n ) \text{O}(kn) O(kn) 的算法,用 C++ 实现可在 1s 内支持 10 7 ∼ 10 8 10^7\sim 10^8 107∼108 次代码语句运行。 k k k 是常数。

  • 时间复杂度为 O ( n ) \text{O}(\sqrt{n}) O(n ) 的算法,用 C++ 实现可在 1s 内支持 10 16 10^{16} 1016 次代码语句运行,再往上有超时的风险。

  • 时间复杂度为 O ( n log ⁡ n ) \text{O}(n\log n) O(nlogn) 的算法,用 C++ 实现可在 1s 内支持 10 7 10^7 107 次代码语句运行。但为能稳定运行,建议数据量不超过 5 × 10 6 5\times 10^6 5×106 。

  • 时间复杂度为 O ( log ⁡ n ) \text{O}(\log n) O(logn) 的算法,理论上支持数组的极限 10 9 10^9 109 ,甚至枚举的极限 10 18 10^{18} 1018 ,但实际经常和别的算法一起出场,使得支持的数量级和别的算法一致。

其他的什么埃氏筛的 O ( n log ⁡ log ⁡ n ) \text{O}(n\log\log n) O(nloglogn) 、 希尔排序的 O ( n 1.3 ) \text{O}(n^{1.3}) O(n1.3) 分析起来比较麻烦,直接按照比较贴近的函数例如 O ( n ( log ⁡ n ) k ) \text{O}(n(\log n)^k) O(n(logn)k) 计算即可。

其他的超越函数级别的时间复杂度,按照加法、乘法进行运算估计数据量即可。

但不是所有的 O ( 1 ) \text{O}(1) O(1)​ 操作的时间都是相同的,其中

  • 整数加减乘:快。
  • 除法、取模:慢(比加、减、乘慢约 10 ∼ 20 10\sim 20 10∼20 倍)。
  • 函数调用、虚函数、异常:有额外开销。
  • STL 容器(如 vectorpush_back)均摊 O ( 1 ) \text{O}(1) O(1) 但常数较大。
  • 浮点数的计算比整数还要慢。

在实际解决问题时需要根据情况分析。

空间复杂度

空间复杂度主要看:

  1. 申请的变量占用的空间大小。
  2. 函数调用占用的空间大小。

一般情况下不超过场景划定的红线 即可。但不建议一次开辟的数组的元素数量超过 10 9 10^9 109 ,否则很大可能会失败导致程序启动失败,即使启动成功,但支持开辟函数的空间变小也有可能使程序崩溃。

不含局部变量的递归函数,其递归深度主要受限于系统栈的大小。在 C++ 中,默认栈空间通常为 8 MB(具体取决于编译器和平台)。

每个函数调用即使没有局部变量,也需要保存返回地址、寄存器状态等,通常占用几十字节(例如 32 ∼ 64 32\sim 64 32∼64字节)。若这些太抽象了我不懂,但有一个要理解:函数要维护自己的地址凡是地址都需要用指针变量存储 ,指针变量可以认为总共就是 32 、 64 32、64 32、64 字节,没有局部变量的情况下也要存储指针

因此,理论最大递归深度 约为 8 × 1024 × 1024 / 64 ≈ 131072 8×1024×1024/64\approx 131072 8×1024×1024/64≈131072,即约 1.3 × 10 5 1.3×10^5 1.3×105 级别。实际中可能略低(如 10 5 10^5 105 左右)。实际测试中,很多递归程序在深度达到 10 5 10^5 105 左右时会栈溢出,所以 10 5 ∼ 2 × 10 5 10^5\sim2\times 10^5 105∼2×105 是一个常见的安全上界。

但要注意 Windows 通常 1 MB,Linux 通常 8 MB (估计没人用 IOS 设备参加算法竞赛吧)。

递归函数内存在局部变量,就需要将这部分变量代入考虑。通常认为:

单次栈帧大小 = = = 固定开销 + + + 局部变量总大小 (含数组) + + + 对齐

之所以有对齐是因为 CPU 一次访问的数据是 4 或 8 字节,为了能顺利访问到所有数据,需要将所有数据按指定的字节对齐,哪怕浪费空间也在所不惜。

例如:

cpp 复制代码
void rec() {
    char a[1000];  // 实际占 1000 字节,但因对齐可能占 1008 字节
}

栈空间小于是就向堆区申请空间模拟递归。堆空间通常远大于栈(几GB vs 几MB),不包含局部变量的情况下,可以模拟上百万层的递归。但有几个问题:

  1. 需要手动管理函数栈帧,且无法完美复现递归函数的运行进度,不如栈区的递归函数无脑。
  2. 访问堆区的指针的单次访问速度比访问栈区的指针慢,数量级庞大时可能比栈区更耗时。

最后贴上《锦鲤》:

相关推荐
超绝振刀怪3 小时前
【C++多态】
开发语言·c++
zc.ovo4 小时前
河北师范大学2026校赛题解(A,E,I)
c++·算法
py有趣4 小时前
力扣热门100题之环形链表
算法·leetcode·链表
py有趣4 小时前
力扣热门100题之回文链表
算法·leetcode·链表
学嵌入式的小杨同学5 小时前
STM32 进阶封神之路(三十九)FreeRTOS 临界区、挂起 / 删除、钩子函数、调度底层原理|从应用到内核深度解析
c++·stm32·单片机·嵌入式硬件·mcu·硬件架构·pcb
oioihoii5 小时前
Cursor根本无法调试C++
开发语言·c++
月落归舟6 小时前
帮你从算法的角度来认识二叉树---(二)
算法·二叉树
SilentSlot7 小时前
【数据结构】Hash
数据结构·算法·哈希算法