算法导论实践——大数乘法(二进制)
感觉算法思路很简单,却实现了一个下午,可能还是对代码理解得不够深刻吧,很多地方感觉很冗余,不过好歹实现了,在此记录一下;
理论
算法的核心是把大数乘法的时间复杂度降到O(nlog3)。在上图中,有递归方程:
其中,Theta(n)是2n次方乘积用移位方式实现的结果。可以看出,子问题主要是由不同项乘积带来的,所以,重点是处理乘法项。这里将中间的和式写为:
则递归方程就变成了
OK,开始实现。
C#实现
BigIntegerMultiplicationUtil
using System;
using System.Collections.Generic;
using System.Text;
namespace BigInteger
{
public static class BigIntegerMultiplicationUtil
{
public static int[] X; //二进制数组X
public static int[] Y; //二进制数组Y
private static string RawX;//用户输入的X
private static string RawY;
private static int MaxLength;//乘积应该开辟的数组长度
public static void InputInteger()
{
Console.WriteLine("输入X:");
RawX = Console.ReadLine();
Console.WriteLine("输入Y:");
RawY = Console.ReadLine();
X = new int[RawX.Length];
Y = new int[RawY.Length];
RawToBinary(RawX, X);
RawToBinary(RawY, Y);
MaxLength = X.Length * Y.Length;
}
//将用户输入的数组raw,转换为二进制数组binary
private static void RawToBinary(string raw,int[] binary)
{
for(int i = 0; i < raw.Length; i++)
{
binary[i] = raw[i] - '0';
}
}
public static int[] BigIntegerMultiplication(int[] x, int[] y)
{
//获取二进制数组的有效位数(比如00001的有效位数为1,其中,默认00000的有效位数为1)
int xLength = GetEffctiveLength(x);
int yLength = GetEffctiveLength(y);
//获取二进制数组有效位数开始的下标,比如00001的Start为4,其中默认00000的Start为0
int xStart = GetStartWith(x) == -1 ? 0 : GetStartWith(x);
int yStart = GetStartWith(y) == -1 ? 0 : GetStartWith(y);
//递归终止条件,即,若某个数组的有效长度为1,就可以计算了
if (xLength == 1)
{
return SimpleMulti(y, x[^1]);
}
if (yLength == 1)
{
return SimpleMulti(x, y[^1]);
}
//**分解过程,n代表数组的中点下标**//
//假设x为10000,则n1 = 5/2 = 2 ;y为0010,则n2 = 2/2 = 1
int n1 = xLength / 2;
int n2 = yLength / 2;
//获取子数组,还是以x为10000,y为0010例,由于MaxLength = 5 * 4 = 20,所以
//A = 0000 0000 0000 0000 0010(20位)
//B = 0000 0000 0000 0000 0000(20位)
//(这里其实做的很瓜皮,根本没必要这么搞(指把这里面出现的所有数组都统一长度),可是当时想着偷懒,不用算长度了,没想到后面却搞出一堆问题,哎,说明我得层数还不够啊,以后有时间再进行优化,今天懒得搞了,等等搞Xamarin去了)
int[] A = SubIntArray(x, xStart, xStart + n1 - 1);
int[] B = SubIntArray(x, xStart + n1, x.Length - 1);
//C = 0000 0000 0000 0000 0001(20位)
//D = 0000 0000 0000 0000 0000(20位)
int[] C = SubIntArray(y, yStart, yStart + n2 - 1);
int[] D = SubIntArray(y, yStart + n2, y.Length - 1);
/*演算步骤(这里考虑一般性):
(x = A * 2^n1 + B, y = C * 2^n2 + D
xy = ac*2(n1+n2)+bd+ad*2^n1+bc*2^n2
xy=ac*2(n1 + n2) + bd + (ad+bc)*2^n1 + bc*(2^n2-2^n1)
xy=ac*2^(n1 + n2) + bd + ((a+b)(c+d) - ac - bd)*2^n1 + bc*(2^n2-2^n1)
xy = ac*2^(n1 + n2) + bd + (a+b)(c+d) * 2^n1 - ac*2^n1 - bd*2^n1 + bc*(2^n2-2^n1))*/
//**递归求解过程,演算上面的表达式**//
int[] AC = BigIntegerMultiplication(A, C);
int[] BD = BigIntegerMultiplication(B, D);
int[] BC = BigIntegerMultiplication(B, C);
int[] AaddB = BinaryAdd(A, B);
int[] CaddD = BinaryAdd(C, D);
int[] AaddBCaddD = BigIntegerMultiplication(AaddB, CaddD);
//处理奇数问题,这里我的A = 0000 0000 0000 0000 0010
//事实上,此时的x = A * 2^n1[向上取整] + B,大家可以自己推演一下,结合前面部分的理论的那张图,就知道了。
n1 = xLength % 2 == 1 ? xLength / 2 + 1 : xLength / 2;
n2 = yLength % 2 == 1 ? yLength / 2 + 1 : yLength / 2;
//一顿演算
int[] AddPart = BinaryAdd(BinaryAdd(BinaryAdd(MoveLeft(AC, (n1 + n2)), BD), MoveLeft(AaddBCaddD, n1)), MoveLeft(BC, n2));
int[] XY = BinarySub(BinarySub(BinarySub(AddPart, MoveLeft(AC, n1)), MoveLeft(BD, n1)),MoveLeft(BC,n1));
//返回结果
return XY;
}
//打印结果
public static void GetResult(int[] A)
{
int startWith = -1;
for(int i = 0; i < A.Length; i++)
{
if (A[i] != 0)
{
startWith = i;
break;
}
}
if(startWith == -1)
{
Console.Write("0");
}
else
{
for(int i = startWith; i < A.Length; i++)
{
Console.Write(A[i]);
Console.Write(" ");
}
}
Console.WriteLine(" ");
}
//获取二进制数组的有效长度
public static int GetEffctiveLength(int[] A)
{
int startWith = -1;
for (int i = 0; i < A.Length; i++)
{
if (A[i] != 0)
{
startWith = i;
break;
}
}
if (startWith == -1)
{
return 1;
}
else
{
return A.Length - startWith;
}
}
//获取二进制数组有效长度的开始的下标
public static int GetStartWith(int[] A)
{
int startWith = -1;
for (int i = 0; i < A.Length; i++)
{
if (A[i] != 0)
{
startWith = i;
break;
}
}
return startWith;
}
//数组左移a位
public static int[] MoveLeft(int[] A, int a)
{
int[] B = new int[MaxLength];
//获得有效数组,因为前面我的ABCD长度都直接设置为MaxLength了,但是这就很蠢了,根本就没有必要---
int[] EffectiveA = SubIntArray(A, A.Length - GetEffctiveLength(A), A.Length - 1, true);
for (int i = EffectiveA.Length - 1; i >= 0; i--)
{
B[MaxLength - a + i - EffectiveA.Length] = EffectiveA[i];
}
return B;
}
//二进制加法
//这算是统一长度之后的好处吧,就是加法特别简单
public static int[] BinaryAdd(int[]A,int[] B)
{
for(int i = MaxLength - 1; i >= 0; i--)
{
if(A[i] + B[i] == 2)
{
A[i] = 0;
A[i - 1]++;
}
else if(A[i] + B[i] == 3)
{
A[i] = 1;
A[i - 1]++;
}
else
{
A[i] += B[i];
}
}
return A;
}
//二进制减法
//和加法类似
public static int[] BinarySub(int[] A, int[] B)
{
for (int i = MaxLength - 1; i >= 0; i--)
{
if (A[i] - B[i] == -1)
{
A[i] = 1;
A[i - 1]--;
}
else if(A[i] - B[i] == -2)
{
A[i] = 0;
A[i - 1]--;
}
else
{
A[i] -= B[i];
}
}
return A;
}
//获取统一长度的子数组
public static int[] SubIntArray(int[] A, int p, int q)
{
int[] B = new int[MaxLength];
for (int i = q; i >= p; i--)
{
B[MaxLength - 1 + (i - q)] = A[i];
}
return B;
}
//重载SubIntArray,获取有效长度的子数组
//大概只用到了一个地方(左移MoveLeft的时候)
public static int[] SubIntArray(int[] A, int p, int q, bool isCutTrue)
{
int[] B = new int[GetEffctiveLength(A)];
if(GetEffctiveLength(A) == 1)
{
B[^1] = A[^1];
}
else
{
for (int i = q; i >= p; i--)
{
B[GetEffctiveLength(A) + (i - q) - 1] = A[i];
}
}
return B;
}
//计算朴素乘法,事实上a的值只可能为0或者1,因此,直接判断就好了
public static int[] SimpleMulti(int[] A, int a)
{
int[] B = new int[MaxLength];
if (a == 0)
{
for (int i = 0; i < A.Length; i++)
{
B[MaxLength - i - 1] = 0;
}
}
else
{
for (int i = 0; i < A.Length; i++)
{
B[MaxLength - i - 1] = A[A.Length - i - 1];
}
}
return B;
}
}
}
Program
using System;
using BigInteger;
namespace BigInteger
{
class Program
{
static void Main(string[] args)
{
//Console.WriteLine("Hello World!");
BigIntegerMultiplicationUtil.InputInteger();
int[] X = BigIntegerMultiplicationUtil.X;
int[] Y = BigIntegerMultiplicationUtil.Y;
int[] XY = BigIntegerMultiplicationUtil.BigIntegerMultiplication(X, Y);
BigIntegerMultiplicationUtil.GetResult(XY);
}
}
}