欧拉函数(Euler’s totient function),也叫φ函数,入参只能是正整数。定义是从1到N这N个数里,和N互质的数的个数。欧拉函数没有通项公式,只有分段递归公式。下面是1~10的欧拉函数值:
x | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
---|---|---|---|---|---|---|---|---|---|---|
φ(x) | 1 | 1 | 2 | 2 | 4 | 2 | 6 | 4 | 6 | 4 |
欧拉函数最简单的算法是循环。但是这个计算方法非常费时。高斯证明了以下一个公式:
∑
d
∣
n
ϕ
(
d
)
=
n
\sum_{d|n}\phi(d)=n
d∣n∑ϕ(d)=n
这里的d|n的意思是遍历n所有的约数。比如10的所有约数是1,2,5,10。这个公式没有恰当的翻译,我姑且翻译为约数欧拉函数和。所以应用上述公式有:
ϕ
(
1
)
+
ϕ
(
2
)
+
ϕ
(
5
)
+
ϕ
(
10
)
=
10
\phi(1)+\phi(2)+\phi(5)+\phi(10)=10
ϕ(1)+ϕ(2)+ϕ(5)+ϕ(10)=10
验证下哈,确实没错:
ϕ
(
1
)
+
ϕ
(
2
)
+
ϕ
(
5
)
+
ϕ
(
10
)
=
1
+
1
+
4
+
4
=
10
\phi(1)+\phi(2)+\phi(5)+\phi(10)=1+1+4+4=10
ϕ(1)+ϕ(2)+ϕ(5)+ϕ(10)=1+1+4+4=10
但是明白了这个公式后,我们就可以用一种快速的算法去计算了。但是计算过程并不能让人马上明白,所以我以刚才的φ(10)为例子。细细讲一下:
∵
ϕ
(
1
)
+
ϕ
(
2
)
+
ϕ
(
5
)
+
ϕ
(
10
)
=
10
∴
ϕ
(
10
)
=
10
−
ϕ
(
1
)
−
ϕ
(
2
)
−
ϕ
(
5
)
\because \phi(1)+\phi(2)+\phi(5)+\phi(10)=10\\ \therefore \phi(10)=10-\phi(1)-\phi(2)-\phi(5)\\
∵ϕ(1)+ϕ(2)+ϕ(5)+ϕ(10)=10∴ϕ(10)=10−ϕ(1)−ϕ(2)−ϕ(5)
所以就是找到所有的因数。这个时候需要用到一种类似埃拉托色尼筛选法的算法。托色尼筛选法算法的本质是,我们不要去前面找所有的因数。而是去后面找所有的倍数减去我本身,与常规数组的向前遍历的算法稍有不同。我以求φ(10)为例子,讲解一下
第一步先初始化为n
x | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
---|---|---|---|---|---|---|---|---|---|---|---|
φ(x) | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
1后面所有的数都是1的倍数,所以全部减去φ(1),所以变成:
x | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
---|---|---|---|---|---|---|---|---|---|---|---|
φ(x) | 0 | 1 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
2后面所有2的倍数所以全部减去φ(2),所以变成:
x | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
---|---|---|---|---|---|---|---|---|---|---|---|
φ(x) | 0 | 1 | 1 | 2 | 2 | 4 | 4 | 6 | 6 | 8 | 8 |
3后面所有3的倍数所以全部减去φ(3),所以变成:
x | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
---|---|---|---|---|---|---|---|---|---|---|---|
φ(x) | 0 | 1 | 1 | 2 | 2 | 4 | 2 | 6 | 6 | 6 | 8 |
4后面所有4的倍数所以全部减去φ(4),所以变成:
x | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
---|---|---|---|---|---|---|---|---|---|---|---|
φ(x) | 0 | 1 | 1 | 2 | 2 | 4 | 2 | 6 | 4 | 6 | 8 |
5后面所有5的倍数所以全部减去φ(5),所以变成:
x | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
---|---|---|---|---|---|---|---|---|---|---|---|
φ(x) | 0 | 1 | 1 | 2 | 2 | 4 | 2 | 6 | 4 | 6 | 4 |
那么明白了算法之后,代码就容易了:
# _*_ coding:utf-8 _*_
def euler_totient(n):
phi = [i for i in range(0, n + 1)]
for i in range(1, n + 1):
# 把i后面的全部减去phi[i]
for j in range(i << 1, n + 1, i):
phi[j] -= phi[i]
return phi[n]
if __name__ == '__main__':
print(euler_totient(10))
测试结果:
4