1 / 97

基礎的なアルゴリズム

基礎的なアルゴリズム. ・ 再帰呼び出し ・ 分割統治とバランス化 ・ グラフ探索 ・ 列挙法 ・ 動的計画. 再帰呼び出し. サブルーチンを作る. ・ サブルーチンとは、いわばプログラムの部品 ・ プログラム実行中、そこに行き一定の処理をして、またもとの場所に帰ってこれる ・ 通常プログラムを作る際には、作業を階層的に「部品化」し、設計をやりやすくする - 作業単位がわかる - 全体の見通しがよくなる ・ サブルーチンを実行することを、 呼び出す という. 再帰呼び出し. ・ 通常の部品の概念では、部品の中に同じ部品が入ることはない

Download Presentation

基礎的なアルゴリズム

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. 基礎的なアルゴリズム ・ 再帰呼び出し ・ 分割統治とバランス化 ・ グラフ探索 ・ 列挙法 ・ 動的計画

  2. 再帰呼び出し

  3. サブルーチンを作る ・サブルーチンとは、いわばプログラムの部品 ・ プログラム実行中、そこに行き一定の処理をして、またもとの場所に帰ってこれる ・通常プログラムを作る際には、作業を階層的に「部品化」し、設計をやりやすくする - 作業単位がわかる - 全体の見通しがよくなる ・サブルーチンを実行することを、呼び出すという

  4. 再帰呼び出し ・通常の部品の概念では、部品の中に同じ部品が入ることはない ・ しかし、サブルーチンは、自分自身を呼び出せる (サブルーチンが自分自身を呼び出すことを再帰呼び出しという) ・自分を呼び出すと、サブルーチンの最初から、呼び出すところまでを繰り返すループになる  for ループと同じものが作れる sub (int i){ printf (“%d\n”, i); // i の値を表示 if ( i<100 ) sub(i+1); }

  5. 練習問題 ・次のループを再帰呼び出しで書け。擬似コードでよい  - 10 から 20 までを表示  - 50 から 20 までの偶数を表示  - 1,2,…,9,10,10,9,…,1という順番で数字を表示。ただし、ひとつのサブルーチンで  - m×n の長方形を “#” で表示

  6. 複数回の再帰呼び出し ・for ループは単純に繰り返すだけだが、再帰呼び出しは、1つのサブルーチンが自分を複数回呼び出すことができる sub (int i){ printf (“%d\n”, i); // i の値を表示 if ( i<100 ){ sub(i+1); sub(i+1); } } ・ どのように動くのだろうか?  1レベル目が1回、2レベル目が2回、3レベル目が4回、と指数的に実行解数が増える

  7. 2分木のような計算構造 ・1つのサブルーチン実行を1つの点で書き、再帰的に呼び出した関係があるところに線を引く ・2分木の構造が得られる ・3回呼び出せば3分木、 4回呼び出せば4分木、 ・呼び出しの回数が変化すれば、より複雑な構造になる  いろいろな計算ができる

  8. 組合せ的な処理の例 ・例:8文字の○×△からなる文字列を全て表示 char s[9]; sub (int i){ if ( i = = 8 ){ s[8] = 0; printf (“%s\n”, s); // ○×△の表示 } else { s[i] = ‘o’; sub(i+1); s[i] = ‘x’; sub(i+1); s[i] = ‘A’; sub(i+1); } }

  9. 組合せ的な検索 ・再帰呼び出しの計算構造は、(逆さにした)木のようである ・このような、計算の構造を表した木を計算木、あるいは再帰木という ・このように、処理を分けることを分枝するという

  10. 分枝の例 ・a[0],…,a[9]の組合せで、合計がちょうど bになるものを表示 int a[10], flag[10]; sub (int i, int s){ int j; if ( i = = 10 ){ if ( s != b ) return; for (j=0 ; j<10 ; j++ ) if (flag[j] = = 1) printf (“%d ”, a[j]); // 数 の表示 printf (“\n”); } else { flag[i] = 1; sub(i+1, s+a[i]); flag[i] = 0; sub(i+1, s); } }

  11. 分枝限定法 ・分枝操作を行うと、全ての組合せを検索できる ・しかし、全ての組合せのうち、一部しか見る必要がないのであれば、全て見るのは無駄 ・見る必要のない部分を省略したい ・分枝作業をした後、「見る必要のない再帰呼び出し」は呼び出さないようにしよう ・ 必要(可能性)のない分枝を切ることを、限定操作という 限定操作をしながら分枝する方法を、分枝限定法という

  12. 限定操作の例 ・a[0],…,a[9]の組合せで、合計が b以下になるものを表示 int a[10], flag[10]; sub (int i, int s){ int j; if ( s > b ) return; // 限定操作! if ( i = = 10 ){ for (j=0 ; j<10 ; j++ ) if (flag[j] = = 1) printf (“%d ”, a[j]); // 数の表示 printf ("\n"); } else { flag[i] = 1; sub(i+1, s+a[i]); flag[i] = 0; sub(i+1, s); } }

  13. 分割統治とバランス化

  14. 問題の分割 ・人間が問題を解く(作業をする)場合、問題なりやることなりを分割して、小さくしてやることがある 見通しを良くする、という点と、やりやすくする、という点 ・例えば、部屋の片づけのしかた -まず、各部屋にしまうものを分類 -次に各部屋の中を、個別に整理 ・さらに各部屋の片づけをする際に、本棚や机、それぞれにしまうものを集めてから、それぞれをやっつける ・ このように再帰的に問題を分割して解く方法を分割統治法という

  15. 分割統治法の構造 ・分割統治法を、手法としてしっかり見てみる ・問題が与えられると、 -まず、問題を分割できるように整形する -問題を分割する(できた問題を子問題という) - 各子問題を解く(再帰呼び出し) - 各子問題の解を合わせて、もとの問題の解を作る ・ 問題によっては、必ずしも全ての手続きが必要なわけではない

  16. 分割統治法の例 ・先の、再帰呼び出しの例はだいたい分割統治法といってよい ・合計がちょうど bになる組合せを求める i番目の数を含む組合せを求める問題と、そうでない組合せを求める問題に分割している 整形と分割、統合に関して、複雑な処理が必要ない

  17. ソート ・分割統治法を使うと、効率良く解ける問題がいくつかある ・基本的な例がソート ・数値 a[0],…,a[n] を小さい順に並び替える問題を考える ・戦略として、次の手順を考える - まず、数値を a[0],…,a[k] と a[k+1],…,a[n]に分割する -それぞれをソートする -ソートしてできた列を合わせて、全体のソート列を得る   (この操作をマージ(合併・併合)という) -この操作を再帰的に行うことで、ソートをする

  18. マージの例 ・例題 1,9,5,3,7,6,2,0,8,4 - 1,9,5,3,7,6 と 2,0,8,4 に分割 -それぞれをソートする: 1,3,5,6,7,9 と 0,2,4,8 -マージする (両者を同時に先頭から見ていく) 1,3,5,6,7,9 と 0,2,4,8 0,1,2,3,4,5,6,7,8,9 ・マージは線形時間でできる

  19. マージをするプログラムの例 ・配列 a,b の数列をマージして、配列 c に格納 ・配列 a,b の最後に、大きな数 HUGEが入っているとする { int ia, ib=0, ic=0; for ( ia=0 ; a[ia]<HUGE; ia++){ while (b[ib]<a[ia] ){ c[ic] = b[ib]; ic++; ib++; } c[ic] = a[ia]; ic++; } }

  20. マージソートをするプログラムの例 ・配列 a,b 両方に数列を格納しておくと、配列 a にソートした列が入る merge_sort (int *a, int *b, int s, int t){ int i1, i2, ia; if ( s = = t ) return; merge_sort (b, a, s, (s+t)/2 ); merge_sort (b, a, (s+t)/2, t); for ( i1=s,i2=(s+t)/2,ia=s ; i1<(s+t)/2; i1++){ while ( i2<t && b[i2]<b[i1] ){ a[ia] = b[i2]; ia++; i2++; } a[ia] = b[i1]; ia++; } while ( i2<t ){ a[ia] = b[i2]; ia++; i2++; } }

  21. 再帰呼び出しの様子 ・マージソートの再帰呼び出しの様子は、2分木になる merge_sort (int *a, int *b, int s, int t){ int i1, i2, ia; if ( s = = t ) return; merge_sort (b, a, s, (s+t)/2 ); merge_sort (b, a, (s+t)/2, t); for ( i1=s,i2=(s+t)/2,ia=s ; i1<(s+t)/2; i1++){ …

  22. 分割は、「同じ大きさ」が正解? ・ 先ほどのマージソートは配列をだいたい同じ大きさに分割していたが、もっといい分割はあるか?  分割のしかたを変えると、速さは変わるのか? ・ まず、2つ目の疑問は、Yes ・ マージソートの各反復の計算時間は、ソートする配列の大きさに線形 O(n) ・ もし、毎回 「1個と残り」という分割をすると、配列の長さは毎回1ずつ小さくなる 計算時間は 1+・・・+nに比例、つまり O(n2)

  23. 「同じ大きさ」が正解 ・ 毎回ちょうど半分に分割すると、配列の長さは毎回半分+1以下になる O(log n) 回分割すると長さが1になる 各レベルで計算時間を合計すると、O(n) になるので、合計はO(nlog n) ・ 分割の仕方で計算量のオーダーが変わった ・ これは最適なんだろうか?  ソートの計算時間の下界と一致するので、最適

  24. 分割すればいい、と言うものではない ・ 分割ができるからといって、分割統治法を使えば速くなる、というものでもない a[1],…,a[n] の中から最大のものを見つける問題は、分割しても解けるが、速くならない  - a[1],…,a[k] と a[k+1],…,a[n] に分割してそれぞれの最大を再帰的に求める  - 両最大値のうち、大きいほうを全体の最大値として返す ・計算木の各頂点で O(1) しか時間 をかけないので、どう分割しても O(n) 時間になる

  25. クイックソート ・ マージソートは、分割前に何もせず、分割後にがんばる -まず、問題を分割できるように整形する -問題を分割する(できた問題を子問題という) - 各子問題を解く(再帰呼び出し) - 各子問題の解を合わせて、もとの問題の解を作る ・逆のアイディアでソートができないか? ・ クイックソートと言う方法がある

  26. 分割の前にがんばる ・ まず、数値をある数 kより大きなものと小さなものに分割する ・それぞれを小さい順に並べる ・ 終わると、全体が小さい順になっている k

  27. k との大小で分割 ・ 数値を kとの大小比較で分割するにはどうすればいいか?  - 右からスキャンして kより小さいものを見つけて止まる  - 左からスキャンして kより大きいものを見つけて止まる ・両者を入れ替える ・ 繰り返す。スキャンしている場所がすれ違ったら終わる

  28. 分割のバランスがとれない ・ kの値によって、分割してできる子問題の大きさは変わる ・しかし、真ん中の値(中央値)を見つけるのは、簡単ではない ・しょうがないので、適当なものを選ぼう 最悪の場合、1つと残り、という分割を繰り返すので  計算時間は O(n2) になる ・ しかし、例えば、分割が 1:9 以上になる確率は 2/10 で、 こういうことは、起こりにくい。そんな分割がたくさん起こることは、なおない。平均的には、 O(nlog n) になる

  29. 合計が最も大きい区間を求める ・ a[0],…,a[n-1] の区間 a[h],…,a[k] 中で、合計が最も大きいものを求める (負の値があると、全体=最大とはかぎらない) ・実験の時系列データの解析など? 各区間の取り方は O(n2) なので、素朴に計算すると O(n3) 時間になる 各区間の合計の計算の仕方を工夫する(左端を固定して、右側をひとつずつずらし、新しく入ってくる要素を今までの合計に足し込む)と O(n2) になる ・ 分割統治法を使うと、速く解ける

  30. 分割して計算 ① a[0],…,a[n-1] を a[0],…,a[k-1] と a[k],…,a[n-1]に分割 ②右端が a[k-1] となる区間の中で最大を求める ③左端が a[k] となる区間の中で最大を求める a[k] と a[k] を両方含む区間の中で最大なものは、②と③を合わせると求まる ④再帰呼び出しで、a[0],…,a[k-1] の中の最大と、a[k],…,a[n-1] の中の最大を求める ・ ①と②と③ はO(n) 時間 問題を半分に分割するようにすれば、マージソートと同じ計算量になり、全体の計算時間は O(nlog n)

  31. ~余談~: k番目を線形時間で見つける ・ クイックソートは、問題を分割するときに、だいたい真ん中になる数が知りたい ・ こういう、数の中から k番目にあるものを見つける問題を k best 問題という ・ 簡単にやろうとしたら、数字をソートすればよい (本末転倒だが) ・ だが、凝った方法で、ソートせずに線形時間の方法がある

  32. ~余談~: k番目を線形時間で見つける 2 ・ 入力した数を a[0],…,a[n-1] とする ・ 基本的な戦略は、数 xを選び、 a[0],…,a[n-1] を xをより大きなものと小さなものに分ける ・ xより小さいものが k個以上あったら、k番目はxより小さい xより小さいものの中で k番目を見つける ・ xより大きいものが k個以上あったら、k番目はxより大きい xより大きいものの中で n-1-k番目を見つける ・ という具合に小さくしていく。効率は xの選び方しだい

  33. ~余談~: k番目を線形時間で見つける 3 ・ xを選ぶのには、「いいかげんな」 中央値を使う (クイックソートにも、いいかげんな中央値で十分だが) ・ a[0],…,a[n-1] 中を7個ずつに分け、それぞれの7個の中で中央値を見つける(定数時間が n/7 回で O(n) 時間) ・ 見つけた中央値の中でさらに中央値を見つける   (1/7 の大きさの問題を再帰的に解く) ・ これは、真ん中 1/4 以上はじっこに行くことはない   (3/4 の大きさの問題を再帰的に解く、上と合わせて 25/28 )

  34. ~余談~: k番目を線形時間で見つける 4 ・ それぞれの7個の中で中央値より、半分のものは大きい ・ 半分のもののさらに半分は、●より大きい ・ 半分のもののさらに半分は、●より小さい ・ よって、真ん中 1/4 以上はじっこに行くことはない 大 小

  35. ~余談~: k番目を線形時間で見つける 5 ・ 問題の大きさの合計が、毎回 25/28 になる ・ 問題を分けるのにかかる時間は O(n) ・ 全体の計算時間は O(n + (25/28)n + (25/28)2n + (25/28)3n …) = O(n)

  36. 行列積の計算量

  37. 行列のかけ算 ・ 行列の積を求める演算は、行列の操作の中でも基本的なものだが、計算に比較的時間がかかる (次元の3乗のオーダー) ・うまいことやって、もっと速くできないだろうか? × =

  38. 分割統治法は使えるか? ・ ソートでは、問題を分割して高速化した。行列も問題を分割したらうまく解けるかも ・うまいことやって、もっと速くできないだろうか? ・分割するといっても、どのように分割する?どのように解く? ・まずはどのように分割するか、分割した問題をどのように解くかを考えないと × =

  39. 簡単な分割 ・ 左の行列は横長の行列2つに、右の行列は縦長の行列2つに分けてみよう ・横長と縦長の行列の組合せ1つの積を求めると、答えの1ブロックが埋まる 4回積を求めると、もとの行列の積が求まる ・でも、「正方行列の積」が「正方でない行列の積」になるので、気持ちが悪いなあ × =

  40. もう少し分割 ・ 両行列ともに4つに分割する ・1/4 ブロックの積を2つ求め、足すと、1ブロック埋まる 正方行列の積を8回求めると、もとの行列の積が求まる 同じ問題を解いているので、再帰呼び出しできる ・さて、これは速くなっているのでしょうか??? 解析してみないと、よくわからない。。。 AE +BG AF +BH A B E F × = CE +DG CF +DH C D G H

  41. 計算時間の再帰式 ・ 計算が再帰的なので、計算時間の再帰式を作ろう ・ この方法でn次元行列のかけ算をする時間を T(n)とする ・ 問題を分割したり、積の足し算をして解を求める時間は O(n2)であるので、定数を使って、cn2時間であるとする ・ 再帰は、大きさ n/2 の問題について 8回行われるので、 T(n) = cn2 + 8T(n/2) となる AE +BG AF +BH A B E F × = CE +DG CF +DH C D G H

  42. 計算時間の再帰式 ・T(n) = cn2 + 8T(n/2)を満たす関数 T(n) とは、どのような関数であろうか? ・ 図示して考えてみよう (簡単のため、n = 2kであるとする) ・ 大きさ nの問題を受け取ったら、cn2 の計算時間をかけ、 8 個の大きさ n/2の問題に関して再帰呼び出しが起こる ・ (8個の)大きさ n/2の問題は、それぞれ c(n/2)2 の計算時間をかけ、それぞれ 8 個の、大きさ n/4の問題を作る ・ (64個の)大きさ n/4の問題は... (以下省略) ・ (???個の)大きさ 1の問題が呼び出される

  43. 計算時間の再帰式 ・ 大きさ nの問題を受け取ったら、cn2 の計算時間をかけ、 8 個の大きさ n/2の問題に関して再帰呼び出しが起こる... ・ 1レベル下がると大きさは 1/2になり、計算時間は 2倍に増える 全体の計算時間は一番下のレベルの2倍を超えない ・ k = log2n レベル下がると、問題の大きさが 1になり、再帰呼び出しは起こらない そのレベルでの計算時間の合計は cn2 ×2k = cn3 ・ 計算時間は、O(cn3) ・ 苦労したのに何もかわらないや。。。

  44. 再帰式、一般的には ・T(n) = cn2 + 8T(n/2)の n2 の部分や、8の部分、n/2 の 2の部分が変化すると、関数 T(n) のオーダーも変化する ・ どのように変わるか、少し調べてみよう ・ n2 の部分、8の部分が変わると、1レベル下がったときの計算時間の増加量が変わる  -8の部分が 2 になると、1レベル下がると計算時間が半分に  全体の時間はトップレベルの2倍を超えず、O(n2)  -8の部分が 4 になると、各レベルでの計算時間は等しくなる  全体の計算時間はトップレベルの高さ倍となり、O(n2 log n)

  45. 再帰式、一般的には  - n2 が nになると 1レベル下がるごとに計算時間は4倍になる  一番下のレベルの計算時間の合計はトップレベルの n2倍になる。しかし、トップレベルの計算時間が O(n) であるので、掛け合わせると O(n3)  - n2 が n4になると、1レベル下の計算時間は半分になる  全体の計算時間はトップレベルの2倍を超えず、 O(n4)  - n2 が n3になると、各レベル下の計算時間は等しくなる  全体の計算時間は O(n3 log n)

  46. 再帰式、一般的には  - n/2が n/4になると、1レベル下がるごとに計算時間は1/2になり、さらにレベルの高さが半分に  計算時間の合計はトップレベルの2倍以下。O(n2)  - n/2が n/4になり、n2 が nになると、1レベル下がるごとに計算時間は2倍になる。レベルの高さが半分になる  一番下のレベルでは、トップレベルの n1/2 倍。O(n3/2)

  47. 再帰式、一般的には ・ 一般に、T(n) = cna + bT(n/c)という再帰式が成り立つとき、  - a > logcbならば、トップレベルが全体の計算時間を支配し、計算量は O(na) となる  - a < logcbならば、一番下のレベルが全体の計算時間を支配し、計算量は O(nlogc b) となる  - a = logcbならば、各レベルの計算時間は等しくなり、計算量は高さのファクターが入り、O(nalog n) となる

  48. 行列積の高速化 ・ 先の問題の分割では、答えの行列をブロックごとに求めていた その結果、8回のかけ算を必要としている 行列積は a < logcb (2 < log28)の場合であるので、 再帰呼び出しの回数 8を減らすか、子問題の大きさ n/2 を小さくしなければならない ・ 工夫して、再帰の数、つまりかけ算の数を減らした人がいる AE +BG AF +BH A B E F × = CE +DG CF +DH C D G H

  49. 分配則の利用 ・ 行列を足してからかけると、行列積の和が得られる 例)(A + B)E = AE + BE B(G – E) = BG – BE ・ これらをうまく組合わせて足し合わせると、目的である行列積が出てくる 例)(AE + BE) + (BG – BE) = AE+BG AE +BG AF +BH A B E F × = CE +DG CF +DH C D G H

  50. 再帰式の変化 ・ うまい和積取り方と組合せ方を考えて、7回の行列積で、4つのブロックの解が得られるようにした ・ 計算時間の再帰式は、T(n) = cn2 + 8T(n/2)から T(n) = cn2 + 7T(n/2)になった ・log27 = 2.80… なので、およそ O(cn2.80) 時間になる ・ この方法で一番すごいやつは、100個以上の行列に分解して同様のことを行い、O(cn2.31…) 時間を達成 AE +BG AF +BH A B E F × = CE +DG CF +DH C D G H

More Related