|
楼主 |
发表于 2023-2-13 13:23
|
显示全部楼层
另一位答主@北海若 用python从朴素算法一步步优化,展示了几种计算阶乘的方法,本来我想用C++实现一下,但效率一直无法突破,所以此处暂时略去,如果以后有啥发现再补,没有就不补了。
在这里补一下C++实现的朴素算法吧,只放一些没注释的代码,原理与下面的python一致(虽然python也没有注释
#include<iostream>
#include<chrono>
#include &#34;unsigned_bigint.h&#34;
#include &#34;timer.h&#34;
using namespace std;
using ubigint = kedixa::unsigned_bigint;
ubigint multi(int s, int t)
{
if(t - s < 40)
{
ubigint u(1u);
for(; s <= t; ++s)
u *= s;
return u;
}
int mid = (t+s) / 2;
return multi(s, mid) * multi(mid+1, t);
}
ubigint fac(int n)
{
return multi(1, n);
}
ubigint fac3(int n)
{
int M = 64;
vector<ubigint> vbig(M, ubigint(1));
for(int i = 2; i <= n;)
{
ubigint t(1u);
int r = 0;
while(i <= n && t.size() < 50)
t *= i, ++i, ++r;
for(int j = 0; j < M; ++j)
{
if(vbig[j] == 1u) {
vbig[j] = std::move(t);
break;
}
t *= vbig[j];
vbig[j] = ubigint(1u);
}
}
ubigint u(1u);
for(auto &b : vbig)
u *= b;
return u;
}
int main()
{
kedixa::timer t;
auto u1 = fac(100000);
cout << t.stop() << endl;
t.reset();
u1 = fac3(100000);
cout << t.stop() << endl;
}
无符号整数乘法是自己写的,需要的头文件在kedixa/klibcpp,实现在kedixa/klibcpp,实现的思路在C++大整数运算(一):概述 - kedixa的博客,找一些优秀的高精度计算库可能会更快一些。cpython中的算法还没看懂,如果能按照相同的思路写出来,效率应该差不多。
补充完毕。
----------
此处说一个关于朴素算法另一位答主可能没发现的优化,在计算高精度乘法的时候,用一个很大的整数乘以一个很小的整数,此时不管用什么方法计算,复杂度都不会降下来,而当两个大小相近的整数相乘的时候,高精度乘法的优势才能展现出来,因此如果用下面这种方法计算,速度会提高很多。
import math
import time
import timeit
# 朴素方法
def f1():
y = 1
for i in range(1, 100001):
y *= i
return y
# 一点小优化
def f2(s, t):
if s==t:
return s
mid = (s+t)//2
return f2(s, mid) * f2(mid+1, t)
# 小范围优化
def f3(s, t):
if t - s < 50:
r = 1
for i in range(s, t+1):
r *= i
return r
mid = (s+t)//2
return f3(s, mid) * f3(mid+1, t)
print(timeit.timeit(&#34;f1()&#34;, &#34;from __main__ import f1&#34;, number = 1))
print(timeit.timeit(&#34;f2(1, 100000)&#34;, &#34;from __main__ import f2&#34;, number = 1))
print(timeit.timeit(&#34;f3(1, 100000)&#34;, &#34;from __main__ import f3&#34;, number = 1))
print(timeit.timeit(&#34;math.factorial(100000)&#34;, &#34;import math&#34;, number=1))我的电脑性能略差(划掉),修正了电脑性能差的问题后(换了个电脑跑),上面的代码输出约为
3.5504379999999855
0.32651099999998223
0.23477900000000318
0.18042300000001887感觉仔细研究一下还能再快一点,在同一个机器上,我用C++实现这种方法(f3)在 O2的优化下大概需要0.18秒,与math.factorial比较接近了。
8.23
再补一个非递归版的
def f4(n):
L = [1 for i in range(32)]
i = 2
while i <= n:
t, r = 1, 0
while i <= n and r < 50:
t *= i
i += 1
r += 1
pass
for j in range(0, 32):
if L[j] == 1:
L[j] = t
break
t *= L[j]
L[j] = 1
u = 1
for i in L:
u *= i
return u按说非递归应该更快一点,但实际跑起来比f3略慢。看了一下cpython的实现 https://github.com/python/cpython/blob/772d809a63f40fd35679da3fb115cdf7fa81bd20/Modules/mathmodule.c#L1654 好像用的就是[1]中的方法。
另外提供一点资料
[1] Matrix67: The Aha Moments
[2] Java and C# Implementations |
|