分治算法——Karastsuba算法

分治(Divide and Conquer)算法:问题可以分解为子问题,每个问题是可以独立的解决的,从子问题的解可以构建原问题。

Divide:中间分、随机分、奇偶分等,将问题分解成独立的子问题

Conquer:子问题的解可以单独解决,从子问题的解构建原问题最终的解

Combine:每一步将子问题产生的解进行合并得到最终的解,合并的复杂度影响最终的算法时间复杂度

Karatsuba算法是在普通乘法算法的基础上进行的提升,使得最终的复杂度从O(n^2)变为了O(n^1.585),基本思想是将原问题的规模每次减小一般,并且每次解决三个子问题:

X =  Xl*2n/2 + Xr    [Xl 左侧n/2位数  Xr 右侧n/2位数]
Y =  Yl*2n/2 + Yr    [Yl 左侧n/2位数  Yr 右侧n/2位数] 
XY = (Xl*2n/2 + Xr)(Yl*2n/2 + Yr)
   = 2n XlYl + 2n/2(XlYr + XrYl) + XrYr
XY = 2n XlYl + 2n/2 * [(Xl + Xr)(Yl + Yr) - XlYl - XrYr] + XrYr
XY = 22ceil(n/2) XlYl + 2ceil(n/2) * [(Xl + Xr)(Yl + Yr) - XlYl - XrYr] + XrYr

从而得到最终的算法时间复杂度为T(n) = 3T(n/2) + O(n),得到T(n) = O(n^1.585)。算法的伪代码如下:

karatsuba(num1, num2)
  if (num1 < 10) or (num2 < 10)
   return num1*num2
  /* calculates the size of the numbers */
  m = max(size_base10(num1), size_base10(num2))
  m2 = m/2
  /* split the digit sequences about the middle */
  high1, low1 = split_at(num1, m2)
  high2, low2 = split_at(num2, m2)
  /* 3 calls made to numbers approximately half the size */
  z0 = karatsuba(low1,low2)
  z1 = karatsuba((low1+high1),(low2+high2))
  z2 = karatsuba(high1,high2)
  return (z2*10^(2*m2))+((z1-z2-z0)*10^(m2))+(z0)

下面是使用C++具体实现的过程,如果直接使用整数类型实现,可能会发生溢出,因此使用输入的字符串表示,实际运算的过程将字符串转换为数组进行加、减、乘操作。先看最终的算法实现:

string Multiplicate(string x, string y)
{
	int len = GetSameSize(x, y);
	if (len == 0) return 0;
	if (len == 1) return MultiplyString(x, y);
	int p = len % 2 == 0 ? len / 2 : len / 2 + 1;

	string Xh = x.substr(0, len / 2);
	string Yh = y.substr(0, len / 2);
	string Xl = x.substr(len / 2);
	string Yl = y.substr(len / 2);

	string P1 = Multiplicate(Xh, Yh);
	string P2 = Multiplicate(Xl, Yl);
	string P3 = Multiplicate(AddString(Xh, Xl), AddString(Yh, Yl));

	return
		AddString(
			AddString(
				MultiplyPower(P1, 2 * p),
				MultiplyPower(MinusString(MinusString(P3, P1), P2), p)
				), P2
		);
}

上述就是按照伪代码进行实现,但是使用了字符串的数字运算操作,包括字符串与数组的转换,数组加、减、乘,具体实现如下:

