整数的故事(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 是 一个很不错的技术.将一个整 ...
随机推荐
- MVC扩展HttpHandler
扩展用来做防盗链 访问特殊后缀名的处理方式 localhost:8080/Home/index.aspx localhost:8080/Home/mao.jpg 比如 这样一个地址 ...
- ES6标准之基础
let和const命令 ES6新增let命令,用于声明变量,是块级作用域. let声明的变量不会像var声明的变量发生“变量提升”现象,所以,变量一定要在声明后使用,不然就会报错. 暂时性死区:只要块 ...
- Lambda的前世今生
先看一段代码吧 class Student{ delegate void Say(string content); public void Show() { //Lambda的前世今生 //总结:La ...
- 【转】 C++析构函数的作用和用法
转自:https://www.cnblogs.com/puyangsky/p/5319470.html 一.定义1. 作用:对象消亡时,自动被调用,用来释放对象占用的空间2.特点: (1) 名字与 ...
- vue 上传图片视频组件,可拍照选择照片,解决苹果手机拍照旋转问题
1.创建组件components > uploadImg > index.vue <template> <input type="file" name ...
- 第9天【btrfs文件系统、压缩工具及for语句、程序包管理】
btrfs文件系统管理与应用(01)_recv halt centos7: mkfs.btrfs命令: -L:指定卷标 -m:元数据 -d:指定数据存储的类型,raid1.5.10.single 实验 ...
- hdu多校第3场A.Ascending Rating
Problem A. Ascending Rating Time Limit: / MS (Java/Others) Memory Limit: / K (Java/Others) Total Sub ...
- centos安装ruby
下面开始安装Ruby 一.下载Ruby源码包 wget ftp://ftp.ruby-lang.org/pub/ruby/2.1/ruby-2.1.7.tar.gz 二.解压安装Ruby tar zx ...
- 福大软工 · BETA 版冲刺前准备(团队)
BETA 版冲刺前准备 队名:第三视角 作业链接 组长博客 应作业要求为了更加顺利地开展beta版本的冲刺,上次的alpha版本展示后,我们组对之前开发过程中存在的各种问题进行了全面的讨论,并对其进行 ...
- 第一章01:熟悉java,发展历史
1. java由来: 原公司,sun,后来被oracle\甲骨文公司收购 java,主要用于开发互联网软件,例如:QQ.迅雷.淘宝.京东
