2.5.1 引言
正如2.1节所说, 算法这一术语最初指的是用整数的十进制法表示的用法进行算术运算的过程。修改后能处理二进制表示的这些算法是计算机算术的基础。这些算法为理解算法这一概念及算法复杂度提供了很好的实例。因此本书将讨论这些算法。
除算术中常用的整数算法以外,还有许多涉及整数的算法,包括欧里几德算法,这是最有用的算法之一,很可能是数学中最古老的算法。我们还将描述一个算法,用于对任意基数b求正整数的b进制展开和求同余幂,在密码学中这是个重要的算法。
2.5.2 整数的表示
日常生活中都用十进制记号表示整数。例如,965用来表示 9.102+6.10+5。 不过有时用10以外的数字为基数更为方便。特别是计算机常用二进制记号(以2为基数)来做算术运算,而用八进制(基数为8)或十六进制(基数为16)记号来表示字符,如字母或字符。事实上,可以用1以外的任何正整数为基数表示整数。定理1陈述的就是这一结论。
定理1 令b为不等于1的正整数。那么如果n是个正整数,就可以唯一地表示为下面的形式:
n=ak.bk+ ak-1.bk-1+...+a1b+a0
其中k是非负整数,a0, a1,...,ak 是小于b的非负整数, ak不等于0.
本定理的证明可以在[R099]中找到。 定理1中给出的n的表示为n的b进制展开。 n的b进制展开表示为(ak.ak-1...a1a0)b。例如, (245)8 表示2.82+4.8+5=165.
二进制展开 选择2为基数就是给出整数的二进制展开。 在二进制记号中每位数字是0或1。换言之,整数的二进制是个二进制串。 计算机用二进制展开(及作为二进制展开变种的相关展开)表示整数并作整数的运算。
例如: 以(101011111)2为二项式展开的整数之十进制展开是什么?
解:我们的
(101011111)2=1*28+0*27+1*26+0*25+1*24+1*23+1*22+1*21+1*20=351
十六进制展开 16是计算机科学中使用的另一个基数。整数以16为基数的展开称为十六进制展开。这种展开要用16个不同的数字。通常使用的数字是0,1,2,3,4,5,6,7,8,9,A,B,C,D,E和F, 其中A到F表示数字对应于(十进制的)10到15。
例2 十六进制展开(2AE0B)16 的十进制展开是什么?
解: 我们有:
(2AE0B)16 =2*164+A*163+E*162+0*16+11=(175627)10
每个16进制数字可以用4个二进制位表示。例如, 可以看出(11100101)2 =(E5)16, 因为(1110)2=(E)16 而 (0101)2=(5)16 。 字节是长度为8的二进制串,所以字节可以用两个十六进制数字表示。
进制转换 现在介绍一个构造整数n的b进制展开算法。首先,用b除n得到商和余数,即:
n=b*q0+a0 , 0≤a0<b
余数a0就是n的b进制展开的最右边一位数字,下一步用b除q0得:
q0=b*q1+a1 , 0≤a1<b
可以看出a1是n的b进制展开中从右边数的第二个数字。继续这一过程,不断用商数除以b并以余数为新的进制数字。这一过程在商为0时为止。
例3 求(12345)10 的八进制展开。
解: 首先用8除12345, 得:
12345=8*1543 +1
不断用8除得商数,得
1543= 8*192+7
192=8*24+ 0 、
24=8*3+0
3=8*0+3
由于这些余数是八进制展开中的数字, 于是:
(12345)10=(30071)8
例4 求(177130)10的十六进制展开。
解:首先用16除177130, 得:
177130 = 16* 11070 + 10
不断用16商数,得:
11070= 16*691+ 14
691=16*43+3
43=16*2 +11
2=16*0 +2
由于这些余数就是(177130)10的十六进制(基数为16)展开中的数字,于是
(177130)10 =(2B3EA)16
(回忆一下,整数10,11,和14 分别对应于十六进制数字A,B和E。)
例5 求(241)10 的二进制展开。
解: 首先用2除241, 得:
241= 2* 120 +1
不断用2除商数,得:
120=2* 60 + 0
60=2*30+0
30=2*15+0
15=2*7+0
7=2*3+1
3=2*1+1
1=2*0+1
由于这些余数就是(241)10的二进制(基数为2)展开中的数字,于是(241)10=(11110001)2
算法1中给出的伪码用于计算整数n的b进制展开(ak-1...a1a0)b
算法1构造b进制展开 Procedure base b expansion (n:正整数) q:=n k:=0 while q≠0 begin ak:=⌊q mod b⌋ q:=q/b k:= k+1 end{n的b进制展开是(ak-1...a1a0)b}
|
在算法1中,q 表示不断用b去除时得到的商,初值q=n。 b进制展开的数字就是做这些除法时得到的余数,由q mod b给出。 在得到的商q=0时,算法结束。
注意 算法1可以认为是一个贪心算法。
二进制与十六进制之间的转换非常容易的,因为每个十六进制对应着一组4个二进制数字,这种对应关系如表2-3所示(未表示开头的0)。 (把证明该表的正确性在本节末尾留做习题11和12。)例6说明了这种转换。
表2-3 整数0到15的十六进制、八进制和二进制表示。
十进制 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
十六进制 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | A | B | C | D | E | F |
八进制 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |
二进制 | 0 | 1 | 10 | 11 | 100 | 101 | 110 | 111 | 1000 | 1001 | 1010 | 1011 | 1100 | 1101 | 1110 | 1111 |
例6 求(11 1110 1011 1100)2的十六进制展开和(A8D)16 的二进制展开。
解: 为了把(11 1110 1011 1100)2 转化为十六进制记号,把数字分成4个一组,必要时在最左边的一组加0.这些组是0011、1110、1011、1100, 分别对应十六进制数字的3、E、B和C。 于是
(11 1110 1011 1100)2= (3EBC)16
为了把(A8D)16 转化为二进制记号,把每个十六进制数字换成一组4个二进制数字。这些数是1010, 1000, 1101. 于是(A8D)16= (1010 1000 1101)2
2.5.3 整数运算算法
用整数的二进制展开做运算在计算机科学中分外重要。 我们将介绍对两个表示为二进制展开的整数做加法和乘法的算法。 还要以使用的位运算的实际次数来分析这些算法的复杂度。在整个讨论中假定a和b的二进制展开为:
a=(an-1an-2···a1·a0)2, b=(bn-1bn-2···b1b0)2
从而a 和b各有n个二进制位(必要时,让其中一个的开头加几位0)。
考虑两个二进制记号表示的整数相加的问题。 做加法的过程可以通常借助纸笔做加法的办法来设计,也就是对二进制的相加,有进位时再加上进位,这样来计算两个整数的和。现在来详细描述这个过程。
要把a和b相加,首先要把最右边的数字相加。这样可得:
a0+b0=c0+s0
其中,s0是a+b的二进制展开中最右边的一位数字,而c0是进位, c0为0或1。然后把下一对二进制及进位相加,
a1+b1+c0=c0*2+s1
其中s1是a+b的二进制展开中最右边的一位(从右算起)数字,而c1为进位。 继续进行这一过程,把两个二进制展开中对应的二进制及进位相加,给出a+b的二进制展开中从右算起的下一位数字。最后把an-1,bn-1
和cn-2 相加得 cn-1*2+sn-1 . a +b 的首位数字是Sn =cn-1。 这一过程产生a 与b之和的二进制展开,即a+b= (SnSn-1···S1S0)2.
例7 把 a=(1110)2 和 b= (1011)2 相加。
解: 按照算法中规定的步聚,首先注意到:
a0 +b0 = 0+1= 0*2+ 1
所以c0=0 而s0=1。 然后,因为
a1+b1+c0=1+1+0=1*2+0
所以c1=1而s1=0.继续做下去,
a2+b2+c1= 1+0+1=1*2+0
于是c2=1而s2=0. 最后由于
a3+b3+c2=1+1+1=1*2+1
从而c3=1 且 s3=1. 这表明 s4 =c4=1. 因此 s=a + b =(11001)2 相加的过程如图 2-6所示。
1 | 1 | 1 | ||
1 | 1 | 1 | 0 | |
1 | 0 | 1 | 1 | |
1 | 1 | 0 | 0 | 1 |
图2-6 (1110)2和(1011)2相加
加法的算法可用伪代码描述如下。
算法2 整数相加 procedure add(a, b; 正整数) {a 和 b 的二进制展开分别是=(an-1an-2···a1·a0)2,=(bn-1bn-2···b1b0)2} c:=0 for j=: 0 to n-1 begin d:=⌊(aj+bj+c)/2⌋ sj:=aj+bj+c-2d c:=d end sn:=c {和数的二进制展开是(snsn-1···s0)2} |
下面分析算法2使用的二进制相加的次数。
例8 两个二进制展开中有n个(或少于n个)二进制位的整数相加,算法2需要多少次二进制位加法。
解 两个整数相加是相继对二进制位相加,再加上进位完成的。把两个二进制位及进位相加需要3次或少于3次二进制位加法。 因此需要的二进制位加法的总数少于二进制展开中位数的3倍。从而算法2把两个n位整数相加需要的二进制位加法次数是O(n).
下面考虑两个n位整数a 和 b 的乘法,传统的算法(用纸笔作算法)是这样的。根据分配律看出:
ab= a(b020+b121+···+bn-12n-1) = a(b020)+a(b121)+···+a(bn-12n-1)
可以用这一等式计算ab. 首先注意在bj=1时, abj=a; 而bj=0时, abj=0 , 每当用2乘一项时,结果都是把这一项的二进制展开向左移一位并在尾部加止一个0. 因此可以把abj 的二进制展开向左移位j位, 再在尾部加上j 个0来计算(abj)2j . 最后,把n个整数 abj2j , j = 0,1,2,···,n-1, 相加就得到 ab.
这个乘法过程可用伪代码描述成算法3 。
算法3 整数相乘 Procedure multiply (a , b :正整数) {a 和 b 的二进制展开分别是=(an-1an-2···a1·a0)2,=(bn-1bn-2···b1b0)2} for j:= 0 to n-1 begin if bj=1 then cj = a shiftedj places else cj := 0 end {c0, c1, ··· , cn-1 是部分乘积 } p:= 0 for j:= 0 to n-1 p:=p+cj {p是 ab的值}。 |
例9 解释了怎样使用这一算法:
例9 求 a =(110)2 和 b = (101)2 的乘积。
解: 首先注意:
a b0 20 = (110)2 *1*20 =(110)2
a b1 21 = (110)2 *0*21=(0000)2
及
a b2 22= (110)2 * 1* 22 = (11000)2
为求乘积, 把(110)2、(0000)2、(11000)2 相加, 完成这些加法(用算法2,必要时首位加0)即得 ab=(11110)2 . 这一过程如图2-7所示。
1 | 1 | 0 | ||
1 | 0 | 1 | ||
1 | 1 | 0 | ||
0 | 0 | 0 | ||
1 | 1 | 0 | ||
下面判断算法3做乘法时使用的二进制位相加次数和移位数。
例10 用算法3计算a 和b 的乘积需用多少次二进制位加法和移位。
解: 算法3计算a和b之乘积的办法是把部分乘积 C0, C1,C2,··· , Cn-1 相加。 当bj = 1 时, 部分积cj 的计算是是把a的二进制展开移j 位。 当bj = 0 时, 因为cj =0, 所以需要移位。于是,为求出所有n个整数 abj2j,
j=0,1,2,···,n-1, 需要至多:
0+1+2+···+n -1
次移位。 因此根据2.2节例4, 需要移位次数是O(n2).
要把 abj 从j=0 到 j=n-1, 需要做一个n 位整数、一个(n+1)位整数··· ··· 和一个2n位整数的加法。从例8知道, 这些加法都需要O(n) 次二进制位相加。 因此完成所有n个数的加法需要O(n2) 次加法。
令人吃惊的是, 还有比传统的整数算法更有效的算法。其中一个算法是使用O(n1.585) 次位运算来完成n 位数的乘法, 将在6.3 节介绍。
给定整数a和d, d>0, 可以用算法4求q=a div d 和 r = a mod d . 在这个算法中, 当a 为正数时, 就从a 中尽能多次减去d, 直到剩下的值小于d. 减法的次数就是商而减剩下的值就是余数。 算法4也能处理a为负数的情况。先求当|a| 除以d 的商q 和余数r; 当r>0时, 再用这些结果求出当a除以d 时用的商-(q+1) 和余数d-r。 具体过程留给读者证明(练习55): 假设a>d, 该算法用O(q loga)次运算。
算法 4 计算div 和mod procedure division algorithm (a: 整数,d:正整数) q:= 0 r:=|a| while r ≧d begin r:= r-d q=q+1 end if a<0 且 r>0 then begin r:=d-r q:=-(q+1) end {q=a div d 是商, r = a mod d 是余数} |
当正整数a除以正整数d时,有比算法4更有效的算法能确定商 q = a div d 和 余数 r = a mod d (详情见KN98). 这些算法需要O(loga ·logb ) 次位运算。 如果a 和 d 的二进制展开都不超过n 位, 那么loga·logd 可换成n2 . 这意味着需要O(n2) 次位运算来求a除以d的商和余数。
2.5.4 同余幂
在密码学中重要的是有效地求bn mod m, 其中 b , n, m, 都是大整数。先计算bn, 再求 bn 除以m的余数,这是不可行的,因为bn是非常大的数。可行的是一种利用指数n的二进制展开(比如 n= (ak-1 ··· a1· a0)2) 的算法。 这个算法依次求 b mod m, b2 mod m, b4 mod m, ··· , b(2k-1) mod m, 把其中 aj = 1 的那些项 b2j mod m 乘起来,在每次乘法后求乘积除以m余数。这个算法的伪码如算法5所示。
算法5 同余幂 procedure modular exponentiation (b: 整数, n = ((ak-1 ··· a1· a0)2), m : 正整数 x :=1 power: = b mod m for i := 0 to k-1 begin if ai = 1 then x: = (x·power) mod m power := (power ·power) mod m end {x 等于 bn mod m} |