void StringToArray(string a, int *arr)
{
	int n = a.size();
	for(int i = 0; i < n; i++)
		arr[n - i - 1] = a.at(i) - '0';
}
void ArrayToString(int *arr, int len, string & a)
{
	for(int i = 0; i < len; i++)
		a += '0' + arr[len - i - 1];
}
string DelPreZero(string a)
{
	int zeros = 0;
	for (int i = 0; i < a.size(); i++)
		if (a.at(i) == '0') zeros++;
		else break;
	if (zeros == a.size()) return "0";
	return a.substr(zeros);
}
void MultiplyArray(int a[], int la, int b[], int lb, int *arr)
{
	int i;
	for (i = 0; i < la; i++)
		for (int j = 0; j < lb; j++)
			arr[i + j] += a[i] * b[j];
	for (i = 0; i < la + lb - 1; i++)
	{
		arr[i + 1] += arr[i] / 10;
		arr[i] = arr[i] % 10;
	}
}
void AddArray(int a[], int la, int b[], int lb, int *arr)
{
	int i;
	int len = la > lb ? lb : la;
	for (i = 0; i < len; i++)
		arr[i] += a[i] + b[i];
	if (la > lb)
	{
		for (i = lb; i < la; i++)
			arr[i] = a[i];
		for (i = 0; i < la; i++)
		{
			arr[i + 1] += arr[i] / 10;
			arr[i] = arr[i] % 10;
		}
	}
	else
	{
		for (i = la; i < lb; i++)
			arr[i] = b[i];
		for (i = 0; i < lb; i++)
		{
			arr[i + 1] += arr[i] / 10;
			arr[i] = arr[i] % 10;
		}
	}
}
void MinusArray(int a[], int la, int b[], int lb, int *arr) //a must be bigger than b
{
	int i;
	for (i = 0; i < lb; i++)
		arr[i] = a[i] - b[i];
	for (i = lb; i < la; i++)
		arr[i] = a[i];
	for (i = 0; i < la - 1; i++)
	{
		if (arr[i] < 0)
		{
			arr[i + 1]--;
			arr[i] = 10 + arr[i];
		}
	}
}
string MultiplyString(string a, string b)
{
	int m = a.size(), n = b.size();
	int *arrA = new int[m];
	int *arrB = new int[n];
	StringToArray(a, arrA);
	StringToArray(b, arrB);

	int *arrC = new int[m + n];
	for(int i = 0; i < n + m; i++)	arrC[i] = 0;

	string rst;
	MultiplyArray(arrA, m, arrB, n, arrC);
	ArrayToString(arrC, m + n, rst);

	delete []arrA;
	delete []arrB;
	delete []arrC;
	return DelPreZero(rst);
}

string AddString(string a, string b)
{
	int m = a.size(), n = b.size();
	int *arrA = new int[m];
	int *arrB = new int[n];
	StringToArray(a, arrA);
	StringToArray(b, arrB);

	int i, len = m > n ? m : n;
	int *arrC = new int[len + 1];
	for(i = 0; i < len + 1; i++) arrC[i] = 0;
	AddArray(arrA, m, arrB, n, arrC);

	string rst;
	ArrayToString(arrC, len + 1, rst);

	delete []arrA;
	delete []arrB;
	delete []arrC;
	return DelPreZero(rst);
}

string MultiplyPower(string a, int len)
{
	for(int i = 0; i < len; i++)
		a += '0';

	return DelPreZero(a);
}

string MinusString(string a, string b)
{
	int m = a.size(), n = b.size();
	int *arrA = new int[m];
	int *arrB = new int[n];
	StringToArray(a, arrA);
	StringToArray(b, arrB);

	string rst;
	int i, len = m > n ? m : n;
	int *arrC = new int[len];
	for(i = 0; i < len; i++) arrC[i] = 0;

	MinusArray(arrA, m, arrB, n, arrC);
	ArrayToString(arrC, len, rst);

	delete []arrA;
	delete []arrB;
	delete []arrC;
	return DelPreZero(rst);
}

主要是涉及到字符串与数组的转换中字符串在数字中是逆序的,进行数组运算时方便,同时对于数组间的减法,只支持a 大于b的减法,如果是a 小于b可以用b减去a后再取反即可。还有就是对数组的动态空间申请后,需要及时释放。

参考:

1.http://www.geeksforgeeks.org/divide-and-conquer-set-2-karatsuba-algorithm-for-fast-multiplication/

2.http://en.wikipedia.org/wiki/Karatsuba_algorithm#Pseudo_Code_Implementation

时间: 2024-11-03 02:48:46

分治算法——Karastsuba算法的相关文章

分治策略(2)&mdash;&mdash;算法导论(4)

