講義用スタイル
印刷用スタイル
Algorithm intro. using nipamo

プログラミング言語「にぱも」 (nipamo) を使ったアルゴリズム入門

はじめに

この文章は、プログラミング言語「にぱも」を学んだプログラミング初心者の ための、アルゴリズムの入門、というより 導入 のための文書です。 言いたいことはただひとつ、 「アルゴリズムを考えるということが大事 である」 です。これまでに先人がいろいろなアルゴリズムを考えてい て、我々はそれを簡単に利用することが出来るわけですが、そのためにはアル ゴリズムというものに対する知識が必要です。この文書はそのさわりの部分だ けですが紹介して、今後の読者の学習の方向性だけでも示せればと思います。

そもそもアルゴリズムとは?

"Algorithm" とは、辞書的には「算法」などと訳されることもありますが、計 算・問題解決のための、特にコンピュータプログラムにおける 手 順 のことです。プログラムが書けて、それが動いて、正しい結果さえ得 られればよいわけではなく、いかにより良いアルゴリズムで書かれるかが大事 です。この「より良い」がポイントです。処理時間の観点が重要視されますが、 他にもプログラムの記述の簡単さ=バグの入りにくさも大切です。

手順が大事、の例

例えば「1から10000までの数の合計を求めなさい」という問題をとくプ ログラムを作成することを考えてみます。

なにも考えないプログラム

一番単純な書き方は、
アを1とする
アに2を足す
アに3を足す
#
# ... ひたすら続ける
#
アに10000を足す
アを出力する
です。が、これだとプログラムが10001行も必要です。コーディングが大 変だし、書き損なう可能性もあります。

繰り返しをつかう

同じようなことを繰り返すのだから、我々は繰り返し構文を知っているわけで、 それを使えばコーディングがすごく簡単になります。つまり、以下になります。
アを0とする
10000までのそれぞれについて繰り返す
 アにそれを足す
アを出力する

もっと簡単に計算できる?

実はもっと簡単に計算できます。1から10000までの数を2つ用意して、 逆に並べたものと足してみましょう。つまり;
     1+    2+    3+...+ 9998+ 9999+10000
+10000+ 9999+ 9998+...+    3+    2+    1
----------------------------------------
 10001+10001+10001+...+10001+10001+10001
となり、10001 が 10000 個あることになります。これが、1から10000 までの数の2倍だったわけなので、求める数は
10001x10000/2 = 50005000
となります。これは100まででも100000 でも一緒で、要するに最後の数と、最 後の数に1を足したものを掛けて2で割ればよいので、プログラムとしては以 下になります。
アを10000とする
イをアとする
イに1を加える
アにイを掛ける
アを2で割る
アを出力する
ちなみにこれはガウス が小学生のときに求めたという逸話で有名(実際 は微妙に違うという説 もあり)です。等差数列の和の公式でもあります。
繰り返しの場合、1万までの和を求めるには1万回の足し算が必要ですが、上 の場合、いくつまでの和でも5回の計算ですむので計算の量としてはすごく効 率がよいわけです。このような、 効率のよい手順 (を考えること) が、すなわちアルゴリズムなわけです。

よくある例:ソート (sort)

数学の公式はさておき、コンピュータでよく出てくるような処理は、先人たち の知恵=アルゴリズムが蓄積されています。その中でもソート(並び替え)や サーチ(探索)はかなり研究がなされています。ここではソートの代表的なア ルゴリズムについて紹介します。

バブルソート

以下のプログラムをみてください。
ハイレツを【5、6、9、2、7、3、8、1、4】とする
ナガサを ハイレツの長さとする #空白を挟む(!)
0以上ナガサ未満のそれぞれをアとして繰り返す
 イを1とする
 イにアを足す
 イ以上ナガサ未満のそれぞれをウとして繰り返す
  もしハイレツのア番目がハイレツのウ番目以下ならば
   ハイレツのア番目とハイレツのウ番目を交換する
0以上ナガサ未満のそれぞれをアとして繰り返す
 ハイレツのア番目を出力する
