トップページ -> Pythonで数学をしてみよう! -> Pythonで素因数分解してみよう!

Pythonで素因数分解してみよう!

今回は素因数分解をしていきたいと思います. 素朴な問題ですが,暗号技術に関連してくるテーマです.
ところで最初に順番に読み進めなくても大丈夫と言ったのですが,第2回のみ第1回の内容と強く関連しています. 第1回の素数判定のページを読んでからこのページに戻っていただけると理解が深まります.

課題

与えられた入力nに対して素因数分解を返すプログラムを書きたい

すべての素因数を求めたいのですから2からn-1までの数で割ってしまえば,簡単に素因数分解ができそうですね. さらに素数判定のときの要領で2からint(√n)までの数に限定すれば無駄を省けます.

解法①


n = int(input())
n_ = n # 元々のnを記憶しておく
div_list = [] # 素因数のリストを作る

num = 2 # 2から割り始める
while num <= int(n_**0.5)+1: # 割る数がint(n_**0.5)+1以下の間 繰り返す
    if n%num == 0:
        div_list.append(num) # 割り切れたら素因数のリストに追加
        n = n/num # nを更新する
    else:
        num += 1 # 割り切れなければ次の数を試す
    if n < num:
        break # 割る数のほうがnより大きくなったら終了
        
# n が1でなければそれも素因数なので追加
if n != 1:
    div_list.append(int(n))

print(div_list)
2から順番に割っていき割り切れた場合は素因数のリストに追加しています. 最後の if n != 1 については,理解できない場合はここを消して10で試してみてください. (というか私は10で間違いに気づいてこの文を追加しました)
1回の素因数分解の最大計算量はO(√n)ですね. 13桁くらいなら数ならすぐ終わります. 調子に乗って1からnまでのすべての数を素因数分解してみましょう.

課題

1からnまでの素因数分解を全て求めたい.

計算量O(√n)の素因数分解をn回行うのでO(n√n)の計算量です.(section1 の実行時間と大きくかけ離れる理由は実験中)

解法①


n = int(input())
div_list = [[] for i in range(n+1)] # 素因数のリストを作る
for num in range(n+1):
    n_ = num # 元々のnを記憶しておく
    div_num = 2 # 2から割り始める
    while div_num <= int(n_**0.5)+1: # 割る数がint(n_**0.5)+1以下の間 繰り返す
        if num%div_num == 0:
            div_list[n_].append(div_num) # 割り切れたら素因数のリストに追加
            num = num/div_num # nを更新する
        else:
            div_num += 1 # 割り切れなければ次の数を試す
        if num < div_num:
            break # 割る数のほうがnより大きくなったら終了

    # n が1でなければそれも素因数なので追加
    if num != 1:
        div_list[n_].append(int(num))
リストを出力してもらうと大変なのでprintはコメントアウトしておきます. 面倒なので1に関しては無視しています. 以下のコードで素因数分解を出力できます.

for i in range(2,len(div_list)):
print(i,end="=")
for j in range(len(div_list[i])):
    if j == len(div_list[i]) - 1:
        print(div_list[i][j])
    else:
        print(div_list[i][j],end="*")
1からnまでの素因数分解ができたのですが,このままでは遅くて使いづらいです. 100000以上になると結構かかります. そこで第1回で求めた素数のリストを使うことにします. nまでの素因数を求めるのに√n回の割り算をしていましたが,素数のリストを使って割っていくことで無駄な割り算を省きます.

解法②


n = int(input())
num_list = [True for i in range(n+1)]
num_list[0] = False
num_list[1] = False

# エラトステネスの篩
for i in range(2,int(n**0.5)+1):
    if num_list[i] == True:
        p = i
        for j in range(p*p,n+1,p):
            num_list[j] = False
           
        prime_list = []
        for i in range(n+1):
            if num_list[i]:
                prime_list.append(i)

# ここから素因数分解
div_list = [[] for i in range(n+1)] # 素因数のリストを作る
for num in range(n+1):
    ind = 0
    n_ = num # 元々のnを記憶しておく
    div_num = 2 # 2から割り始める
    while div_num <= int(n_**0.5)+1: # 割る数がint(n_**0.5)+1以下の間 繰り返す
        div_num = prime_list[ind] # 2から割り始める
        if num%div_num == 0:
            div_list[n_].append(div_num) # 割り切れたら素因数のリストに追加
            num = num/div_num # nを更新する
        else:
            ind += 1 # 割り切れなければ次の数を試す
        if num < div_num:
            break # 割る数のほうがnより大きくなったら終了

    # n が1でなければそれも素因数なので追加
    if num != 1:
        div_list[n_].append(int(num))

とても速くなりましたね. それもそのはず以下のグラフを見てください. 一回あたりの素因数分解にかかる計算量のグラフです.
計算量の推移
だいぶ,計算量を削減できています. ここまでは割と思いつくのですが,さらに高速に素因数分解を行う方法があります. 以下のような方法でnの素因数分解を求めます.
  1. エラトステネスの篩を応用し,最小の素因数のリストを作る.
  2. n をリストを参照し最小の素因数で割る.
  3. n ← n/(最小の素因数) で更新する.
  4. n==1 なら終了,そうでなければ手順2に戻る.

解法③


n = int(input())

min_factor = [False for i in range(n+1)]
min_factor[0] = True
min_factor[1] = True

# 最小の素因数を求める
for i in range(2,int(n**0.5)+1):
    if min_factor[i] == False:
        p = i
        min_factor[i] = p
        for j in range(p*p,n+1,p):
            if min_factor[j] == False:
                min_factor[j] = p
                
for i in range(n+1):
    if min_factor[i] == False:
        min_factor[i] = i
  
# 最小の素因数を使って素因数分解を行う
div_list = [[] for i in range(n+1)]
for num in range(2,n+1):
    num_ = num
    while num > 1:
        div_list[num_].append(min_factor[num])
        num = int(num/min_factor[num])
このアルゴリズム全体の計算量を求めてみましょう.最小の素因数を求める部分がエラトステネスの篩の応用であるためO(n*log(log(n))) 素因数分解を行う部分はnの素因数の数だけ割り算を行うことになりますが, nが2**mより小さければ少なくともm回の割り算を行えば素因数分解ができるため O(log(n))です. よって,アルゴリズム全体の計算量は,O(n*log(log(n))) + O(n*log(n)) = O(max(n*log(log(n)),log(n))) = O(n*log(n)) となります.

今回は素因数分解をして遊んでみました. 興味がある方は,素因数分解と暗号技術の関連についても調べてみると面白いですよ. 余裕があればこれについても書いてみたいと思います. 最小公倍数・最大公約数について考えてみたいと思います.

<- 前へ戻る 【目次に戻る】 次へ進む ->