- #1 : ユークリッドの互除法
- #2 : 拡張されたユークリッドの互除法 (導入)
- #3 : 拡張されたユークリッドの互除法 (実装)
概要
ユークリッドの互除法によって、2 つの整数 $a, b$ の最大公約数を求めつつ、一次不定方程式 $ax + by = {\rm gcd}(a, b)$ の解 $(x, y)$ を求める実装を Python で行います。
今回は、行列演算を用いて 1 回のループで計算を済ませたいと思います。
最終的な実装は、以下の通りです。
from typing import Tuple
# 再帰的に簡潔に記述するパターン
def ex_gcd(
a: int,
b: int,
x: int = 1,
y: int = 0,
u: int = 0,
v: int = 1,
) -> Tuple[int, int, int]:
return (a, x, y) if b == 0 else ex_gcd(
b,
a % b,
u,
v,
x - (a // b) * u,
y - (a // b) * v,
)
print(ex_gcd(8767, 4664))
# (11, 133, -250)
# 計算内容を意識して再帰的に記述しないパターン
def extended_euclidean_algorithm(a: int, b: int) -> Tuple[int, int, int]:
x: int = 1
y: int = 0
u: int = 0
v: int = 1
while b != 0:
x, u = u, x - (a // b) * u
y, v = v, y - (a // b) * v
a, b = b, a % b
return a, x, y
print(extended_euclidean_algorithm(8767, 4664))
# (11, 133, -250)
連立方程式は行列を使って解く
前回 のユークリッドの互除法の除算を再掲します。
任意の正整数 $a, b$ に対して、$r_0 = a, \ r_1 = b, \ r_n = {\rm gcd}(a, b)$ とすると、以下の通りになりました。
$$ \begin{align} r_0 &= q_0 r_1 + r_2 \\ r_1 &= q_1 r_2 + r_3 \\ r_2 &= q_2 r_3 + r_4 \\ \vdots \\ r_{n-3} &= q_{n-3} r_{n-2} + r_{n-1} \\ r_{n-2} &= q_{n-2} r_{n-1} + r_n \\ r_{n-1} &= q_{n-1} r_n \end{align} $$
この後、下から順に移項と代入を繰り返していくわけですが、これは $r_i \ (0 \leq i \leq n)$ を未知数とする連立方程式を解くことに他なりません。
したがって、まずはこの連立方程式を行列で書き直してみます。
$$ \begin{align} \left( \begin{array}{c} r_0 \\ r_1 \end{array} \right) &= \left( \begin{array}{cc} q_0 & 1 \\ 1 & 0 \end{array} \right) \left( \begin{array}{c} r_1 \\ r_2 \end{array} \right) \\ \left( \begin{array}{c} r_1 \\ r_2 \end{array} \right) &= \left( \begin{array}{cc} q_1 & 1 \\ 1 & 0 \end{array} \right) \left( \begin{array}{c} r_2 \\ r_3 \end{array} \right) \\ \left( \begin{array}{c} r_2 \\ r_3 \end{array} \right) &= \left( \begin{array}{cc} q_2 & 1 \\ 1 & 0 \end{array} \right) \left( \begin{array}{c} r_3 \\ r_4 \end{array} \right) \\ \vdots \\ \left( \begin{array}{c} r_{n-3} \\ r_{n-2} \end{array} \right) &= \left( \begin{array}{cc} q_{n-3} & 1 \\ 1 & 0 \end{array} \right) \left( \begin{array}{c} r_{n-2} \\ r_{n-1} \end{array} \right) \\ \left( \begin{array}{c} r_{n-2} \\ r_{n-1} \end{array} \right) &= \left( \begin{array}{cc} q_{n-2} & 1 \\ 1 & 0 \end{array} \right) \left( \begin{array}{c} r_{n-1} \\ r_n \end{array} \right) \\ \left( \begin{array}{c} r_{n-1} \\ r_n \end{array} \right) &= \left( \begin{array}{cc} q_{n-1} & 1 \\ 1 & 0 \end{array} \right) \left( \begin{array}{c} r_n \\ 0 \end{array} \right) \end{align} $$
これをまとめると、以下のような式に変形できました。
$$ \left( \begin{array}{c} a \\ b \end{array} \right) = \left( \begin{array}{cc} q_0 & 1 \\ 1 & 0 \end{array} \right) \left( \begin{array}{cc} q_1 & 1 \\ 1 & 0 \end{array} \right) \left( \begin{array}{cc} q_2 & 1 \\ 1 & 0 \end{array} \right) \cdots \left( \begin{array}{cc} q_{n-3} & 1 \\ 1 & 0 \end{array} \right) \left( \begin{array}{cc} q_{n-2} & 1 \\ 1 & 0 \end{array} \right) \left( \begin{array}{cc} q_{n-1} & 1 \\ 1 & 0 \end{array} \right) \left( \begin{array}{c} {\rm gcd}(a, b) \\ 0 \end{array} \right) $$
なお、$r_0 = a, \ r_1 = b, \ r_n = {\rm gcd}(a, b)$ は元に戻しています。
両辺の左から逆行列を掛けていく
$Q_i = \left( \begin{array}{cc} q_i & 1 \\ 1 & 0 \end{array} \right) \ (0 \leq i \leq n-1)$ とおくと、以下のように書き直せます。
$$ \left( \begin{array}{c} a \\ b \end{array} \right) = Q_0 Q_1 Q_2 \cdots Q_{n-3} Q_{n-2} Q_{n-1} \left( \begin{array}{c} {\rm gcd}(a, b) \\ 0 \end{array} \right) $$
ここで、行列式 ${\rm det} \ Q_i = -1 \neq 0$ より、行列 $Q_i$ は正則行列であり、逆行列が存在することが分かります。
したがって、逆行列 $Q_i^{-1} = \left( \begin{array}{cc} 0 & 1 \\ 1 & -q_i \end{array} \right) \ (0 \leq i \leq n-1)$ を両辺の左から順番に掛けていくと、以下のようになります。
$$ Q_{n-1}^{-1} Q_{n-2}^{-1} Q_{n-3}^{-1} \cdots Q_2^{-1} Q_1^{-1} Q_0^{-1} \left( \begin{array}{c} a \\ b \end{array} \right) = \left( \begin{array}{c} {\rm gcd}(a, b) \\ 0 \end{array} \right) \\ \left( \begin{array}{cc} 0 & 1 \\ 1 & -q_{n-1} \end{array} \right) \left( \begin{array}{cc} 0 & 1 \\ 1 & -q_{n-2} \end{array} \right) \left( \begin{array}{cc} 0 & 1 \\ 1 & -q_{n-3} \end{array} \right) \cdots \left( \begin{array}{cc} 0 & 1 \\ 1 & -q_2 \end{array} \right) \left( \begin{array}{cc} 0 & 1 \\ 1 & -q_1 \end{array} \right) \left( \begin{array}{cc} 0 & 1 \\ 1 & -q_0 \end{array} \right) \left( \begin{array}{c} a \\ b \end{array} \right) = \left( \begin{array}{c} {\rm gcd}(a, b) \\ 0 \end{array} \right) $$
あとは、以下のようにおくことで、
$$ \begin{align} \left( \begin{array}{cc} x & y \\ u & v \end{array} \right) &= \left( \begin{array}{cc} 0 & 1 \\ 1 & -q_{n-1} \end{array} \right) \left( \begin{array}{cc} 0 & 1 \\ 1 & -q_{n-2} \end{array} \right) \left( \begin{array}{cc} 0 & 1 \\ 1 & -q_{n-3} \end{array} \right) \cdots \left( \begin{array}{cc} 0 & 1 \\ 1 & -q_2 \end{array} \right) \left( \begin{array}{cc} 0 & 1 \\ 1 & -q_1 \end{array} \right) \left( \begin{array}{cc} 0 & 1 \\ 1 & -q_0 \end{array} \right) \end{align} $$
最終的に、このような簡潔な形で書き表すことができました。
$$ \left( \begin{array}{cc} x & y \\ u & v \end{array} \right) \left( \begin{array}{c} a \\ b \end{array} \right) = \left( \begin{array}{c} {\rm gcd}(a, b) \\ 0 \end{array} \right) $$
この行列 $\left( \begin{array}{cc} x & y \\ u & v \end{array} \right)$ を求めることで、一次不定方程式 $ax + by = {\rm gcd}(a, b)$ の解 $(x, y)$ を求められるというわけです。
解を求めるアルゴリズム
最後に、行列 $\left( \begin{array}{cc} x & y \\ u & v \end{array} \right)$ を求めるアルゴリズムを考えるわけですが、これは単純で、$Q_0$ から順番に左から $Q_i$ を掛けていくだけです。
ユークリッドの互除法において、除算の商 $q_i$ の値が判明する順番は $q_0, q_1, q_2, \ldots$ となっているわけですから、対応する行列 $Q_i^{-1}$ の値が判明する順番は $Q_0^{-1}, Q_1^{-1}, Q_2^{-1}, \ldots$ となります。
そのため、1 回のループの中で最大公約数を求める過程と並行して、一次不定方程式の解を求められるというわけです。
アルゴリズムとしては、単位行列 $\left( \begin{array}{cc} 1 & 0 \\ 0 & 1 \end{array} \right)$ の左から $Q_i^{-1} = \left( \begin{array}{cc} 0 & 1 \\ 1 & -q_i \end{array} \right) \ (0 \leq i \leq n-1)$ を掛けていくので、$x_0 = 1, \ y_0 = 0, \ u_0 = 0, \ v_0 = 1$ としたとき、以下のように順に計算していくことになります。
$$ \begin{align} \left( \begin{array}{cc} x_1 & y_1 \\ u_1 & v_1 \end{array} \right) &= \left( \begin{array}{cc} 0 & 1 \\ 1 & -q_0 \end{array} \right) \left( \begin{array}{cc} x_0 & y_0 \\ u_0 & v_0 \end{array} \right) = \left( \begin{array}{cc} u_0 & v_0 \\ x_0 - q_0 u_0 & y_0 - q_0 v_0 \end{array} \right) \\ \left( \begin{array}{cc} x_2 & y_2 \\ u_2 & v_2 \end{array} \right) &= \left( \begin{array}{cc} 0 & 1 \\ 1 & -q_1 \end{array} \right) \left( \begin{array}{cc} x_1 & y_1 \\ u_1 & v_1 \end{array} \right) = \left( \begin{array}{cc} u_1 & v_1 \\ x_1 - q_1 u_1 & y_1 - q_1 v_1 \end{array} \right) \\ \left( \begin{array}{cc} x_3 & y_3 \\ u_3 & v_3 \end{array} \right) &= \left( \begin{array}{cc} 0 & 1 \\ 1 & -q_2 \end{array} \right) \left( \begin{array}{cc} x_2 & y_2 \\ u_2 & v_2 \end{array} \right) = \left( \begin{array}{cc} u_2 & v_2 \\ x_2 - q_2 u_2 & y_2 - q_2 v_2 \end{array} \right) \\ \vdots \end{align} $$
これを、ユークリッドの互除法と合わせて書き下すと、以下のようになります。
- 2 つの整数を $a, b$ とする。また、$x = 1, \ y = 0, \ u = 0, \ v = 1$ を初期値とする
- $b = 0$ ならば、$a$ が最大公約数であり、$x, y$ が一次不定方程式の解であるため、アルゴリズムを終了する
- $b \neq 0$ ならば、$a$ を $b$ で除算した商を $q$ 、剰余を $r$ とするとき、$b, r$ を新たな $a, b$ として、さらに $u, v, x-qu, y-qv$ を新たな $x, y, u, v$ として、手順 2 に戻る
これを Python で実装したのが冒頭のコードです。
以上で、2 つの整数 $a, b$ の最大公約数を求めつつ、一次不定方程式 $ax + by = {\rm gcd}(a, b)$ の解 $(x, y)$ を求めるアルゴリズムがすっきりする形にできたかなと思います。
メモ
これで一段落です。
すっきりした形になったと書きましたが、実際の計算量とか実行速度のことは検証していません。興味がないので検証はしません。
綺麗に書けたということに意義がある、自己満足のためのものです。
また、これを利用した RSA 暗号の話については、面倒でなければそのうち書くと思います。