这些数字存在于包含
Size
个项的uint[]
数组中。DigitCollection Convert(BitCollection bits)
{
if (bits.Size == 1)
return bits[0] ? DigitCollection.SingleOne : DigitCollection.SingleZero;
DigitCollection left = Convert(bits.Left);
DigitCollection right = Convert(bits.Right);
DigitCollection power = MakePowerOfTwo(bits.Right.Size);
return Add(Multiply(left, power), right);
}
DigitCollection Multiply(DigitCollection x, DigitCollection y)
{
// Assume x and y are the same digit size.
if (x and y are both single digits)
return the trivial solution;
// Assume that z2, z1 and z0 are all the same digit size as x and y.
DigitCollection z2 = Multiply(x.Left, y.Left);
DigitCollection z0 = Multiply(x.Right, y.Right);
DigitCollection z1 = Subtract(
Multiply(Add(x.Left, x.Right), Add(y.Left, y.Right)),
Add(z2, z0));
return Add(Add(FullShift(z2), HalfShift(z1)), z0);
}
这个算法的时间复杂度是多少?假设有n = 2d个数字。那么O(Multiply(n))是多少?我们递归三次,每次问题规模减半。其余的加、减和移位操作都不超过O(n)。因此我们得到一个递推公式:
T(n) = 3 T(n/2) + O(n)
DigitCollection MakePowerOfTwo(int p)
{
if (p == 0) return DigitCollection.SingleOne;
DigitCollection power = MakePowerOfTwo(p / 2);
power = Multiply(power, power);
if (p % 2 != 0)
power = Add(power, power);
return power;
}
这段内容主要涉及乘法运算,因此时间复杂度为O(n1.58)。
现在我们可以看到Convert函数的原始调用也受到乘法运算的影响。
因此,使用此算法转换2d个二进制位,需要大约O(21.58 d)步骤。在您的情况下,您需要转换225个二进制位,因此需要进行约7770亿次计算。
关键事实是,该算法的成本完全由乘法运算的成本支配。如果您能将乘法运算的成本降低到小于O(n1.58),则可以获得巨大的收益。如果我是您,我会研究改进Karatsuba算法的十进制乘法算法。
O(n * log(n)^ 2)
[分治转换算法](http://gmplib.org/manual/Binary-to-Radix.html)。 GMP使用接近于O(n * log(n))
的基于FFT的乘法算法。我知道Java的BigInteger
缺乏次二次幂乘法。只是好奇:C#的 BigInteger
是否具有次二次幂乘法?它是否具有O(n * log(n))
乘法? - Mysticial我不知道这个方法是否更快,但是这里有一个我很久以前用Delphi写的处理大整数作为字符串的例子(非常简单粗暴)- 这是针对128位无符号整数的,但你可以无限扩展。
Function HexToBinShort(hex:integer):string;
begin
case hex of
0: result:='0000'; //convert each hex digit to binary string
1: result:='0001'; //could do this with high-nybble and low nybble
2: result:='0010'; //of each sequential byte in the array (mask and bit shift)
3: result:='0011'; //ie: binstring:=binstring + HexToBinShort(nybble[i])
4: result:='0100'; //but must count DOWN for i (start with MSB!)
5: result:='0101';
6: result:='0110';
7: result:='0111';
8: result:='1000';
9: result:='1001';
10: result:='1010';
11: result:='1011';
12: result:='1100';
13: result:='1101';
14: result:='1110';
15: result:='1111';
end;
end;
Function BinToIntString(binstring:string):string;
var i, j : integer;
var calcHold, calc2 :string;
begin
calc2:=binstring[Length(binstring)]; // first bit is easy 0 or 1
for i := (Length(binstring) - 1) downto 1 do begin
if binstring[i] = '1' then begin
calcHold:=generateCard(Length(binstring)-i);
calc2 := AddDecimalStrings(calcHold, calc2);
end;
end;
result:=calc2;
end;
generateCard 用于创建一个十进制字符串表示 2^i(其中 i>0)
Function generateCard(i:integer):string;
var j : integer;
var outVal : string;
begin
outVal := '2';
if i > 1 then begin
for j := 2 to i do begin
outVal:= MulByTwo(outVal);
end;
end;
result := outVal;
end;
MulByTwo函数可以将一个小数字符串乘以2。
Function MulByTwo(val:string):string;
var i : integer;
var carry, hold : integer;
var outHold : string;
var outString :string;
var outString2 : string;
begin
outString:= StringOfChar('0', Length(val) + 1);
outString2:= StringOfChar('0', Length(val));
carry :=0;
for i := Length(val) downto 1 do begin
hold := StrToInt(val[i]) * 2 + carry;
if hold >= 10 then begin
carry := 1;
hold := hold - 10;
end else begin
carry := 0;
end;
outHold := IntToStr(hold);
outString[i+1] := outHold[1];
end;
if carry = 1 then begin
outString[1] := '1';
result := outString;
end else begin
for i := 1 to length(outString2) do outString2[i]:=outString[i+1];
result := outString2;
end;
end;
最后,AddDecimalStrings...它可以将两个小数字符串相加:
Function AddDecimalStrings(val1, val2:string):string;
var i,j :integer;
var carry, hold, largest: integer;
var outString, outString2, bigVal, smVal, outHold:string;
begin
if Length(val1) > Length(val2) then begin
largest:= Length(val1);
bigVal := val1;
smVal := StringOfChar('0', largest);
j:=1;
for i := (largest - length(val2) +1) to largest do begin
smVal[i] := val2[j];
j:=j+1;
end;
end else begin
if length(val2) > Length(val1) then begin
largest:=Length(val2);
bigVal:=val2;
smVal := StringOfChar('0', largest);
j:=1;
for i := (largest - length(val1) +1) to largest do begin
smVal[i] := val1[j];
j:=j+1;
end;
end else begin
largest:=length(val1);
bigVal:=val1;
smVal:=val2;
end;
end;
carry:=0;
outString:=StringOfChar('0', largest +1);
outString2:=StringOfChar('0', largest);
for i := largest downto 1 do begin
hold := StrToInt(bigVal[i]) + StrToInt(smVal[i]) + carry;
if hold >=10 then begin
carry:=1;
hold := hold - 10;
end else begin
carry:=0;
end;
outHold:= IntToStr(hold);
outString[i+1]:=outHold[1];
end;
if carry = 1 then begin
outString[1] := '1';
result := outString;
end else begin
for i := 1 to length(outString2) do outString2[i]:=outString[i+1];
result := outString2;
end;
end;
这些函数允许您对几乎任意大小的字符串进行基本算术运算。当数字位数太大无法通过索引数组时,您将遇到另一个障碍。
顺便提一下,这是除以二的方法(用于反向操作很有用...)。我这里不处理奇数。
Function DivByTwo(val:string):string;
var i : integer;
var hold : integer;
var outHold : string;
var outString, outString2 :string;
begin
outString:=StringOfChar('0',Length(val));
for i := Length(val) downto 1 do begin
if StrToInt(val[i]) mod 2 = 0 then begin
hold:= Math.Floor(StrToInt(val[i]) / 2);
outHold:= IntToStr(hold);
outString[i]:=outHold[1];
end else begin
hold:= Math.Floor((StrToInt(val[i]) - 1) / 2);
outHold:=IntToStr(hold);
outString[i]:= outHold[1];
if i <> Length(val) then begin
hold:= StrToInt(outString[i+1]) + 5;
outHold:= IntToStr(hold);
outString[i+1] := outHold[1];
end;
end;
end;
outString2:=StringOfChar('0',Length(val)-1);
if (outString[1] = '0') and (length(outString) > 1) then begin
for i := 1 to length(outString2) do outString2[i]:=outString[i+1];
result:=outString2;
end else begin
result:=outString;
end;
end;
编辑:我刚用一个900万位长的二进制字符串尝试了一下,结果非常慢!这并不奇怪。这是完全未经优化的代码,有很多可以进行优化的地方。尽管如此,我仍然觉得这可能是你想要编写的类型(或规模)的问题,至少部分地使用完全优化的汇编语言。单个操作很小,但必须执行很多次——这意味着需要使用汇编语言。在这里还可以利用多线程。
这是迄今为止最有效的方法。
10
,而是除以例如10^100000
,并递归地继续,直到所有子转换都简化为微不足道的内容为止。要实现次二次性能,需要亚二次乘法运算。要实现O(n * log(n)^2)
,需要使用基于FFT的乘法。因此,这是一个非平凡的算法。 - Mysticial