このプログラムは2重のループ=繰り返しになっていることが分かると思いま す。「ア」は外側の、「ウ」は内側のループのカウンタ=今、配列のどの要素 を操作しているかを覚えておく変数です。「イ」は、アの1つ次を示していま す。

一番最初の時には、「ア」は0番目、「イ」は1番目、「ウ」も1番目です。 そして、「ウ」は繰り返すたびにカウントアップ=1づつ増えていって、 ハイレツのア番目とハイレツのウ番目を比べて、ア番目が小さければ ア番目とウ番目を入れ替えています。つまり、配列の最初は

【5、6、9、2、7、3、8、1、4】
ですが、この5と、そのあとの数字を比べて小さければ大きいものと交換する ので、1回目には
6、5、9、2、7、3、8、1、4】
となり、次には
、5、、2、7、3、8、1、4】
となり、あとの 2,7,3,8,1,4 は9より小さいので 交換しません。つまり、最初のループで、この配列の中で 一番大きな数字を一番左(0番目)に置きました。結果として 配列は
、5、6、2、7、3、8、1、4】
となっています。

ここで、「ア」を1つ増やします。すなわち、配列の一番左は配列全 体の中で一番大きなものであることが決まっているので、その次に大きい ものを探しにいく、ということです。そうすると、次のループが終わった ときには、

、5、2、6、3、7、1、4】
となり、これを繰り返すわけですので
9、8、2、5、3、6、1、4】
【9、8、7、2、3、5、1、4】
【9、8、7、6、2、3、1、4】
【9、8、7、6、5、2、1、3】
【9、8、7、6、5、4、1、2】
【9、8、7、6、5、4、3、1】
と交換がすすんで最後まで行って終了、となります。

このバブルソートの場合、9つの要素に対して比較(と、必要に応じて)並び 替えをするので、8+7+6+5+4+3+2+1 = 9*8/2 回の処理が必要となります。要素 が少ないときは問題にはなりませんが、要素が多いと、その 2乗程 度 の計算量が必要となり、かなり効率が悪いです。
この「2乗程度」のような計算量の 次数 のことを 「オー ダー」 と呼び、 O(n2) のように記述することがあります。

クイックソート

クイックソートは、平均的に素早く (quick) 並び替えを行なうように考えら れたもののうちの代表的なアルゴリズムです。以下にコードを示します。
関数クイックを引数ハで定義する
 ナガサを ハの長さとする #空白を挟む(!)
 もしナガサが1以下ならば
  ハを返す
 ではなくナガサが2と同じならば
  もしハの0番目がハの1番目以下ならば
   ハの0番目とハの1番目を交換する
  ハを返す
 ではなければ
  ナガサを2で割る
  ナガサを整数にする
  ナカをハのナガサ番目とする
  ダイを空配列とする
  ショウを空配列とする
  ハのそれぞれをアとして繰り返す
   もしアがナカより大きいならば
    ダイにアを付け足す
   ではなくアがナカと違うならば
    ショウにアを付け足す
  カエリを関数クイックを引数ダイで呼び出したものとする
  カエリにナカを付け足す
  カエリに関数クイックを引数ショウで呼び出したものを合わせる
  カエリを返す
#
ハイレツを【5、6、9、2、7、3、8、1、4】とする
カエリチを関数クイックを引数ハイレツで呼び出したものとする
カエリチを出力する
クイックソートは、名前の通り早くソートすることが可能なソートアルゴ リズムです。特に要素が多い時に効果を発揮します。クイックソートは、 再帰関数の形で定義されます。再帰関数とは、自分の定義の中に自分が入っ ていて呼び出される(これが再帰呼び出し)関数のことです。

まず、データは

【5、6、9、2、7、3、8、1、4】
要素数は9でランダムに並んでいます。 これをクイックソートを使用して降順にソートします。 まず、この9個の要素の中からひとつの要素を選びます。 ここでは、要素数の中間の値であるものを選びます。 コードにすると、
  ナガサを2で割る
  ナガサを整数にする
  ナカをハのナガサ番目とする
