整数的故事(4)——Karastuba算法
我们在小学就学过用竖式计算两个多位数的乘法:

这个过程简单而繁琐,没有最强大脑的普通大众通常是用计算器代替的。然而对于超大整数的乘法,计算器也未必靠得住,它还存在“溢出”一说。这就需要我们自行编写算法了。
竖式算法
虽然对于Python来说,不必太过关心整数的长度和溢出问题,但对于其它编程语言就未必了。这里我们暂且抛开语言本身的特性,只关注算法本身。假设输入的两个长整数x和y,它们的乘积将会溢出,所以需要将乘积转换成字符串,根据乘法竖式的运算规则,很容易写出下面的代码:
# 竖式法计算两个整数相乘
def multi(x, y):
x_str = str(x)
y_str = str(y)
x_len = len(x_str)
y_len = len(y_str)
z = [0] * (x_len + y_len) for i in range(x_len):
for j in range(y_len):
z[x_len - i - 1 + y_len - j - 1] += int(x_str[i]) * int(y_str[j]) for i in range(len(z) - 1):
if z[i] >= 10:
z[i + 1] += (int(z[i]) // 10)
z[i] = int(z[i]) % 10 return list2str(z) # 将z转换成字符串并删除左侧的0
def list2str(z):
result = [str(i) for i in z]
return ''.join(result[::-1]).lstrip('') # 打印运行结果
def paint(a, b):
print('{0} * {1} = {2}'.format(a, b, multi(a, b))) if __name__ == '__main__':
paint(123,321)
paint(123,456)
paint(123456789000, 987654321000)
代码中9~11行用两个循环模拟了乘法计算的过程,以123×321为例,在循环结束后z将存储下面的数据:

11行的for循环是处理进位问题。最后将列表转换为字符串,再去掉多余的0,打印结果:

Karastuba算法
竖式乘法偏向于使用蛮力,Karastuba博士在1960年提出了一个更简单的算法,其思想是把两个大整数的乘法转化为若干次小规模的乘法和少量的加法,这就是Karastuba算法。
对于两个n位的大整数x和y,可以把x和y分解成两部分:

例如:

是不是有点似成相识?没错,这实际上是利用了欧几里德算式将一个整数分解成m=qn+r的形式。现在x和y的乘积可以表示为:

这就把原来的大整数乘法变成了四次效较小规模的乘法(其中10n的运算可以通过位移高效处理)和少量加法。上式还可以更进一步:

看起来更复杂了,但是对于计算机来说,x1y1和x0y0已经计算过了,不需要再次计算。x1y0+x0y1被转换成一次乘法和少量的加法,多一个加法运算对时间复杂度没有影响,而减少一个乘法却能减少时间复杂度。对每一个乘法都进行类似的分解,反复迭代xiyi,直到其中一个乘数只有1位为止。按照这种思路可以编写新的乘法运算代码:
# karastuba算法计算两个n位的大整数乘法, x >=0, y >= 0
def karastuba(x, y, n):
if x == 0 or y == 0:
return 0
elif n == 1:
return x * y k = n // 2
x1 = x // (10 ** k)
x0 = x % (10 ** k)
y1 = y // (10 ** k)
y0 = y % (10 ** k)
z0 = karastuba(x0, y0, k) # 计算x0y0
z1 = karastuba(x1, y1, k) # 计算x1y1
z2 = karastuba((x1 + x0), (y0 + y1), k) - z1 - z0 return z1 * (10 ** n) + z2 * (10 ** k) + z0
然而运行时会发现这段代码很难生效,原因是计算时要求的环境太过理想——每次迭代时xi和yi的位数都必须相同。这就需要重新审视Karastuba算法,看看非理想状态下是如何计算的。
假设x和y分别是m位和n位的大整数,x和y可以这样分解:

反复迭代xiyi,直到其中一个乘数只有1位为止。
示例: 123×321 = ?

现在可以编写能够正确运行的大整数乘法代码:
def karastuba(x, y):
''' karastuba算法计算两个n位的大整数乘法, x >=0, y >= 0 '''
if x == 0 or y == 0:
return 0
m, n = len(str(x)), len(str(y)) # x和y的位数
if m == 1 or n == 1: # 如果x或y只有1位,直接计算结果
return x * y
m //= 2
x1, x0 = x // (10 ** m), x % (10 ** m) # 分解x
n //= 2
y1, y0 = y // (10 ** n), y % (10 ** n) # 分解y
# 迭代分解够的4个较小规模的乘法
x1y1 = karastuba(x1, y1)
x1y0 = karastuba(x1, y0)
x0y1 = karastuba(x0, y1)
x0y0 = karastuba(x0, y0)
return x1y1 * (10 ** (m + n)) + x1y0 * (10 ** m) + x0y1 * (10 ** n) + x0y0 def paint(x, y):
''' 在控制台打印karastuba(x, y)的运行结果 '''
print('{0} * {1} = {2}, ({3})'.format(x, y, karastuba(x, y), x * y)) if __name__ == '__main__':
paint(123, 321)
paint(123456789, 987456)
paint(1234567891234567, 1234567891234567)
先看看windows计算器下1234567891234567×1234567891234567的运行结果:

计算器已经无法给出精确的结果,但karastuba没有问题:

对于非10进制整数,Karastuba算法依然适用。
作者:我是8位的
整数的故事(4)——Karastuba算法的更多相关文章
- 用Java实现在【520,1314】之间生成随机整数的故事
做一个积极的人 编码.改bug.提升自己 我有一个乐园,面向编程,春暖花开! 在未来城市工作的的程序员小木,做了一个梦,梦到自己在塔鲁姆的街道上看到一个姑娘,这个姑娘从远处走向他,脸上带着微笑.让小木 ...
- C语言实现整数数组的逆置算法
读入100个整数到一个数组中,写出实现该数组进行逆置的算法. 方法一: 假设100个整数读入到数组a中,算法f1的思想是分别从数组两端依次将对应数进行交换,即a[i]与a[100 - i - 1]进行 ...
- png的故事:隔行扫描算法
转载自AlloyTeam:http://www.alloyteam.com/2017/06/the-story-of-png-deinterlacing-algorithm/ 前言 前文已经讲解过如何 ...
- php取两个整数的最大公约数算法大全
php计算两个整数的最大公约数常用算法 <?php//计时,返回秒function microtime_float (){ list( $usec , $sec ) = explode ( &q ...
- 素数算法(Prime Num Algorithm)
素数算法(Prime Num Algorithm) 数学是科学的皇后,而素数可以说是数学的最为核心的概念之一.围绕素数产生了很多伟大的故事,最为著名莫过于哥德巴赫猜想.素数定理和黎曼猜想(有趣的是,自 ...
- 十大经典排序算法总结(JavaScript描述)
前言 读者自行尝试可以想看源码戳这,博主在github建了个库,读者可以Clone下来本地尝试.此博文配合源码体验更棒哦~~~ 个人博客:Damonare的个人博客 原文地址:十大经典算法总结 这世界 ...
- (转)神经网络和深度学习简史(第一部分):从感知机到BP算法
深度|神经网络和深度学习简史(第一部分):从感知机到BP算法 2016-01-23 机器之心 来自Andrey Kurenkov 作者:Andrey Kurenkov 机器之心编译出品 参与:chen ...
- 【转】Bresenham快速画直线算法
一. 算法原理简介: 算法原理的详细描述及部分实现可参考: http://www.cs.helsinki.fi/group/goa/mallinnus/lines/bresen ...
- protocol buffer 整数序列化
http://blog.csdn.net/csfreebird/article/details/7624807 varints用于正整数 (无符号整数) varints 是 一个很不错的技术.将一个整 ...
随机推荐
- group by查询每组时间最新的一条记录
最近需要查询每组时间最新的记录 表如下:
- .NET Core / C# 开发 IOT 嵌入式设备的个人见解
https://www.cnblogs.com/whuanle/p/10589496.html
- 活动代码页437--修改windows的系统编码
1.首先查看系统编码 win+R打开运行,输入cmd回车,打开命令提示符窗口,输入chcp回车,会查询当前系统的活动代码页,它指明了当前系统使用的编码: 或者,打开cmd后,点击cmd窗口左上角图标, ...
- python的类和对象——类的静态字段番外篇
什么是静态字段 在开始之前,先上图,解释一下什么是类的静态字段(我有的时候会叫它类的静态变量,总之说的都是它.后面大多数情况可能会简称为类变量.): 我们看上面的例子,这里的money就是静态字段,首 ...
- EOS使用
公司要玩区块链,听说EOS交易快,就弄来玩玩.弄了一天终于编译成功了. Scanning dependencies of target nodeos [%] Building CXX object p ...
- IPFS初探
背景:听说IPFS=bittorrent+bitcoin+git+afs,有可能取代http,好像厉害的不行,所以要研究一下. 编译参考:https://github.com/ipfs/go-ipfs ...
- rest_framework 之视图
1. 继承ModelSerilizer,直接指定要序列化的表模型 MySerializers.py from app import models # 继承ModelSerilizer,直接指定要序列化 ...
- 同时使用n和nvm踩到的坑
平时在公司的时候,一直只使用一个node版本.这两天想多了解下node的新版本,使用tj大神的 n 包管理工具来安装node包. 安装过程中一直难以容忍过慢的包下载速度,于是根据日志的描述直接把包下载 ...
- 对TCP协议握手的理解(转)
reference:https://www.cnblogs.com/awkflf11/p/9191708.html 目录: 31.Tcp握手的一些问题? 21.Tcp三次握手及SYN攻击: 四次握手? ...
- PyCharm基本用法
1.修改字体 在file->settings下进行修改如下图: 2 修改颜色背景 在file->settings下修改,如图: 3连接git 点击file->settings,选择版 ...
