コンピュータ演習II 第11回 アルゴリズム入門¶
今回の主役は アルゴリズム という考え方です。アルゴリズムとは何か、どう読んでコードに翻訳するかを、リストを小さい順に並べ替える 整列(ソート) を題材にして学びます。次の3 つを学びます。
- アルゴリズムの読み方(日本語の番号付きの手順を読んで、for 文と if 文に翻訳する)
- 2 つの整列アルゴリズム(バブルソートと選択ソート。同じ問題を違う戦略で解く)
- アルゴリズムの良し悪し(比較回数と交換回数を数えて比べる)
新しい文法は1 つも出てきません。これまでに学んだ for 文/if 文/入れ替え(swap)だけで、代表的なソーティングアルゴリズムが書けます。
1. アルゴリズムとは¶
アルゴリズム とは、問題を解くための手順を、順番に並べて書いたものです。この資料では、バブルソートと選択ソートをコードで示す前に、まず日本語の番号付きの手順(アルゴリズム)として示します。手順は 上から順に実行され、繰り返したいところは「手順◯へ戻る」と書きます。
これまで学んできたように、同じ処理は3 つの形で表せます。
- 手順(アルゴリズム): 日本語の文章で「何をどの順にするか」を書いたもの
- フローチャート: 手順を図(記号と矢印)で表したもの
- Python コード: 手順をコンピュータが実行できる形で書いたもの
3 つは 同じものの表現違い です。ただし、手順の「手順◯へ戻る」という書き方は、for 文とそのままの形では対応しません。そこでこの章では、手順を Python コードに翻訳するための 規則 を先に覚えてしまいます。
1.1 翻訳の規則: 繰り返しの「3 点セット」→ for 文¶
「手順◯へ戻る」で繰り返しを書くと、手順の中に必ず次の 3 点セット が現れます。
- はじめの値: 「
iを 0 とする」 - 進め方: 「
iを1 増やす」 - 続ける条件: 「
iがn - 1より小さければ、手順◯へ戻る」
この3 点セットを見つけたら、まとめて1 つの for 文 for i in range(0, n - 1): に落とし込んでください。これが翻訳の規則です。はじめの値が range の開始に、続ける条件が range の終わりに対応し、「1 増やして戻る」は for 文が自動でやってくれます。つまり for 文は、手順ではばらばらの場所に書かれる3 点セットを1 行にまとめた 省略記法 だと考えてください。
あわせて、次の2 つも覚えておいてください。
- 「◯回に達していなければ、手順△へ戻る」のように回数で書かれた戻りも、同じ規則で
for ... in range(0, ◯):になります。 - 戻り以外の「〜ならば」は if 文になります。
1.2 同じものを3 つの表現で見る¶
規則を確かめるために、「0 から 4 までの数を順に出力する」という1 つの処理を、3 つの表現で縦に並べます。まず手順です。3 点セット(手順1 の「0 とする」、手順3 の「1 増やす」と「5 より小さければ戻る」)を探しながら読んでください。
- カウンタ
iを 0 とする。iを出力する。iを1 増やす。iが 5 より小さければ、手順2 へ戻る。
この手順をそのまま図にしたのが、次のフローチャートです。手順3 の「手順2 へ戻る」が、図では 下から左へ回ってひし形へ戻る矢印 になっています。3 点セットは、i = 0 の箱と、i = i + 1 の箱と、i < 5 のひし形+戻り矢印です。
そして、この3 点セット(箱2 つと、ひし形+戻り矢印)をまとめて1 行の for 文に落とし込んだのが、次の Python コードです。
for i in range(0, 5):
print(i)
3 つの表現はどれも「0, 1, 2, 3, 4 と1 行ずつ出力する」という 同じ動作 を表しています。手順の「戻る」=図の戻り矢印=for 文、という対応を頭に入れておいてください。
この読み方は、次の2 章の復習で最初の練習をし、その後の2 つのソートで繰り返し練習します。また、授業後の課題の発展問題では、資料で扱っていないアルゴリズムを、コードの例を示さず 手順だけから実装 します。手順とコードの対応を意識しながら読み進めてください。
2. 復習: 入れ替え(swap)と最大値移動¶
2.1 tmp を使った入れ替え¶
第09回の復習です。リストの隣り合う2 つの要素 numbers[i] と numbers[i + 1] を入れ替えるには、片方の値を一時変数 tmp に 逃がして から、3 ステップで行うのでした。いきなり numbers[i] = numbers[i + 1] とすると、もとの値が消えてしまうからです。
今回はこの3 ステップの入れ替えを何度も書きます。手が覚えるまで、まずは1 回書いて動かしてください。
numbers = [6, 1, 2, 5, 0]
i = 0 # 入れ替えたい位置(ここでは 0 番目と 1 番目)
tmp = numbers[i] # numbers[0](=6)を tmp に逃がす
numbers[i] = numbers[i + 1] # numbers[0] に numbers[1](=1)を入れる
numbers[i + 1] = tmp # numbers[1] に、逃がしておいた 6 を入れる
print(numbers)
[1, 6, 2, 5, 0]
実行結果
[1, 6, 2, 5, 0]
2.2 最大値移動(最大値を後ろへ運ぶ操作)¶
これも第09回の復習です。隣り合うペアを 先頭から末尾まで順に 比べ、前のほうが大きければ入れ替える、という処理を 1 パス と呼ぶことにします。ループ変数は j を使います(あとで外側のループに i を使うためです)。
numbers = [6, 1, 2, 5, 0]
# 1 パス: 先頭から末尾まで、前のほうが大きければ入れ替える
for j in range(0, len(numbers) - 1): # j = 0, 1, 2, 3
if numbers[j] > numbers[j + 1]: # 前のほうが大きければ
tmp = numbers[j] # tmp を使って入れ替える
numbers[j] = numbers[j + 1]
numbers[j + 1] = tmp
print(numbers)
[1, 2, 5, 0, 6]
実行結果
[1, 2, 5, 0, 6]
1 パスが終わると、いちばん大きい要素(ここでは 6)が、いちばん後ろまで運ばれます。ただし、それ以外の部分([1, 2, 5, 0])はまだ並んでいません。
ここで考えてください。1 パスで「最大の要素が1 つ、後ろに確定する」なら、パスを何度も繰り返せば、大きい要素から順に後ろへ確定していき、最後にはリスト全体が並ぶはずです。この「1 パスを繰り返す」が、1 つ目の整列アルゴリズム バブルソート です。
2.3 1 パスをアルゴリズムとして読む¶
2.2 でやったばかりの「1 パス」を、1 章の規則でさっそく読んでみます。手順の形で書くと次のようになります。
- 数を入れたリスト
numbersを用意する。位置jを 0 とする。numbers[j]がnumbers[j + 1]より大きいならば、互いの値をtmpを使って入れ替える。jを1 増やす。jが「末尾の1 つ手前の位置」を超えていなければ、手順2 へ戻る。
たった3 つの手順ですが、1 章の3 点セットの規則で読むと構造が見えてきます。
| 手順 | 見つかる言葉 | コードでの形 |
|---|---|---|
| 1 | (変数の用意) | numbers = [6, 1, 2, 5, 0] |
| 1 & 3 | j を 0 とし、1 増やして「手順2 へ戻る」 |
for j in range(0, len(numbers) - 1): |
| 2 | 「〜ならば」 | if numbers[j] > numbers[j + 1]: と tmp を使った入れ替え3 行 |
2.2 のコードと見比べてください。「j を 0 とする」「j を1 増やす」「手順2 へ戻る」の3 点セット(手順1 と手順3)が for 文1 つに畳まれ、手順2 が if 文+入れ替えに、そのまま対応しています。逆に言えば、2.2 のコードを書けた人は、この手順を(無意識に)読み取って実装していたことになります。
3. バブルソート¶
まず、バブルソートを日本語の手順(アルゴリズム)として書きます。
- 数を入れたリスト
numbersを用意し、その長さをnとする。パスの番号iを 0 とする。- 位置
jを 0 とする。numbers[j]がnumbers[j + 1]より大きいならば、2 つをtmpを使って入れ替える。jを1 増やす。jがn - 1より小さければ、手順3 へ戻る。iを1 増やす。iがn - 1より小さければ、手順2 へ戻る。※ 手順2〜4 が1 パス。全部で
n - 1パス。1 パスが終わると、いちばん大きい要素がいちばん後ろに確定する
1 章の3 点セットの規則で読むと、3 点セットが2 組(j の手順2 & 4 と、i の手順1 & 5)、「〜ならば」が1 か所(手順3)あるので、for 文2 つと if 文1 つでできる、と見当が付きます。この後のコードとは、次のように対応します。
| 手順 | コードの対応する行 |
|---|---|
1. リスト numbers を用意し、長さを n とする |
numbers = [6, 1, 2, 5, 0] と n = len(numbers) |
1 & 5. i を 0 とし、1 増やして手順2 へ戻る |
外側の for i in range(0, n - 1): |
2 & 4. j を 0 とし、1 増やして手順3 へ戻る |
内側の for j in ...: |
| 3. 大きいならば、入れ替える | if numbers[j] > numbers[j + 1]: と、tmp を使った3 行 |
「戻る」が2 つあるときは、戻り先に注目 してください。手順4 は近く(手順3)へ、手順5 は遠く(手順2)へ戻ります。近くへ戻る手順4 の繰り返しが、遠くへ戻る手順5 の繰り返しの 中にすっぽり入っている ので、手順4 が内側の for 文、手順5 がいちばん外側の for 文になります。
3.1 1 パスを外側の for 文で繰り返す¶
2.2 の1 パスを、外側の for 文で繰り返します。第10回で学んだ2重ループの形そのままで、外側 i がパスの番号、内側 j がパスの中の位置 という役割分担です。
パスは何回繰り返せばよいでしょうか。1 パスごとに要素が1 つ後ろに確定するので、要素数を n とすると n - 1 回 で十分です(最後の1 個は、残りが確定すれば自動的に決まります)。
各パスの終わりに print(numbers) を入れて、リストが少しずつ並んでいく様子を観察します。
numbers = [6, 1, 2, 5, 0]
n = len(numbers) # 要素数(5)
for i in range(0, n - 1): # 外側: パスを n - 1 回(i = 0, 1, 2, 3)
for j in range(0, n - 1): # 内側: 2.2 の 1 パスそのまま
if numbers[j] > numbers[j + 1]: # 前のほうが大きければ
tmp = numbers[j] # tmp を使って入れ替える
numbers[j] = numbers[j + 1]
numbers[j + 1] = tmp
print(f"パス {i + 1} 後の配列: {numbers}") # パスが 1 回終わるごとに出力
実行結果
パス 1 後の配列: [1, 2, 5, 0, 6]
パス 2 後の配列: [1, 2, 0, 5, 6]
パス 3 後の配列: [1, 0, 2, 5, 6]
パス 4 後の配列: [0, 1, 2, 5, 6]
1 パス目で 6 が、2 パス目で 5 が、3 パス目で 2 が……と、大きい要素から順に、後ろへ確定していく のが分かります。小さい要素が泡(bubble)のように前へ浮かび上がっていく様子から、この方法を バブルソート と呼びます。
これで並べ替えは完成です。内側の for 文は2.2 の1 パスとまったく同じで、外側の for 文を1 行かぶせただけ、という点を確かめてください。
3.2 無駄な作業を減らして効率化¶
3.1 のプログラムには、少しむだがあります。1 パス目が終わった時点で最後の1 個は確定しているのに、2 パス目でも内側は最後のペアまで比べています。すでに確定した後ろの部分は、もう比べる必要がありません。
パス i が始まる時点で、後ろの i 個は確定しています。そこで内側を range(0, n - 1 - i) にすると、パスが進むごとに比べる範囲が1 つずつ狭くなります。第10回3 章の「内側の range を外側の変数で変える」書き方が、ここで役に立ちます。
numbers = [6, 1, 2, 5, 0]
n = len(numbers)
for i in range(0, n - 1): # 外側: パスを n - 1 回
for j in range(0, n - 1 - i): # 内側: 確定した後ろの i 個は比べない
if numbers[j] > numbers[j + 1]:
tmp = numbers[j]
numbers[j] = numbers[j + 1]
numbers[j + 1] = tmp
print(numbers) # 整列の完成形を出力
[0, 1, 2, 5, 6]
実行結果
[0, 1, 2, 5, 6]
結果は3.1 と同じですが、比べる回数が減っています(どれだけ減るかは5 章で数えます)。この
for i in range(0, n - 1):
for j in range(0, n - 1 - i):
if numbers[j] > numbers[j + 1]:
(入れ替え)
という形が、バブルソートの完成形です。下の図は、この流れをフローチャートで表したものです。ひし形が3 つ(外側の繰り返し/内側の繰り返し/大小の比較)現れる点と、内側のループが終わってから外側が1 つ進む点を確かめてください。
4. 選択ソート¶
4.1 最小値の位置(min_index)を探す¶
並べ替えにはバブルソート以外にもさまざまな方法があります。ここでは、同じ問題を解く2 つ目のアルゴリズム として 選択ソート を学びます。2重ループの骨格はバブルソートと同じですが、戦略が違います。考え方を説明するために、リストを 並べ終わった部分(先頭側) と まだ並べ替えていない部分(それより後ろ) の2 つに分けて考えます。最初は、リスト全体が「まだ並べ替えていない部分」です。選択ソートは、「まだ並べ替えていない部分の中からいちばん小さい要素を選んで(選択して)、その部分の先頭に置く」を繰り返します。置いた要素は「並べ終わった部分」の仲間入りをするので、並べ終わった部分が先頭から1 つずつ伸びていき、最後にはリスト全体が並びます。
その準備として、まず「リストの中で最小値が どの位置にあるか」を探します。第06回では最大値そのものを求めましたが、今回は入れ替えに使うので、値ではなく 位置(インデックス) を覚えておく必要があります。変数 min_index に「これまでで最小だった位置」を入れておき、より小さい要素を見つけるたびに更新します。
これも、まずアルゴリズムとして書きます。
- 数を入れたリスト
numbersを用意する。最小値の位置min_indexを 0 とし、位置jを 1 とする。numbers[j]がnumbers[min_index]より小さいならば、min_indexをjにする。jを1 増やす。jがリストの長さより小さければ、手順2 へ戻る。
3 点セット(手順1 & 3)が1 組なので for 文1 つ、「〜ならば」(手順2)が1 か所なので if 文1 つでできる、と見当が付きます。下のコードと見比べてください。
numbers = [6, 3, 8, 1, 5]
min_index = 0 # 最小値の位置。まず 0 番目とみなす
for j in range(1, len(numbers)): # 1 番目から末尾まで順に見る(0 番目をmin_index としているため1 から始める)
if numbers[j] < numbers[min_index]: # いままでの最小より小さければ
min_index = j # 最小値の位置を更新する
print(f"最小値の位置: {min_index}")
print(f"最小値: {numbers[min_index]}")
実行結果
最小値の位置: 3
最小値: 1
最小値 1 は3 番目にある、と分かりました。min_index には位置(3)が、numbers[min_index] にはその値(1)が入っている、という区別に注意してください。
4.2 選択ソートを完成させる¶
4.1 の「最小値の位置を探す」を組み込んで、選択ソート全体をアルゴリズムとして書きます。3 章のバブルソートと同じく、繰り返しが2 つある完全な形です。
- 数を入れたリスト
numbersを用意し、その長さをnとする。位置iを 0 とする。- 最小値の位置
min_indexをiとし、位置jをi + 1とする。numbers[j]がnumbers[min_index]より小さいならば、min_indexをjにする。jを1 増やす。jがnより小さければ、手順3 へ戻る。min_index番目の要素とi番目の要素を、tmpを使って交換する(これでi番目までが「並べ終わった部分」になる)。iを1 増やす。iがn - 1より小さければ、手順2 へ戻る。※ 手順2〜5 が1 パス
1 章の3 点セットの規則で読むと、3 点セットが2 組(j の手順2 & 4 と、i の手順1 & 6)あるので、バブルソートと同じ2重ループだと分かります。手順2〜4 は、4.1 の「最小値の位置を探す」そのものです(探しはじめが 0 番目から i 番目に変わっただけ)。下のコードとは、次のように対応します。
| 手順 | コードの対応する行 |
|---|---|
1. リスト numbers を用意し、長さを n とする |
numbers = [6, 3, 8, 1, 5] と n = len(numbers) |
1 & 6. i を 0 とし、1 増やして手順2 へ戻る |
外側の for i in range(0, n - 1): |
2 & 4. min_index と j を用意し、j を1 増やして手順3 へ戻る |
min_index = i と、内側の for j in range(i + 1, n): |
3. より小さいならば min_index を j にする |
if numbers[j] < numbers[min_index]: と min_index = j |
5. min_index 番目と i 番目を交換する |
内側の for 文を抜けたあとの、tmp を使った3 行 |
外側の i が「まだ並べ替えていない部分の先頭(=次に埋めたい位置)」、内側の j が「その部分を探索するときの位置」です。内側の探索は i 番目から後ろだけを見ればよいので、min_index = i から始めて range(i + 1, n) を調べます。第10回4 章の range(i + 1, ...) と同じ形です。
なお、手順2〜4 をまとめて「i 番目から末尾までの中から、最小値の位置 min_index を探す」と1 行で書く 粗い書き方 もあります。粗く書かれた手順を見たら、中身の3 点セット(この手順2〜4)を自分で組み立ててください。
下のコードでは、動きを観察するために、各パスの終わりに min_index と交換後の numbers を出力する行を足しています(print(min_index, numbers)。並べ替えそのものには不要な、観察用の行です)。
numbers = [6, 3, 8, 1, 5]
n = len(numbers)
for i in range(0, n - 1): # 外側: 確定させたい位置(i = 0, 1, 2, 3)
min_index = i # まず i 番目を最小とみなす
for j in range(i + 1, n): # 内側: i より後ろから最小値の位置を探す
if numbers[j] < numbers[min_index]:
min_index = j # 最小値の位置を更新する
tmp = numbers[i] # i 番目と最小値の位置を交換する
numbers[i] = numbers[min_index]
numbers[min_index] = tmp
print("最小値の位置:", min_index, "移動後のリスト:", numbers) # パスごとに位置とリストを出力(観察用)
実行結果
最小値の位置: 3 移動後のリスト: [1, 3, 8, 6, 5]
最小値の位置: 1 移動後のリスト: [1, 3, 8, 6, 5]
最小値の位置: 4 移動後のリスト: [1, 3, 5, 6, 8]
最小値の位置: 3 移動後のリスト: [1, 3, 5, 6, 8]
1 パス目で最小値 1(3 番目)が先頭に来て、以降は前から順に確定していきます。2 パス目の min_index は 1、つまり i と同じ位置なので、自分自身との交換になりリストは変わりません(間違いではありません)。
注意してほしいのは、交換(swap)が内側のループの外にある ことです。バブルソートは比べるたびに交換しましたが、選択ソートは 探し終わってから1 パスに1 回だけ 交換します。下の図は、この流れをフローチャートで表したものです。
4.3 バブルソートとの対比¶
2 つのソートを並べると、同じ2重ループの骨格 でできていることが分かります。
| バブルソート | 選択ソート | |
|---|---|---|
外側の i |
何パス目か | 確定させたい位置 |
内側の j |
range(0, n - 1 - i) |
range(i + 1, n) |
| 内側ですること | 隣同士を比べ、そのたびに交換 | 最小値の位置を探すだけ |
| 交換の場所 | 内側のループの 中(比べるたび) | 内側のループの 外(1 パスに1 回) |
| 確定していく場所 | 後ろ から(最大値から) | 前 から(最小値から) |
比較回数はどちらも 4 + 3 + 2 + 1 = 10 回で同じです(選択ソートの内側 range(i + 1, n) も、回数を数えると 4, 3, 2, 1 回です)。一方、交換回数は選択ソートでは 1 パスに1 回、合計 n - 1 回まで に抑えられます。この違いは、次の5 章で count 変数を入れて実際に数えて確かめます。
5. アルゴリズムの良し悪し: 比較回数と交換回数で比べる¶
5.1 count 変数で数える¶
バブルソートと選択ソートは、同じ問題を解く2 つのアルゴリズムでした。では、どちらが「良い」のでしょうか。アルゴリズムの良し悪しを比べる物差しの1 つが 操作の回数、つまり 比較した回数 と 実際に入れ替えた回数 です。まずバブルソートで数えます。第06回で学んだ「count = count + 1 で数える」書き方を2 か所に入れるだけです。
- 比較回数
compare_count: if 文で比べるたびに1 増やす(if 文の直前に置きます) - 交換回数
swap_count: 実際に入れ替えたときだけ1 増やす(if 文の中に置きます)
2 つの count を増やす場所の違い(if の外か中か)に注意してください。
# バブルソートの比較回数と交換回数を数える
numbers = [6, 1, 2, 5, 0]
n = len(numbers)
compare_count = 0 # 比較した回数
swap_count = 0 # 交換した回数
for i in range(0, n - 1):
for j in range(0, n - 1 - i):
compare_count = compare_count + 1 # 比べるたびに数える
if numbers[j] > numbers[j + 1]:
tmp = numbers[j]
numbers[j] = numbers[j + 1]
numbers[j + 1] = tmp
swap_count = swap_count + 1 # 交換したときだけ数える
print(numbers)
print("比較回数:", compare_count)
print("交換回数:", swap_count)
実行結果
[0, 1, 2, 5, 6]
比較回数: 10
交換回数: 7
比較回数の 10 は、内側の回数を足したもの、つまり 4 + 3 + 2 + 1 = 10 回です(3.1 の素朴版なら 4 × 4 = 16 回でした。n - 1 - i の短縮で 6 回減っています)。比較回数はリストの中身によらず一定ですが、交換回数はリストの並び方で変わります。最初から並んでいれば交換は0 回、逆順に並んでいれば比較のたびに交換が起こります。
いろいろなリストに変えて実行し、比較回数が変わらないこと/交換回数が変わることを確かめてください。
5.2 選択ソートと比べる¶
同じ数え方を4.2 の選択ソートに入れると、比較回数はバブルソートと同じ 10 回(内側の回数はどちらも 4 + 3 + 2 + 1)ですが、交換は 内側のループの外で1 パスに1 回だけ なので、交換回数は多くても n - 1 回に抑えられます(このリストにおいて、選択ソートなら 4 回、バブルソートなら 7 回)。
同じ問題を解いても、アルゴリズムによって操作の回数(=手間)が違う。これが「アルゴリズムの良し悪し」の最初の物差しです。
6. アルゴリズムを改造する: 降順に並べ替える¶
最後に、アルゴリズムを少しだけ 改造 します。ここまでは小さい順(昇順)に並べてきました。大きい順(降順)に並べたいときは、どこを変えればよいでしょうか。
答えは 比較の向きを変えるだけ です。バブルソートなら、numbers[j] > numbers[j + 1] の > を < に変えると、「前のほうが 小さければ 入れ替える」になり、大きい要素が前に集まります。変更はこの1 文字だけで、ループの形は一切変わりません。
numbers = [6, 1, 2, 5, 0]
n = len(numbers)
for i in range(0, n - 1):
for j in range(0, n - 1 - i):
if numbers[j] < numbers[j + 1]: # 向きを変えた(> を < に)
tmp = numbers[j]
numbers[j] = numbers[j + 1]
numbers[j + 1] = tmp
print(numbers)
[6, 5, 2, 1, 0]
実行結果
[6, 5, 2, 1, 0]
選択ソートでも同じで、numbers[j] < numbers[min_index] の < を > に変えれば「最大値の位置を探して前へ置く」降順ソートになります(そのときは変数名も max_index にすると読みやすくなります)。アルゴリズムの骨格はそのままで、比較の条件だけで並ぶ向きが決まる、という点を覚えておいてください。
7. フローチャートで流れをつかむ¶
この資料に載せたフローチャートの図は、ソートの流れを目で確かめるための ヒント です。今回の課題では、この流れを自分でフローチャートに組んで提出します(ツールの使い方は、課題の説明と別紙のクイックリファレンスを参照してください)。1 章で見たとおり、フローチャートは手順・コードと並ぶ 同じアルゴリズムの3 つ目の表現 です。
読み方のポイントは次の2 つです。3 章と4 章の図で確かめてください。
- ソートのフローチャートには ひし形が3 つ 現れます。外側の繰り返し/内側の繰り返し/大小の比較(if)の3 つです。第10回の2重ループの図に、if のひし形が1 つ増えた形です。
- バブルソートでは 入れ替えの3 ステップが if の「True」の先(内側のループの中)にあり、選択ソートでは 内側のループを抜けたあと にあります。4.3 の表の「交換の場所」の違いが、図では位置の違いとしてはっきり見えます。
コードを読んでいて流れが分からなくなったら、図の矢印を指でたどり、3.1 や4.2 の「パスごとの出力」と見比べてください。
まとめ¶
- アルゴリズム は、問題を解くための手順を順番に書いたもの。手順/フローチャート/コードは 同じものの3 つの表現。「はじめの値/進め方/続ける条件」の 3 点セット をまとめて for 文に畳む、戻り以外の「〜ならば」→ if 文、が翻訳の規則。
- バブルソート は、第09回の「1 パス」を外側の for 文で
n - 1回繰り返したもの。1 パスごとに最大の要素が 後ろ に確定する。内側をrange(0, n - 1 - i)にすると、確定した部分を比べ直すむだが省ける。 - 選択ソート は、まだ並べ替えていない部分(
i番目から末尾)の中から 最小値の位置min_indexを探し、その部分の先頭(i番目)と交換する。交換は1 パスに1 回だけで、先頭 から順に確定していく。 - バブルソートも選択ソートも 同じ2重ループの骨格。違いは「内側ですること」と「交換の場所」。
- アルゴリズムの良し悪しは 操作の回数 で比べられる。比較回数 は if の直前で、交換回数 は if の中で
count = count + 1すると数えられる。比較回数は2 つのソートで同じだが、交換回数は選択ソートのほうが少ない。 - 降順 にしたいときは、比較の向き(
>か<)を変えるだけでよい(アルゴリズムの小さな改造)。
今回いちばん身につけてほしいのは、個々のソートの暗記ではなく、アルゴリズム(手順)を読んで、for 文と if 文のコードに翻訳する 力です。授業後の課題で、この力を確かめてください。