アルゴリズム
Karatsubaアルゴリズムは、計算量n log 2 3の高速乗算法です。 素朴なアルゴリズムですが、列による乗算にはn 2回の演算が必要です。 数字の長さが数十文字より短い場合(より正確には、実験的に決定された場合)、通常の乗算はより速く機能することに注意してください。
ある数体系BASEに長さnの2つの数AとBがあると想像してください。
A = a n-1 a n-2 ... a 0
B = b n-1 a n-2 ... a 0 、ここでa ? b ? -値acc。 放電桁。
それらはそれぞれ、長さm = n / 2の半分の2つの部分の合計として表すことができます(nが奇数の場合、一方の部分はもう一方よりも1桁短くなります。
A 0 = a m-1 a m-2 ... a 0
A 1 = a n-1 a n-2 ... a m
A = A 0 + A 1 * BASE m
B 0 = b m-1 b m-2 ... b 0
B 1 = b n-1 b n-2 ... b m
B = B 0 + B 1 * BASE m
次に:A * B =
ここでは、4つの乗算演算が必要です(式の一部* BASE ?* Mは乗算ではなく、実際に結果を記録する場所、放電を示します)。 しかし、一方で:
(A 0 + A 1 )*(B 0 + B 1 )= A 0 * B 0 + A 0 * B 1 + A 1 * B 0 + A 1 * B 1
両方の式で強調表示されている部分を確認します。 単純な変換の後、乗算演算の数を3に減らすことができます。2つの乗算を1つおよびいくつかの加算および減算演算で置き換えます。実行時間は1桁少なくなります。
A 0 * B 1 + A 1 * B 0 =
式の最終形式:
A * B =
グラフィカル表現:
例
たとえば、10進数の12345と98765の2つの8桁の数値を乗算します。
アルゴリズムの再帰的な性質は、画像からはっきりと見えます。 4桁未満の数値の場合、単純な乗算が使用されました。
C ++実装
おそらく、数字が保存されている期間から始めるべきです。 長い数字を配列の形式で表すのが便利です。各要素は数字に対応し、下位の数字は低いインデックスの要素に保存されます(つまり、背中合わせに)-このように処理すると便利です。 例:
int long_value[] = { 9, 8, 7, 6, 5, 4} // 456789
パフォーマンスを向上させるために、ベースシステム内の最大数を番号システムの基礎として選択することをお勧めします。 ただし、同時に次の条件が課されます。
- 選択した数体系の最大数の二乗は、選択した基本型に収まる必要があります。 これは、中間計算で1つのカテゴリの製品を別のカテゴリに格納するために必要です。
- 選択したベースタイプのサインを取ることをお勧めします。 これにより、いくつかの中間正規化を取り除くことができます。
- 最大数のいくつかの平方の合計を放電に配置することをお勧めします。 これにより、いくつかの中間的な正規化が取り除かれます。
以下は、必要なすべてのサポート宣言と関数を備えたコメント付きの作業乗算関数です。 より高いパフォーマンスを得るには、数値システムの基数、数字を格納するタイプを変更し、単純な乗算を行う場所で小さなコードのコメントを解除する必要があります。
*このソースコードは、 ソースコードハイライターで強調表示されました。
- #include <cstring>
- #define BASE 10 //番号体系
- #define MIN_LENGTH_FOR_KARATSUBA 4 //短い数字には2次アルゴリズムが乗算されます
- typedef int digit; //数値の数字に対してのみ使用されます
- typedef unsigned long int size_length; //長い数字を入力します
- 名前空間 std を使用し ます 。
- struct long_value { //長い数値のタイプ
- 数字*値; //数字の数字が逆順に書かれた配列
- size_length length; //数が長い
- };
- long_value sum(long_value a、long_value b){
- / * 2つの長い数字を追加する関数。 異なる長さの数字が合計される場合
- *その後、長い方が最初の引数として渡されます。 新しい返品
- *非正規化数。
- * /
- long_value s;
- s.length = a.length + 1;
- s.values = 新しい数字[s.length];
- s.values [a.length-1] = a.values [a.length-1];
- s.values [a.length] = 0;
- for (size_length i = 0; i <b.length; ++ i)
- s.values [i] = a.values [i] + b.values [i];
- return s;
- }
- long_value&sub(long_value&a、long_value b){
- / * 1つの長い数値を別の数値から減算する関数。 最初の内容を変更します
- *数字。 最初の番号への参照を返します。 結果は正規化されません。
- * /
- for (size_length i = 0; i <b.length; ++ i)
- a.values [i]-= b.values [i];
- を返します;
- }
- void normalize(long_value l){
- / *番号の正規化-番号体系に従って各桁を表示します。
- *
- * /
- for (size_length i = 0; i <l.length-1; ++ i){
- if (l.values [i]> = BASE){ //数値が最大値より大きい場合、転送は整理されます
- 桁上げ= l.values [i] / BASE;
- l.values [i + 1] + =キャリーオーバー;
- l.values [i]-=キャリーオーバー* BASE;
- } else if (l.values [i] <0){ //少ない場合はローン
- 桁上げ=(l.values [i] + 1)/ BASE-1;
- l.values [i + 1] + =キャリーオーバー;
- l.values [i]-=キャリーオーバー* BASE;
- }
- }
- }
- long_value karatsuba(long_value a、long_value b){
- long_value製品; //結果の製品
- product.length = a.length + b.length;
- product.values = 新しい数字[product.length];
- if (a.length <MIN_LENGTH_FOR_KARATSUBA){ //数値が短い場合、単純乗算を適用します
- memset(product.values、0、 sizeof (digit)* product.length);
- for (size_length i = 0; i <a.length; ++ i)
- for (size_length j = 0; j <b.length; ++ j){
- product.values [i + j] + = a.values [i] * b.values [j];
- / * MIN_LENGTH_FOR_KARATSUBAまたはBASEを変更する場合は、次のコメントを外します
- *ラインとピックアップacc。 ビットのオーバーフローを避けるための値。
- *たとえば、10進法の場合、100という数字は整理されていることを意味します
- * 1回の排出で1を転送、200-1回の排出で2を転送、2回後に5000-5。
- * if(product.values [i + j]> = 100){
- * product.values [i + j]-= 100;
- * product.values [i + j + 2] + = 1;
- *}
- * /
- }
- } else { //唐atsu掛け
- long_value a_part1; //の下部
- a_part1.values = a.values;
- a_part1.length =(a.length + 1)/ 2;
- long_value a_part2; //の高い部分
- a_part2.values = a.values + a_part1.length;
- a_part2.length = a.length / 2;
- long_value b_part1; // bの下部
- b_part1.values = b.values;
- b_part1.length =(b.length + 1)/ 2;
- long_value b_part2; // bの高い部分
- b_part2.values = b.values + b_part1.length;
- b_part2.length = b.length / 2;
- long_value sum_of_a_parts = sum(a_part1、a_part2); // aの部分の合計
- ノーマライズ(sum_of_a_parts);
- long_value sum_of_b_parts = sum(b_part1、b_part2); //数bの部分の合計
- ノーマライズ(sum_of_b_parts);
- long_value product_of_sums_of_parts = karatsuba(sum_of_a_parts、sum_of_b_parts);
- //部品の合計の積
- long_value product_of_first_parts = karatsuba(a_part1、b_part1); //ジュニアメンバー
- long_value product_of_second_parts = karatsuba(a_part2、b_part2); //シニアメンバー
- long_value sum_of_middle_terms = sub(sub(product_of_sums_of_parts、product_of_first_parts)、product_of_second_parts);
- //中間のメンバーの合計を見つける
- / *
- *多項式の合計
- * /
- memcpy(product.values、product_of_first_parts.values、
- product_of_first_parts.length * sizeof (digit));
- memcpy(product.values + product_of_first_parts.length、
- product_of_second_parts.values、product_of_second_parts.length
- * sizeof (数字));
- for (size_length i = 0; i <sum_of_middle_terms.length; ++ i)
- product.values [a_part1.length + i] + = sum_of_middle_terms.values [i];
- / *
- *ストリッピング
- * /
- delete [] sum_of_a_parts.values;
- delete [] sum_of_b_parts.values;
- delete [] product_of_sums_of_parts.values;
- 削除[] product_of_first_parts.values;
- delete [] product_of_second_parts.values;
- }
- 正規化(製品); //数値の最終的な正規化
- 返品 ;
- }