欧几里得算法
要计算两个整数a与b的最大公因数,先令
r
−
1
=
a
且
r
0
=
b
r_{-1}=a且r_0=b
r−1=a且r0=b,然后计算相继的商和余数
r
i
−
1
=
q
i
+
1
∗
r
i
+
r
i
+
1
r_{i-1}=q_{i+1}*r_i+r_{i+1}
ri−1=qi+1∗ri+ri+1
直到某余数
r
n
+
1
r_{n+1}
rn+1为0。最后的非零余数
r
n
r_n
rn就是a与b的最大公约数。
分两步证明算法的合理性
①
r
n
r_n
rn是a,b的公约数
②
r
n
r_n
rn是a,b的最大公约数
证明:
①若我们把n个式子都写出来,可以从下往上推导
从最后一个式子可以看出,
r
n
r_n
rn整除
r
n
−
1
r_{n-1}
rn−1
然后从倒数第二个式子
r
n
−
2
=
q
n
∗
∗
r
n
−
1
+
r
n
r_{n-2}=q_n**r_{n-1}+r_n
rn−2=qn∗∗rn−1+rn又可以推出
r
n
∣
r
n
−
1
同
时
r
n
∣
r
n
r_n\vert r_{n-1}同时r_n\vert r_n
rn∣rn−1同时rn∣rn,所以
r
n
∣
右
式
r_n\vert 右式
rn∣右式
从而
r
n
∣
r
n
−
2
r_n\vert r_{n-2}
rn∣rn−2
类似这样的推导最后可以得出
r
n
∣
a
且
r
n
∣
b
r_n\vert a且r_n\vert b
rn∣a且rn∣b,即
r
n
r_n
rn是a和b的公因数
②从上往下推
设d为a和b的最大公因数
那么由第一个式子可得
d
∣
r
1
d\vert r_1
d∣r1
再结合第二个式子又可得
d
∣
r
2
d\vert r_2
d∣r2
。。。一直到第n个式子
可得
d
∣
r
n
d\vert r_n
d∣rn
综合①②,
r
n
r_n
rn既是a和b的公因数,又能被a和b的最大公因数整除
所以
r
n
r_n
rn就是a和b的最大公因数
上代码hhh(毕竟看这本书最初就是为了编程)
int gcd(int a,int b)
{
return b==0?a:gcd(b,a%b); //不用判断a,b大小
}
线性方程与最大公因数(扩展欧几里得算法)
对于任意两个整数a和b,考察ax+by的取值
可以发现形如
a
x
+
b
y
ax+by
ax+by的最小正整数等于gcd(a,b)
证明也很容易,
g
c
d
(
a
,
b
)
∣
a
x
+
b
y
gcd(a,b)\vert ax+by
gcd(a,b)∣ax+by 所以若
a
x
+
b
y
=
d
≥
0
ax+by=d\ge0
ax+by=d≥0那么一定d最小一定取gcd(a,b)
接下来考虑如何求解方程 a x + b y = g c d ( a , b ) ax+by=gcd(a,b) ax+by=gcd(a,b)
先举个具体的例子,试解
22
x
+
60
y
=
g
c
d
(
22
,
60
)
22x+60y = gcd(22,60)
22x+60y=gcd(22,60)
自然首先要通过欧几里得算法求最大公因数
60
=
2
∗
22
+
16
60=2*22+16
60=2∗22+16
22
=
1
∗
16
+
6
22=1*16+6
22=1∗16+6
16
=
2
∗
6
+
4
16=2*6+4
16=2∗6+4
6
=
1
∗
4
+
2
6=1*4+2
6=1∗4+2
4
=
2
∗
2
+
0
4=2*2+0
4=2∗2+0
这表明gcd(22,60)=2,这是一个无需求助于欧几里得算法也显而易见的事实。
然而,用欧几里得算法计算很关键。
首先,将第一个式子改写为
16
=
a
−
2
b
16=a-2b
16=a−2b,其中a=60,b=22
接着用这个值去替换第二个式子中的16可得
b
=
1
∗
16
+
6
=
1
∗
(
a
−
2
b
)
+
6
b=1*16+6=1*(a-2b)+6
b=1∗16+6=1∗(a−2b)+6
然后再把6移到左边整理得
6
=
−
a
+
3
b
6=-a+3b
6=−a+3b
我们又可以把这个值代入下一个式子,并重复上述操作得到
4
=
3
a
−
8
b
4=3a-8b
4=3a−8b
最后可以得到
2
=
−
4
a
+
11
b
2=-4a+11b
2=−4a+11b
可以发现,我们在运用欧几里得算法的同时,把那个线性方程解了出来
用抽象点的语言描述就有如下过程
a
=
b
∗
q
1
+
r
1
→
r
1
=
a
−
b
∗
q
1
a=b*q_1+r_1\;\to\; r_1=a-b*q_1
a=b∗q1+r1→r1=a−b∗q1
b
=
r
1
∗
q
2
+
r
2
→
r
2
=
b
−
r
1
∗
q
1
b=r_1*q_2+r_2\;\to\; r2=b-r_1*q_1
b=r1∗q2+r2→r2=b−r1∗q1
…
r
n
−
2
=
r
n
−
1
∗
q
n
−
1
+
r
n
→
r
n
=
r
n
−
2
−
r
n
−
1
∗
q
n
−
1
r_{n-2}=r_{n-1}*q_{n-1}+r_n\;\to\;r_n=r_{n-2}-r_{n-1}*q_{n-1}
rn−2=rn−1∗qn−1+rn→rn=rn−2−rn−1∗qn−1
显然最后的
r
n
r_n
rn可以是一个仅关于a和b的表达式,这样我们就解出了线性方程组
a
x
+
b
y
=
g
c
d
(
a
,
b
)
ax+by=gcd(a,b)
ax+by=gcd(a,b)
那么如何用程序解决呢
可以考虑递归的思想
求解
a
x
+
b
y
=
g
c
d
(
a
,
b
)
ax+by=gcd(a,b)
ax+by=gcd(a,b)
也就是求解
b
x
′
+
(
a
−
[
a
b
]
∗
b
)
y
′
=
g
c
d
(
a
,
b
)
bx'+(a-[\frac ab]*b)y'=gcd(a,b)
bx′+(a−[ba]∗b)y′=gcd(a,b)
展开来就是
a
y
′
+
b
(
x
′
−
[
a
b
]
y
′
)
=
g
c
d
(
a
,
b
)
ay'+b(x'-[\frac ab]y')=gcd(a,b)
ay′+b(x′−[ba]y′)=gcd(a,b)
对比得到
{
x
=
y
′
y
=
x
′
−
[
a
b
]
y
′
\left\{ \begin {aligned} x&=&y'\\ y&=&x'-[\frac ab]y'\\ \end {aligned} \right.
⎩⎨⎧xy==y′x′−[ba]y′
那么这就是递归的链条
而递归的基例也很明显啦
r
n
∗
1
+
0
=
g
c
d
(
a
,
b
)
r_n*1 +0=gcd(a,b)
rn∗1+0=gcd(a,b) 也就是
x
=
1
,
y
=
0
x=1,y=0
x=1,y=0
于是我们就有如下代码
int extgcd(int a,int b,int &x,int &y) //注意x和y要传引用
{
int d;
if(b==0) {x=1,y=0,d=a;}
else
{
d = extgcd(b,a%b,y,x); //交换x,y的位置,那么返回之后x=y',y=x'
y-=a/b*x;
}
return d;
}
会解 a x + b y = g c d ( a , b ) ax+by=gcd(a,b) ax+by=gcd(a,b)之后我们就可讨论,这个方程有多少解,以及如何表述所有解得问题
我们从互素的a和b开始,即让
g
c
d
(
a
,
b
)
=
1
gcd(a,b)=1
gcd(a,b)=1,假设
(
x
1
,
y
1
)
(x_1,y_1)
(x1,y1)是方程
a
x
+
b
y
=
1
ax+by=1
ax+by=1的一个解。
我们可以通过
x
1
减
去
b
的
倍
数
和
y
1
加
上
a
的
相
同
倍
数
x_1减去b的倍数和y_1加上a的相同倍数
x1减去b的倍数和y1加上a的相同倍数,即可得到其他解。
换句话说,对任何整数k,我们得到新解
(
x
1
+
k
b
,
y
1
−
k
a
)
(x_1+kb,y_1-ka)
(x1+kb,y1−ka)
这一事实通过计算可以很容易得到证明
另外,我们可以证明这种方法给出了所有解。
假设
(
x
1
,
y
1
)
(x_1,y_1)
(x1,y1)与
(
x
2
,
y
2
)
(x_2,y_2)
(x2,y2)是方程
a
x
+
b
y
=
1
ax+by=1
ax+by=1的两个解
即
a
x
1
+
b
y
1
=
1
且
a
x
2
+
b
y
2
=
1
ax_1+by_1=1\;且\;ax_2+by_2=1
ax1+by1=1且ax2+by2=1
我们用
y
2
y_2
y2乘以第一个方程,用
y
1
y_1
y1乘以第二个方程再相见就消去了b
即
a
x
1
y
2
−
a
x
2
y
1
=
y
2
−
y
1
ax_1y_2-ax_2y_1=y_2-y_1
ax1y2−ax2y1=y2−y1
同理我们可以得到
b
x
2
y
1
−
b
x
1
y
2
=
x
2
−
x
1
bx_2y_1-bx_1y_2=x_2-x_1
bx2y1−bx1y2=x2−x1
我们设
k
=
x
2
y
1
−
x
1
y
2
k=x_2y_1-x_1y_2
k=x2y1−x1y2
则有
x
2
=
x
1
+
k
b
,
y
2
=
y
1
−
k
a
x_2=x_1+kb,y_2=y_1-ka
x2=x1+kb,y2=y1−ka
这就说明所有解都可以用上述的方式表示
那么我们再考虑a和b不互素的情况,显然我们可以将其转化为互素
即
a
g
x
+
b
g
y
=
1
\frac ag x+\frac bg y=1
gax+gby=1 其中
g
=
g
c
d
(
a
,
b
)
g=gcd(a,b)
g=gcd(a,b)
这样最后我们就得到了线性方程定理
线性方程定理:设a与b是非零整数,g=gcd(a,b)。方程 a x + b y = g ax+by=g ax+by=g总是有一个整数解 ( x 1 , y 1 ) (x_1,y_1) (x1,y1),它可由前面叙述的欧几里得算法得到。则方程的每一个解可由 ( x 1 + k ∗ b g , y 1 − k ∗ a g ) (x_1+k*\frac bg,y_1-k*\frac ag) (x1+k∗gb,y1−k∗ga)得到,其中k可为任意整数。
算数基本定理
引理:令p是素数,假设p整除乘积ab,则p整除a或p整除b
证明:
假设p不整除a,则p一定整除b
因为
g
c
d
(
a
,
p
)
∣
p
gcd(a,p)|p
gcd(a,p)∣p,所以是1或p,又p不整除a,所以
g
c
d
(
a
,
p
)
gcd(a,p)
gcd(a,p)一定等于1
那么由线性方程定理
p
x
+
a
y
=
1
px+ay=1
px+ay=1一定有整数解x与y
两边同成b得
p
b
x
+
a
b
y
=
b
pbx+aby=b
pbx+aby=b
显
然
p
∣
p
b
x
,
又
由
题
p
∣
a
b
y
,
所
以
p
∣
b
,
证
毕
显然p|pbx,又由题p|aby,所以p|b,证毕
显然p∣pbx,又由题p∣aby,所以p∣b,证毕
引理指出如果素数p整除乘积ab,则它必整除其中一个因数。
注意合数不具有这样的性质
素数整除性质:假设素数p整除乘积 a 1 a 2 . . . a r a_1a_2...a_r a1a2...ar,则p整除 a 1 , a 2 . . . . a r a_1,a_2....a_r a1,a2....ar中至少一个因数
证明:
由引理迭代证明即可:
若p不整除
a
1
a_1
a1,则p一定整除
a
2
a
3
.
.
.
a
r
a_2a_3...a_r
a2a3...ar
。。。
算数基本定理:每个整数 n ≥ 2 n\geq2 n≥2可唯一分解成素数乘积 n = p 1 p 2 . . . p r n=p_1p_2...p_r n=p1p2...pr
证明:
算数基本定理实际上包含两个断言
断言1 数n可以以某种方式分解成素数的乘积
断言2 仅有一种这样的因数分解(不考虑因数重排)
先证断言1,n=2显然成立,假设
n
≤
N
n\leq N
n≤N成立,考虑n=N+1
若N+1已经是素数,则证毕
否则若是合数,则由合数定义
N
+
1
=
n
1
n
2
且
2
≤
n
1
,
n
2
≤
N
N+1=n_1n_2且2\leq n1,n2 \leq N
N+1=n1n2且2≤n1,n2≤N,由假设知n1和n2可被分解,即
n
1
=
p
1
p
2
.
.
.
p
r
与
n
2
=
q
1
q
2
.
.
.
q
s
n_1=p_1p_2...p_r\;与\;n2=q_1q_2...q_s
n1=p1p2...pr与n2=q1q2...qs
乘起来得
N
+
1
=
n
1
n
2
=
p
1
p
2
.
.
.
p
r
q
1
q
2
.
.
.
q
s
N+1=n_1n_2=p_1p_2...p_rq_1q_2...q_s
N+1=n1n2=p1p2...prq1q2...qs
所以N+1也能被分解,证毕
在证断言2,假设能将n分解成两种形式的乘积,即
n
=
p
1
p
2
.
.
.
p
r
=
q
1
q
2
.
.
.
q
s
n=p_1p_2...p_r=q_1q_2...q_s
n=p1p2...pr=q1q2...qs
首先考虑
p
1
∣
n
p_1|n
p1∣n,所以
p
1
∣
q
1
q
2
.
.
.
q
s
p1|q_1q_2...q_s
p1∣q1q2...qs,由素数整除性质p1必整除
q
i
q_i
qi中的至少一个,所以可以重排
q
i
q_i
qi使得
p
1
∣
q
1
p_1|q_1
p1∣q1,同时
q
1
q_1
q1也是质数,所以必有
p
1
=
q
1
p_1=q_1
p1=q1
一直重复上述操作,则最后有
p
i
=
q
i
p_i=q_i
pi=qi
证毕
综上,算数基本定理得到了证明
那么如何去分解一个数n呢?
最简单的办法就是用小于等于
n
\sqrt n
n的整数试除n
显然这是一个效率非常低的过程,对于像
n
=
1
0
128
+
1
n=10^{128}+1
n=10128+1这样的数,如果n最终是素数,则需要检查
n
≈
1
0
64
\sqrt n \approx 10^{64}
n≈1064个可能的因数才会停下来。
假设每秒钟能验证
1000000000
1000 000 000
1000000000(十亿)个可能的因数,将耗时大约
3
∗
1
0
48
3* 10^{48}
3∗1048年!
这就产生了下述两个紧密相关的问题:
问题1 如何分辨已知数n是素数还是合数
问题2 如果n是合数,如何将它分解成素数的乘积
将在后面给出部分解答