1. 引言     这一篇博文首先会介绍基于分治策略的矩阵乘法的Strassen算法,然后会给出几种求解递归式的方法.   2. 矩阵乘法的Strassen算法 (1) 普通矩阵乘法算法     矩阵乘法的基本算法的计算规则是:         若A=(aij)和B=(bij)是n×n的方阵(i,j = 1,2,3...),则C = A · B中的元素Cij为:     下面给出Java实现代码: public static void main(String[] args) { int[][]

hdu 4858 项目管理 图分治 (复合算法)

hdu 4858 项目管理 题意:给n(<=100000)个点,m条边( <=n+10),可能有重边,每个点有个值val,初识为0. 2种操作. 操作1:点x的值,加addx. 操作2:输出x点的邻点的val和. 分析:简单的优化操作1或操作2是不行的. 法一:针对点的度将图中点分为两类点.对于度大于sqrt (n)的点为重点,对于小于等于sqrt(n)的点为轻点. 重点的个数小于sqrt(n)个.针对重点和轻点分别处理. 法二:也可考虑每个点,将其邻点分类.大于该点度的点分为一类,等于该点的

九章算法 基础算法 强化算法 系统设计 大数据 安卓 leetcode 高清视频

leetcode 直播视频讲座录像 九章算法视频录像,PPT 算法班,算法强化班,Java入门与基础算法班,big data项目实战班,Andriod项目实战班 九章算法下载 九章算法面试 九章算法leetcode 九章算法答案 九章算法mitbbs 九章算法班 九章算法ppt 九章算法录像 九章算法培训 九章算法微博 leetcode 视频 九章算法偷录 算法培训 算法班课程大纲: 1 从strStr谈面试技巧与Coding Style(免费试听) 2 二分搜索与旋转排序数组 Binary S

最短路径算法-Dijkstra算法的应用之单词转换(词梯问题)

一,问题描述 在英文单词表中,有一些单词非常相似,它们可以通过只变换一个字符而得到另一个单词.比如:hive-->five:wine-->line:line-->nine:nine-->mine..... 那么,就存在这样一个问题:给定一个单词作为起始单词(相当于图的源点),给定另一个单词作为终点,求从起点单词经过的最少变换(每次变换只会变换一个字符),变成终点单词. 这个问题,其实就是最短路径问题. 由于最短路径问题中,求解源点到终点的最短路径与求解源点到图中所有顶点的最短路径复

【啊哈!算法】算法7:Dijkstra最短路算法

上周我们介绍了神奇的只有五行的Floyd最短路算法,它可以方便的求得任意两点的最短路径,这称为“多源最短路”.本周来来介绍指定一个点(源点)到其余各个顶点的最短路径,也叫做“单源最短路径”.例如求下图中的1号顶点到2.3.4.5.6号顶点的最短路径. <ignore_js_op> 与Floyd-Warshall算法一样这里仍然使用二维数组e来存储顶点之间边的关系,初始值如下. <ignore_js_op> 我们还需要用一个一维数组dis来存储1号顶点到其余各个顶点的初始路程,如下.

【啊哈!算法】算法9:开启树之旅

这是什么?是一个图?不对,确切的说这是一棵树.这哪里像树呢?不要着急我们来变换一下. 是不是很像一棵倒挂的树,也就是说它是根朝上,而叶子朝下的.不像?哈哈,看完下面这幅图你就会觉得像啦. 你可能会问:树和图有什么区别?这个称之为树的东西貌似和无向图差不多嘛.不要着急,继续往下看.树其实就是不包含回路的连通无向图.你可能还是无法理解这其中的差异,举个例子,如下.          上面这个例子中左边的是一棵树,而右边的是一个图.因为左边的没有回路,而右边的存在1->2->5->3->

【啊哈!算法】算法10:二叉树

二叉树是一种特殊的树.二叉树的特点是每个结点最多有两个儿子,左边的叫做左儿子,右边的叫做右儿子,或者说每个结点最多有两棵子树.更加严格的递归定义是:二叉树要么为空,要么由根结点.左子树和右子树组成,而左子树和右子树分别是一棵二叉树. 下面这棵树就是一棵二叉树. 二叉树的使用范围最广,一棵多叉树也可以转化为二叉树,因此我们将着重讲解二叉树. 二叉树中还有连两种特殊的二叉树叫做满二叉树和完全二叉树.如果二叉树中每个内部结点都有两个儿子,这样的二叉树叫做满二叉树.或者说满二叉树所有的叶结点都有同样的深

[算法]有趣算法合辑[31-40]

题目31:八进制转换为十进制 1.程序分析: 2.程序源代码: main() { char *p,s[6];int n; p=s; gets(p); n=0; while(*(p)!='\0') {n=n*8+*p-'0'; p++;} printf("%d",n); } 题目32:求0-7所能组成的奇数个数. 1.程序分析: 2.程序源代码: main() { long sum=4,s=4; int j; for(j=2;j<=8;j++)/*j is place of num

【坐在马桶上看算法】算法9:开启“树”之旅

我们先来看一个例子. 这是什么?是一个图?不对,确切的说这是一棵树.这哪里像树呢?不要着急我们来变换一下. 是不是很像一棵倒挂的树,也就是说它是根朝上,而叶子朝下的.不像?哈哈,看完下面这幅图你就会觉得像啦. 你可能会问:树和图有什么区别?这个称之为树的东西貌似和无向图差不多嘛.不要着急,继续往下看.树其实就是不包含回路的连通无向图.你可能还是无法理解这其中的差异,举个例子,如下.          上面这个例子中左边的是一棵树,而右边的是一个图.因为左边的没有回路,而右边的存在1->2->5