资讯动态

质数判断暴力法优化:从试除法到6k±1与筛法的工程实践

发布时间:2026/10/15 7:07:09 来源:尧图企业网站定制
1. 暴力法到底在暴力什么先说个我自己的经历。有段时间我沉迷刷题平台的每日一题某天遇到一道“判断n是否为质数”的基础题数据范围不大n不超过一亿。这种题放在工程里就是查个表的事但在算法题里它考察的是最基础的数学功底。我想都没想就写了个最朴素的循环从2除到n-1提交后果然超时。我当时还觉得委屈这题也太不讲武德了一亿次循环都不让跑完。后来仔细想了下是我自己对“暴力”的理解太粗糙了。暴力判断质数核心思路就是试除拿每个小于n的正整数去试除n只要有一个能整除n就是合数否则是质数。这个逻辑本身没有任何问题问题在于“每个小于n的正整数”这个范围选得太大。真正成熟的暴力法从来不是无脑循环到n-1而是在保证正确性的前提下把试除范围压缩到最小可验证区间。对于刚接触编程的人来说“暴力判断质数”可能只是一行for循环但对写过几年代码的人来说这个标题背后藏着好几层学问试除范围怎么确定、步长能不能优化、边界情况怎么处理、复杂度怎么分析、暴力到什么规模会撑不住、暴力思路和筛法思路的分水岭在哪。这篇文章我就把这些层一层一层剥开讲。先给结论暴力判断质数在实践中最常见的形式是试除法时间复杂度能做到O(√n)空间复杂度O(1)。这个复杂度在n是10^12以内时可以轻松应对再往上就需要更高级的工具了。下面从数学原理开始讲。2. 试除法的数学底子为什么检查到√n就够了2.1 一个简单但关键的因数对称性暴力法的第一个优化也是最核心的优化就是把试除范围从[2, n-1]缩小到[2, √n]。这个优化不是经验结论而是严格的数学事实推导过程比大多数人想的更简单。假设n是一个合数那么n一定可以分解成a × b其中a和b都是大于1的整数。如果a和b都大于√n那么a × b就会大于√n × √n n这就和a × b n矛盾了。所以a和b中至少有一个不大于√n。换句话说只要n有因子那么至少在[2, √n]这个区间里存在一个因子。基于这个性质我们在判断质数时只需要检查2到√n之间的整数是否能整除n。如果这个区间里找不到任何因子那n必然没有大于√n的因子也就必然是质数。我还记得第一次理解这个结论时的感觉明明只是把循环右边界换了数学上却有完全确定的依据而不是“概率上差不多对”。这给了我很深的印象也是我后来写所有质数相关代码时必讲的一个优化点。这个优化带来的收益非常可观n100万时暴力到n-1需要循环约99.99万次而检查到√n只需要循环999次性能差距是三个数量级。2.2 边界情况0、1、2、负数怎么处理边界情况是暴力法最容易翻车的地方而且往往不是算法本身的问题而是对数学定义的处理不够严谨。先说定义质数素数指的是大于1的自然数中除了1和它本身以外不再有其他因数的自然数。所以小于2的数包括0、1、负数都不是质数。这一点在代码里必须显式处理否则把2当成质数判断时会出错把1当成质数判断时也会出错。具体到代码判断n是否为质数的第一步不是写循环而是先做三个特判n小于2时直接返回falsen等于2或3时直接返回true之后才进入循环试除很多初学者的代码在这个地方吃过亏我记得有次写个小工具输入范围包含1结果输出结果把1算成了质数后来排查了半天发现少写了n 2的判断。另外负数在数学上按照上述定义肯定不是质数因为质数定义限定在自然数。实际工程中如果输入可能为负通常直接返回false。有些特殊场景比如密码学中的某些算法会用到负数的模运算但那就不是“判断质数”的范畴了不用在这里纠结。2.3 从2到√n的朴素实现长什么样理解了边界和范围之后一个标准的暴力判断质数函数就可以这样写int is_prime(long long n) { if (n 2) return 0; if (n 2 || n 3) return 1; for (long long i 2; i * i n; i) { if (n % i 0) return 0; } return 1; }这段代码写法上有个细节值得留意循环条件是i * i n而不是i sqrt(n)。前者避免了调用浮点开根函数也不存在精度问题。如果用sqrt函数由于浮点数精度影响在n特别大时可能出现边界判断误差导致结果不对。这种问题在实际中很少出现但既然写代码就要用最稳妥的方式。另外循环变量类型用long long而不是int是为了避免i * i在n较大时整数溢出。比如n是10^10时√n是10^5i * i会达到10^10如果用32位int存储已经溢出了会导致无限循环或漏判。这个坑在线上环境很容易踩我在某个项目里就遇到过i * i溢出导致死循环的情况后来把类型改成long long才解决。3. 步长优化从2到6k±1暴力也能很优雅3.1 为什么可以跳过2和3的倍数朴素版试除法虽然没错但还是做了很多无用功。以n101为例检查2到10之间的整数时会依次试除2、3、4、5、6、7、8、9、10。其中4、6、8、10显然不是必要的因为如果n能被4整除那么n一定能被2整除而我们早在检查2的时候就已经排除这种情况了。同理6、8、10这些偶数都是2的倍数不必重复检查。进一步观察可以发现除了2和3以外所有的质数都满足一个规律它们都在6的倍数的两侧也就是形如6k-1或6k1k为正整数。反过来不在6的倍数两侧的数即除了2和3以外的偶数以及3的倍数一定不是质数。这个结论的数学证明也不复杂任何整数都可以表示为6k、6k1、6k2、6k3、6k4、6k5中的一种。其中6k、6k2、6k4都是偶数能被2整除6k3能被3整除。所以除了2和3以外只有6k1和6k5可能是质数。注意6k5这个形式等价于6(k1)-1也就是6k-1的形式。3.2 6k±1优化的代码实现与测试基于这个性质试除范围可以进一步压缩。具体的做法是先单独检查2和3是否能整除n然后从5开始步长设为6每次检查i和i2两个数。这样循环的每次迭代只检查两个候选数跳过了大量明显不可能的整数。int is_prime_6k(long long n) { if (n 2) return 0; if (n 2 || n 3) return 1; if (n % 2 0 || n % 3 0) return 0; for (long long i 5; i * i n; i 6) { if (n % i 0 || n % (i 2) 0) return 0; } return 1; }我来测试一下这个优化在实际运行中的效果。写个简单的程序循环判断1万到10万之间所有整数的质数性统计总耗时。朴素版耗时约为6毫秒6k±1优化版耗时约为2毫秒。这个差距在单次判断时几乎感觉得到但在高频调用场景下非常可观。更重要的是6k±1优化在数学上有个额外好处它让试除的候选数从“所有整数”变成“理论上最可能是质数的整数”因此很多人把这个优化的正确性误解为“只判断6k±1就能确认质数”。这是个错误理解。6k±1只是质数的必要条件不是充分条件。比如25 6×4 1但它不是质数能被5整除。反过来在试除时跳过非6k±1的数之所以安全是因为那些数都有小于等于3的因子如果它们整除n那么2或3必然已经整除n了。这个优化实现起来也就多了三四行代码收益却实在。我在很多算法库里见到的标准质数判断函数用的就是这种形式。4. 暴力法还能怎么压榨提前排除与分支简化4.1 先做小因子检查的高性价比上面提到6k±1优化时其实已经隐含了一个思路在进入大循环之前先把2和3这两个最常见的小因子排除掉。这一步的价值在于它能以O(1)的代价解决大约2/3的合数——所有偶数、所有3的倍数都直接在这里被拦下根本不需要进入循环。这个思路可以继续延伸。对于特别大的n可以先检查一组更小的质数比如5、7、11、13等再进入主循环。这样做的好处在数据量大时更加明显因为提前排除一个合数的代价只是一次取模运算而进入主循环后进行多次试除的开销远高于此。不过这个优化要掌握好度。如果你预先检查了前10个质数那主循环的起点就不是5而是第10个质数之后的下一个候选数步长和起点需要相应调整。如果候选质数表取得太长比如几千个预检查比主循环本身还慢得不偿失。一般来说在实际工程中预检查前几十个质数就足够了。4.2 分支结构的写法影响性能在写判断质数这类高频函数时代码里的分支顺序、条件组合也会显著影响性能。举个直观的例子先判断n % 2和先判断n 2看起来怎么安排都行但实际效果完全不同。n 2的分支只命中极小比例的输入而n % 2的分支命中一半的输入。把命中率高的分支放在前面可以让绝大多数输入走最短路径。在6k±1版本的循环里条件n % i 0 || n % (i 2) 0的写法也有讲究。C系语言中||短路求值只要第一个条件为真就不会执行第二个取模。考虑到真实场景下输入n如果是合数小因子出现的概率更高所以把更可能触发退出的判断放前面是合理的。这里i和i2出现因子的概率没有显著差异但至少在知识层面要明白“短路求值对性能的影响”。这些微优化单独拿出来看每个也就快few百纳秒但质数判断函数常常被嵌套在循环里调用在算法题或大数据处理中调用次数动辄百万级别积累起来就是几秒钟的差距值得认真对待。4.3 测量而不是猜测我在做性能优化时有个习惯永远用数据说话不靠感觉做决定。判断质数的暴力实现看起来很简单但不同写法的差距到底有多大最好实际测量。我用一个简单程序做了对比测试。程序的任务是统计1到1000万之间所有质数的个数分别用朴素版和6k±1版实现。运行环境是普通的笔记本关闭优化时朴素版耗时约4.8秒6k±1版约1.6秒开-O2后朴素版约1.2秒6k±1版约0.4秒。在这台机器上6k±1版的性能在两种编译配置下都优于朴素版约3倍。有意思的是在开-O2之后朴素版耗时从4.8秒降到1.2秒这说明编译器优化的威力远超我们手写微优化带来的效果。所以在实际开发中我采购的优先级永远是正确实现数学上更优的算法 → 开编译器优化 → 最后才考虑手写微优化。5. 暴力法的性能极限能撑到多大规模5.1 时间复杂度与真实耗时对照暴力法判断单个n是否为质数的时间复杂度是O(√n)空间复杂度O(1)。这个复杂度看起来不高但当n变得很大时√n也会变得非常大。n10^12时√n10^6单次判断要循环100万次对现代CPU来说依然很快大约几毫秒但如果要做10万次这样的判断总耗时就会达到几百秒显然撑不住。让我给你一个更直观的感受。n和√n的增长对照如下n√n朴素循环次数上界6k±1实际循环次数约10^4100993310^6100099933310^931623316221054010^12100000099999933333310^15316227773162277610540925可以看到当n达到10^15时即使是6k±1优化单次判断也需要循环上千万次。这个量级对于单次调用来说勉强可以接受但如果需要在循环中反复判断质数或者处理大量输入数据暴力法就会成为瓶颈。5.2 什么时候暴力法会被“卡死”在实际工作中我遇到过几个典型的“暴力法卡死”场景。第一个是求某个区间内的质数个数。比如统计1到10^8之间有多少个质数。如果用单点暴力法对每个整数调用一次判断总计算量大约是求和√i的量级粗略估计在10^12次取模运算上下这在普通电脑上要跑几十分钟到几个小时完全不可接受。这种场景应该用筛法而不是暴力法。第二个是密码学里的密钥生成。生成RSA密钥需要生成两个大质数每个通常有1024位以上即n大约是10^308量级。在这个量级下√n大约是10^154哪怕是单次判断暴力法也毫无希望。实际算法会用概率性素性检验比如米勒-拉宾算法绝不可能用试除法。第三个是某些需要实时响应的场景。我做过一个系统需要对外部输入的每个数字做质数判断并实时返回结果。当时输入可能达到10^12量级虽然单次判断只要几毫秒但接口同时被打爆时几毫秒也会导致雪崩。后来做了缓存和预筛把热点数据提前算好才解决这个问题。这些例子说明一件事暴力法在最坏情况下是能工作的但它的上限取决于“单次判断”还是“批量判断”取决于对延迟的容忍度取决于n的规模。弄清楚自己的场景到底属于哪一类才能决定是否值得用暴力法。6. 从暴力到筛法批量判质数的“暴力升级版”6.1 埃氏筛用“标记”替代“试除”当你需要一次性判断2到N之间所有数是否为质数时单点试除法有一个巨大的浪费每个数都要独立做一次试除而大量试除工作在不同数之间是重复的。比如判断11和13时都会试除2判断121和143时都会试除11。这种重复在单点场景下无法避免但在批量场景下可以用筛法彻底消除。埃拉托斯特尼筛法简称埃氏筛的思路是维护一个布尔数组初始全部标记为“可能是质数”从2开始逐个把每个质数的倍数标记为“不是质数”最后仍保持“可能是质数”的数就是质数。它的时间复杂度是O(n log log n)空间复杂度O(n)。以判断2到30之间的质数为例过程是这样的初始时2到30全部标记为true取最小的质数2把4、6、8、10、12、14、16、18、20、22、24、26、28、30标记为false接下来取3把9、15、21、27标记为false6、12、18等已经被2标记过可以跳过接下来取5把25标记为false之后取7但7×749已经超过30标记工作结束。最终剩余的true就是2、3、5、7、11、13、17、19、23、29。埃氏筛的实现也很简洁void sieve(int n, char* is_prime) { for (int i 2; i n; i) is_prime[i] 1; for (int i 2; i * i n; i) { if (is_prime[i]) { for (int j i * i; j n; j i) { is_prime[j] 0; } } } }这里有个细节内层循环j从i * i开始而不是从i * 2开始。因为对于小于i * i的i的倍数比如i的2倍、3倍等它们一定含有比i更小的质因子已经在之前的标记中被处理过了。从i * i开始可以避免重复标记提高效率。6.2 线性筛让每个合数只被标记一次埃氏筛虽然比暴力法高效很多但它仍然存在一个效率问题同一个合数可能会被多个质因子重复标记。比如12既会被2标记又会被3标记30既会被2标记又会被3标记还会被5标记。虽然这种重复标记的总体开销不大但理论上我们完全可以用一种更严格的方法做到“每个合数只被标记一次”这就是欧拉筛也叫线性筛。线性筛的核心思想是每个合数只被它的最小质因子标记。具体做法是在遍历过程中维护一个已发现的质数列表对于当前遍历到的数i把它和质数列表中的每个质数相乘得到合数并在数组中标记同时如果i % 质数 0就立即停止本轮标记。这种做法的关键在于理解内层循环的中止条件当i % prime 0时说明prime是i的最小质因子那么i×prime的最小质因子也是prime。此时如果继续和更大的质数相乘比如prime_next得到的i×prime_next的最小质因子仍然是prime而不是prime_next这就违背了“每个合数只被最小质因子标记”的设计意图。所以必须在此时停止把这个合数留给后续轮次处理。线性筛的时间复杂度是O(n)空间复杂度同样O(n)在n特别大时比如10^8量级线性筛比埃氏筛快不少。不过在n小于10^6时两者的差异几乎可以忽略埃氏筛因为实现更简单反而更不容易出错。6.3 实际选择埃氏筛还是线性筛以我个人的经验批量判断质数的场景中优先考虑的是埃氏筛因为它实现简单、不容易出错。只有在两个条件同时满足时才值得升级到线性筛一是n非常大比如10^7以上二是性能要求极为苛刻或者在反复多次需要质数表的情况下要求稳定低延迟。我之前做过一个系统需要反复查询某个范围内的质数表每次构建都带来不小的消耗。后来优化为“一次性构建前缀和预处理”用埃氏筛构建一次质数表再用前缀和快速回答任意区间内的质数个数查询效果非常好查询时间降到微秒级。这种优化思路本质上是空间换时间也是暴力法向筛法过渡的典型收益。7. 现代视角暴力法真的“不现代”吗7.1 暴力法在工程中的真实地位很多人看到“暴力判断质数”这个标题可能会觉得这是算法入门内容实际工程中谁还用暴力但我的经验是暴力法在工程中的真实地位远被低估。随手算了一下在我参与过的数十个项目里至少有四五个地方用到了质数判断而其中大多数场景的输入规模根本用不到更高级的算法。最简单的场景是写单元测试时判断某个数是否为质数来构造或验证数据。输入量小、调用频率低此时用最简洁的暴力实现完全足够甚至因为逻辑直白代码审查者也容易通过。另一个场景是快速原型开发和脚本工具比如需要验证某个数的因数结构来确定下一步处理流程直接调一个暴力函数省时省力。这种“大材小用”的用法并不丢人。软件工程讲究的是恰好满足需求而不是永远堆上最复杂的算法。暴力法的最大优势不是快而是简单、正确、易验证。对于一次性脚本、数据量小的工具、教学示例来说这是最重要的特质。7.2 但有些场景暴力法真的不适合反过来我也遇到过不少因为坚持暴力法而吃苦头的例子。最典型的是没有评估数据规模就贸然使用暴力法。曾经有个数据处理任务需要对每行数据记录做质数判断数据量预估是几千条我就写了个暴力函数。结果实际数据涨到了百万级每个数字又都在10^12量级跑了一个多小时还没出结果后来改成先批量筛出10^6以内的质数表再把每个数字对小质数表试除几百秒内完成了。事后反思问题的根本在于没有提前做复杂度估算。如果你的场景符合以下任何一条都应该放弃暴力法需要判断的n超过了10^12哪怕只是单次判断也要考虑更高效的算法需要批量判断大量数字且这些数字范围很大需要极低延迟的接口响应单次判断超过1毫秒都嫌多需要判断的n以二进制形式出现且位数很长比如几十位以上这个量级暴力法完全失效7.3 暴力法作为“基准实现”的价值在讨论算法优化时暴力法还有一个经常被忽视的用途作为基准实现来验证高级算法的正确性。我在开发一个质数相关模块时先用暴力法实现了一个简单版本然后用它来测试更高效的版本。做法是对随机生成的一批整数分别用暴力版和优化版判断质数性然后对比结果是否一致。这一步虽然简单却能发现很多隐蔽的边界问题比如大数溢出、浮点精度、循环边界差一等。这种方法在工程中叫“差分测试”或“对拍”。它不需要复杂的测试框架只需要一个正确的暴力实现和一个待验证的高效实现。有了这个基准做优化时心里就踏实得多。每当优化的算法输出与暴力法不一致时问题几乎总能定位到优化算法本身的边界处理而不是暴力法有问题。所以我说不要轻易看不起暴力法。它既是很多低压力场景的最终答案也是高压力场景中验证复杂算法正确性的参照系。8. 于是我还研究了一点Miller-Rabin讲到质数判断如果再往前深挖一点就绕不开现代素性检验算法——特别是米勒-拉宾Miller-Rabin算法。虽然这篇文章的主角是暴力法但我想简单聊聊它因为理解“暴力法为什么力不从心”反而能帮助你更好地理解为什么现代密码学需要完全不同的思路。8.1 确定性算法和概率性算法的分水岭到目前为止我们讨论的暴力法、埃氏筛、线性筛都是确定性算法——它们能百分之百确定一个数是质数还是合数。米勒-拉宾算法则不同它是个蒙特卡洛算法也就是说它有一定的概率会把合数误判为质数。米勒-拉宾算法的核心是费马小定理的推广。费马小定理说如果p是质数那么对于任意不被p整除的整数a有a^(p-1) ≡ 1 (mod p)。反过来如果某个数p不满足这个性质那它肯定不是质数但满足这个性质的数不一定是质数因为存在“伪质数”会骗过费马小定理。米勒-拉宾算法通过把p-1分解成2^s × d的形式并对多个随机基底a进行检测将误判概率压到极低。在工程实现中如果选择一组固定的基底比如前12个质数可以对小于3×10^18的所有整数给出确定性结论。换句话说虽然理论上它是概率性的但在实际应用范围内可以当作确定性算法来用。8.2 你什么时候真需要Miller-Rabin如果要我给出一个简单的选型标准我会这样说n在10^12以内暴力法配合一些优化已经足够n在10^12到3×10^18之间如果只是少量单次判断用6k±1版本的暴力法也还能接受但如果判断次数较多建议用Miller-Rabinn超过3×10^18暴力法完全不可行Miller-Rabin是更合适的选择需要批量判断2到N之间的所有质数用埃氏筛或线性筛不要单独对每个数调Miller-Rabin在实际工程里Miller-Rabin的实现比暴力法复杂得多涉及大整数幂模运算、快速幂算法、随机数生成等代码量不是同一个量级。所以在大多数不涉及密码学的应用中我甚至不建议直接手写Miller-Rabin。先把暴力法和筛法用明白就已经能解决90%以上的实际问题。9. 暴力判断质数的完整代码与使用建议9.1 给出一个可以直接用的完整实现把前面讲的内容整合一下我给出一份可以直接复制使用的完整实现。这份实现结合了6k±1步长优化和边界处理适合在大多数工程场景中单点判断质数。#include stdbool.h bool is_prime(long long n) { if (n 2) return false; if (n 2 || n 3) return true; if (n % 2 0 || n % 3 0) return false; for (long long i 5; i * i n; i 6) { if (n % i 0 || n % (i 2) 0) return false; } return true; }使用这份代码时有几个注意事项i * i n这个写法在n接近10^18时可能溢出。如果你确实需要处理这个量级a一种更稳妥的写法是把条件改成i n / i避免乘法溢出函数名和返回值类型可以根据实际语言环境调整逻辑保持一致编译器优化选项请打开比如C/C的-O2对性能影响很大9.2 如果换成C或Python有哪些差异不同语言实现同一个算法的行为会有差异这里列一下我在实际使用中总结的要点语言注意事项C/C注意整数类型和溢出推荐用long long或uint64_tprintf/scanf比iostream快Javalong类型足够注意循环边界写成i * i n也要小心溢出建议i n / iPython大整数无溢出问题但解释器慢n较大时耗时明显可加jit或不使用Python做高频判断Go用int64标准库math/big可用于大数但性能不如原生类型Python版本有个独特的坑range(2, int(n0.5) 1)中先取整再循环可以避免浮点精度问题但int(n0.5)在n是10^16级别时可能因为浮点误差差1这时可以用math.isqrt(n)获得精确的整数平方根。Python 3.8提供了math.isqrt这是处理质数判断时一个小而实用的细节。9.3 测试用例设计建议写完一个质数判断函数后一定不要只测一两个数字就放心。我的经验是至少要覆盖几类边界边界值0、1、2、3、4、-1小质数5、7、11、13小合数4、6、8、9、10、12大质数和大合数比如10^97质数、10^98合数完全平方数81、100、121、144这些容易在试除边界上出问题我记得有次写实现完全平方数的边界测试挽救了一把。121判断时循环条件恰好到i11此时i * i n必须正确返回false。如果循环条件写成了严格小于而不是小于等于121就会被错误判为质数。这种一两个字符的差别带来的错误只有靠全面测试用例才能捕捉到。10. 我在实战中积累的几个暴论与心得10.1 质数判断可以当成“体检工具”来用在实际项目开发中我逐渐把质数判断当成了一个检查代码健康度的工具。它的特点是输入简单、输出明确、边界多、性能差异大。正因如此它非常适合用来练习算法能力、测试代码风格、验证优化效果。每次换一门新语言我都会先写一个质数判断函数来熟悉语法和处理边界的方式。这比写Hello World有意义得多。10.2 性能和正确性的权衡核心是场景回到最开始的刷题经历如果当时我多思考一下“数据范围上限是10^8√n是10^4”这个数量级就能在写第一版代码时直接采用优化后的循环而不是提交超时后再回头优化。这件事给我的教训是无论看起来多简单的题目动手之前先用复杂度估算确认一下方案的可行性永远不亏。质数判断这个需求虽然基础但它在算法设计上的覆盖范围极广从暴力试除到平方根优化从步长压缩到筛法再到概率性素性检验每一层优化背后都有扎实的数学依据和工程考量。认真把这条线梳理一遍对理解更复杂的算法问题有很大帮助。10.3 多试几门语言暴力的套路是通用的我测试过C、Python、Go、Java四种语言实现同样的质数判断结论是算法层面的优化思路完全一致但语言本身的性能差异很大。以判断10^6以内的质数总耗时为例C的暴力实现大约几十毫秒Python要慢几十倍。因此在高性能场景里不要迷信“用Python写也差不多”语言差异在算法边界明显时会放大到不可忽略的程度。反过来在脚本化、分析型任务里我用Python居多这时暴力法的性能已经不是瓶颈更重要的是快速实现和调试方便。所以选语言、选算法都要看上下文没有放之四海皆准的最优解。10.4 最后一道小练习自己实现一遍如果你读完这篇文章想真正检查自己是否掌握了暴力判断质数的方法我建议做一个练习不查资料不复制代码手写一个函数判断n是否为质数要求正确处理所有边界负数、0、1、2、3只试除到√n实现6k±1步长优化用随机生成的大量整数和另一个版本进行比较确保正确性写完后再用性能测试验证一下看看自己实现的版本和文中的示例代码相比在10^8规模的批量判断中差多少时间。这个练习的收获远不止一个函数本身它帮助你建立“从数学原理到代码实现再到性能验证”的完整思考链路这个链路在后续所有算法学习中都会用到。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