我们在小学就学过用竖式计算两个多位数的乘法:

  这个过程简单而繁琐,没有最强大脑的普通大众通常是用计算器代替的。然而对于超大整数的乘法,计算器也未必靠得住,它还存在“溢出”一说。这就需要我们自行编写算法了。

竖式算法

  虽然对于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位的

  出处:http://www.cnblogs.com/bigmonkey

  本文以学习、研究和分享为主,如需转载,请联系本人,标明作者和出处,非商业用途!

  扫描二维码关注公众号“我是8位的”

整数的故事(4)——Karastuba算法的更多相关文章

  1. 用Java实现在【520,1314】之间生成随机整数的故事

    做一个积极的人 编码.改bug.提升自己 我有一个乐园,面向编程,春暖花开! 在未来城市工作的的程序员小木,做了一个梦,梦到自己在塔鲁姆的街道上看到一个姑娘,这个姑娘从远处走向他,脸上带着微笑.让小木 ...

  2. C语言实现整数数组的逆置算法

    读入100个整数到一个数组中,写出实现该数组进行逆置的算法. 方法一: 假设100个整数读入到数组a中,算法f1的思想是分别从数组两端依次将对应数进行交换,即a[i]与a[100 - i - 1]进行 ...

  3. png的故事:隔行扫描算法

    转载自AlloyTeam:http://www.alloyteam.com/2017/06/the-story-of-png-deinterlacing-algorithm/ 前言 前文已经讲解过如何 ...

  4. php取两个整数的最大公约数算法大全

    php计算两个整数的最大公约数常用算法 <?php//计时,返回秒function microtime_float (){ list( $usec , $sec ) = explode ( &q ...

  5. 素数算法(Prime Num Algorithm)

    素数算法(Prime Num Algorithm) 数学是科学的皇后,而素数可以说是数学的最为核心的概念之一.围绕素数产生了很多伟大的故事,最为著名莫过于哥德巴赫猜想.素数定理和黎曼猜想(有趣的是,自 ...

  6. 十大经典排序算法总结(JavaScript描述)

    前言 读者自行尝试可以想看源码戳这,博主在github建了个库,读者可以Clone下来本地尝试.此博文配合源码体验更棒哦~~~ 个人博客:Damonare的个人博客 原文地址:十大经典算法总结 这世界 ...

  7. (转)神经网络和深度学习简史(第一部分):从感知机到BP算法

    深度|神经网络和深度学习简史(第一部分):从感知机到BP算法 2016-01-23 机器之心 来自Andrey Kurenkov 作者:Andrey Kurenkov 机器之心编译出品 参与:chen ...

  8. 【转】Bresenham快速画直线算法

    一.             算法原理简介: 算法原理的详细描述及部分实现可参考: http://www.cs.helsinki.fi/group/goa/mallinnus/lines/bresen ...

  9. protocol buffer 整数序列化

    http://blog.csdn.net/csfreebird/article/details/7624807 varints用于正整数 (无符号整数) varints 是 一个很不错的技术.将一个整 ...

随机推荐

  1. 这可能是史上最全的css布局教程

    标题严格遵守了新广告法,你再不爽,我也没犯法呀!话不多说,直入正题. 所谓布局,其实包含两个含义:尺寸与定位.也就是说,所有与尺寸和定位相关的属性,都可以用来布局. 大体上,布局中会用到的有:尺寸相关 ...

  2. RabbitMQ(2) 一般介绍

    RabbitMQ 即一个消息队列,主要是用来实现应用程序的异步和解耦,同时也能起到消息缓冲,消息分发的作用. 消息中间件在互联网公司的使用中越来越多,刚才还看到新闻阿里将RocketMQ捐献给了apa ...

  3. Android24以上拍照代码

    public void takePic(){ //创建File对象,用于存储拍照后的照片 File outputImage = new File(getExternalCacheDir()," ...

  4. Linux下一个进程可以开多少线程

    这个问题,整理了一下网上的资料,结果如下: 一.ulimit -n可以查看一个进程最多可以打开多少文件描述符数: 二.一个进程最多可以产生多少线程,可用如下的方法: 32位linux系统最大内存地址4 ...

  5. Mac使用Shell(终端)SSH连接远程服务器

    先进入终端 输入:ssh -p 端口号 服务器用户名@ip (例如ssh -p 22 root@119.10.168.63)回车,到这会让你输入yes或者no来确认是否连接,输入yes回车 然后输入在 ...

  6. SQL-45 将titles_test表名修改为titles_2017。

    题目描述 将titles_test表名修改为titles_2017.CREATE TABLE IF NOT EXISTS titles_test (id int(11) not null primar ...

  7. SharePoint Framework 基于团队的开发(一)

    博客地址:http://blog.csdn.net/FoxDave SharePoint Framework是新的用来构建SharePoint自定制的开发模型,它专注于客户端开发并用热门的开源工具gu ...

  8. php源码学习——开篇

    这个系列是对php源码的学习记录.由于本人水平有限,可能并不能写的非常清晰和深入,所以,可能只适合本人阅读:) 初次接触php源码,看到陌生的文件夹和大量的文件,可能会觉得茫然无措.php-inter ...

  9. centos5&6的启动过程

    CentOS-6系统启动过程: 按下开关按钮 给服务器供电 BIOS自检操作     检查硬件是否存在异常(显示logo画面) MBR引导系统      硬盘启动系统  光驱启动系统  U盘启动系统  ...

  10. linux 查看当前系统版本号

    为避免现场未能完全安装系统,使用yum 安装版本需一致 第一种方法: [root@sky9896sky]# lsb_release -a bash:lsb_release: command not f ...