つまり、配列の長さの半分ですから、最初は4、だけど0から始まってるので 5番目の要素が中間値となります。つまり、7です。そして、この7を基準値 にしてデータ数列をソートしていきます。ちなみにこの基準値、つまり軸とな るものをピボットと呼びます。
【5、6、9、2、、3、8、1、4】
次に、このピボットより大きいものと小さいものに分類します。 その受け皿用の変数として「ダイ」と「ショウ」を用意します。 そして、データ数列の先頭からピボットと比較して、 大きいものは「ダイ」に、小さいものは「ショウ」に付け足していきます。 最初のチェックでは、以下のようになります。
ダイ=【9、8】
ショウ=【5、6、2、3、1、4】
そして、以下のようにそれぞれに対して「クイック」を適用します;
  カエリを関数クイックを引数ダイで呼び出したものとする
  カエリにナカを付け足す
  カエリに関数クイックを引数ショウで呼び出したものを合わせる
「ダイ」については、
【9、8】
の2つだけですから
 ではなくナガサが2と同じならば
  もしハの0番目がハの1番目以下ならば
   ハの0番目とハの1番目を交換する
  ハを返す
を適用して、ここでのソートは終了です。その結果、データは変らず「カエリ」(深さ1)は以下です。
【9、8】
そしてこれに後で「ナカ」を付け足して、さらにこれに「ショウ」をソートし た結果を付け足すわけです。すなわち;
【5、6、2、3、1、4】
についてクイック(深さ2)を適用すると、ピボットが3になり、分類すると
ダイ=【5、6、4】
ショウ=【2、1】
になります。この「ダイ」に再度クイック(深さ3)を適用すると、
ダイ=【】
ショウ=【5、4】
となりますので、クイック(深さ3)のカエリは
【】+6+【5、4】=【6、5、4】
になります。よって、クイック(深さ2)のカエリは
【6、5、4】+3+【2、1】=【6、5、4、3、2、1】
となります。 さらに、これをカエリ(深さ1)に付け足すので、最終結果は以下になります;
【9、8】+7+【6、5、4、3、2、1】=【9、8、7、6、5、4、3、2、1】

以上のようにソートしていけば、降順にソートされることがわかります。今回 は、ピボットを真ん中の値としましたが、ソートに関して、効率のよい値をピ ボットにするのが細かい工夫になります。

クイックソートの場合、平均的に O(n・log(n)) の計算量である ことが知られています。バブルソートの O(n2) と比 べると、 n が大きい場合はクイックソートのほうが圧倒的に有利 です。例えば、n=10000 の場合、
n2=100000000 (108)
ですが、
n・log(n) = 10000・log210000 ≒ 1.3・105
くらいなので、800倍近くの速度差になります。

他には…

「にぱも」のキラーアプリ(?)として、 「エラトステネスの篩(ふるい)」 というものがあります。これは、素数を見付ける代表的なアルゴリズムです。 ソースコードは以下です。
# エラトステネスのふるい
#
サイダイを百とする
リミットをサイダイの平方根とする
0以上サイダイ未満をハイレツに入れる
ハイレツの1番目を0とする
ハイレツのそれぞれをアとして繰り返す
    もしアが0と同じならば
        続ける
    ではなくアがリミット以上ならば
        抜ける
    そうじゃなければ
    イにアを入れる
    イに2を掛ける
    イからアおきにサイダイ未満のそれぞれをウとして繰り返す
            ハイレツのウ番目を0とする
ハイレツのそれぞれをアとして繰り返す
    もしアが0と違うならば
    アを改行出力する
いま、国から 研究費を貰っているのですが、自分としては↑が動いてい るというのが決め手になったんじゃないかと思っています。
これは、素数ではないデータを篩にかけて落していって、残ったものが素数で ある、という処理をしています。じっくり読んでみてください。

おわりに

とりあえず現状の「にぱも」でアルゴリズムということを簡単に説明しました。 これ以外の複雑なプログラムについては、ぜひ専門書を勉強してください。 世界がずっと広がると思います。
文責:前田としゆき <<< ...modified on 2021.07.29

講義用スタイル
印刷用スタイル
(開いてから、ページを再度更新してください)