最近为面试,一直在学习各种各样的技术,但一天下来似乎没有什么成就感,相反感觉并没学到什么,今天在群里(hero)看到一个厉害的同行说到用博客记录下自己学过的知识,仔细想想颇有道理。
由于笔者才疏学浅,记录下的文字肯定有所不正或纰漏之处,本着不误人子弟的忐忑心态,如果有读者看到笔者的这些文字,能以批判的眼光,提出笔者的不妥之处,大家共同进步,小弟先在此拜谢!
今天学习《算法导论》,正好看到数论相关内容,取其中笔者理解的一些内容。
基础概念:
符号d|a("d整除a"):存在某个整数k,使得a = kd;a是d的倍数,d是a的约数;
素数和合数:如果一个整数a>1且只能被平反约数1和它自身整除,则是素数,否则为合数(0,1,所有负整数既不是素数也不是合数)。
除法定理:对于任何整数a和任何正整数n,存在唯一整数q和r,满足0<= r < n 且a = qn + r。q = a / n(向下取整)称为商,r = a mod n 称为余数
公约数与最大公约数:
d是a,b的公约数,则有对于任意整数x和y, d|a 且 d|b 蕴涵着 d | (ax + by)。
两个不同时为0的整数a与b的公约数中最大的称为最大公约数,记做gcd(a,b)。特别的,gcd(a,0) = a,gcd(0,0) = 0
定理:如果任意整数a和b不都为0,则gcd(a,b)是线性组合集{ax+by: x, y都为整数}中的最小正元素。
互质数:如果两个整数a和b只有公约数1,即gcd(a,b)=1,则a与b称为互质数。
唯一因子分解定理:合数a仅能以一种方式写成如下乘积形式: a = P1^e1 * P2^e2 * ...*Pr ^er,其中Pi为素数,P1<P2<...<Pr,且ei为正整数
最大公约数:
求两个正整数a,b的最大公约数算法。
1.因子分解求最大公约数
根据上面提到的唯一因子分解定理可得
a = P1^e1 *P2^e2 * ...*Pr ^er
b = P1^f1 *P2^f2 * ...*Pr ^fr
则 gcd(a,b) = P1^min(e1,f1) *P2^min(e2,f2) * ...*Pr ^min(er,fr)。
但目前已知最好的因子分解算法也不够理想,所以以上方法不大可能获得高效率。
2.欧几里得算法
GCD递归定理:对任意非负整数a和任意正整数b,gcd(a,b) = gcd(b, a mod b)
so,求最大公约数的递归算法的伪代码
EUCLID(a,b)
if b == 0 return a
return EUCLID(b,a mod b)
例:EUCLID(30,21) = EUCLID(21,9) = EUCLID(9,3) = EUCLID(3,0) = 3
当 a < b时,第一次递归调用就会把a,b的位置调转。因为a mod b < b,递归调用过程中b单调递减,因此必定不会无限递归下去。
好像小学学过的。。。辗转相除?!
元素的幂:
求一个数的幂对另一个数的模运算(a^b mod n),也成为模取幂。在许多素数测试程序和RSA公钥加密系统中,模取幂运算时一种很基本的运算。之所以要取模,也有大部分计算机语言的整数类型在放置a^b的值时容易溢出的原因。
采用b的二进制表示,反复平方法可以高效地解决这个问题。
MODULAR-EXPONENTIATION(a, b, n)
1 c = 0
2 d = 1
3 let <bk,bk-1,...,b0> be the binary representation of b
4 for i = k to 0
5 c = 2c
6 d = (d * d) mod n
7 if bi == 1
8 c = c + 1
9 d = (d * a) mod n
10 return d
i | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
bi | 1 | 0 | 0 | 0 | 1 | 1 | 0 | 0 | 0 | 0 |
c | 1 | 2 | 4 | 8 | 17 | 35 | 70 | 140 | 280 | 560 |
d | 7 | 49 | 157 | 526 | 160 | 241 | 298 | 166 | 67 | 1 |
这里的变量c在算法中没有用处,通过c的变化可知以下两部分在循环开始时不变
1.c的值与b的二进制表示的前缀<bk,bk-1,...,bi+1>相同
2.d = a^c mod n
以上的说明,理解起来比较费劲。笔者的理解是:
6行:d = (d * d) mod n 即是 d = (a^i * a^i) mod n = a^2i mod n,而2i即是循环1次,c左移一位
9行:d = (d * a) mod n,由于在6行时d已经变化,因此 d = (((d * d) mod n) * a) mod n,即是 d = (((a^i * a^i) mod n) * a) mod n = (a^i * a^i * a) mod n = a^(2i+1) mod n,而2i+1即是循环1次,左移一位,此二进制位数为1。
以上的算法循环顺序是从b的二进制高位开始,也可以从低位开始实现上述算法,只需要把6行和7~9行的代码调换并且稍加修改即可。
int
MODULAR-EXPONENTIATION(
int
a,
int
b,
int
n){
int
r=1;
while
(b){
if
(b&1)r=(r*a)%n;
a=(a*a)%n;
b>>=1;
}
return
r;
}
为了应用上述知识,可以做一下下面这道练习。
回文字符串(即在字符串中间位置开始两边字符对称的字符串)个数:
给定一个字符串,给定的字符串可以随意排列,求可以组成的回文字符串的个数对10^7+7的模。例如,给定字符串“caabb”,可以组成的回文字符串有“abcba”,"bacab"。
提示1:由n1个a,n2个b,。。。,nk个z组成的n(n=n1+n2+...+nk)个字符的字符串的全排列个数为n!/n1!n2!...nk!
提示2:n! 的唯一因子分解为P1^n1 *P2^n2 *...Pk^nk,其中pi的次数ni= n/Pi + n/Pi^2 + ... + n/Pi^k
来自csdn的在线编程平台——庞果英雄会的一道过往题目