有限個の操作で構成される問題の解決方法
最古のアルゴリズム
正整数 a と b について次の手順で最大公約数を求められる
etc, etc
分野ごとに特化したアルゴリズムが多い
「アルゴリズムとデータ構造」という場合は基礎知識
何かしらの形で使っている
7 million humans から
「アルゴリズムよりライブラリの使い方を覚えるべき!」
Pros
Cons
仕事ではよく使用・調査する
パフォーマンスの調査
コンパイラ関連
並列・平行・分散処理
次のような計算を考える
for (i = 0; i < n; i ++) {
for (j = 0; j < n; j++) {
c[i][j] = a[i] * b[j];
}
}
これをビッグオー記法にすると、
大きい項だけ残す
大きければ性能が悪い
計算量 | 例 |
---|---|
O(1) | ArrayList から値の取り出し |
O(log N) | 二分木の探索 |
O(N) | 単純なループ |
O(N log N) | クイックソート |
O(N ^ 2) | 2重ループ |
O(N!) | N 個すべての組み合わせの列挙 |
まず問題を把握する
それができてからどのアルゴリズムを使うか考える
ライブラリを依存関係が満たされる順番で列挙したい
Library 1
Library 2
依存関係を矢印で表す
ソートアルゴリズム
グラフアルゴリズム
文字列アルゴリズム
線形計画法
リストの整列を行うアルゴリズム
5
3
1
2
4
3
5
1
2
4
3
1
2
4
5
グラフに対するアルゴリズム
最短経路を求めるアルゴリズム
3
4
2
5
6
4
5
最短経路を求めるアルゴリズム
3
4
2
5
6
4
5
3
4
9
5
9
再帰
後戻り
自分自身を呼び出す関数
/* n の階乗を計算する */
int fact(int n) {
if (n == 1) return 1;
return n * fact(n - 1);
}
N個のブロックを別の軸に移動するのに何手順かかる?
難しそうだが、再帰的に考えられる
1
2
3
深さ優先探索での帰りがけ順にあたる
各段階でその段階に適した判断を行う
局所最適が存在しない場合は最適解を導ける
ex) ダイクストラ法
ただし実際に適用するのは難しい
分割: 問題を小さな部分問題に分割する
再帰: 部分問題を再帰的に解決する
統治: それらの解を適切に結合する
入力に対して出力が変わらない関数
→ 結果を保存・再利用できる
hast_t h;
int heavy_function(int a);
int memoize_function(int a) {
if (hash_exists(h, a)) {
return hash_get(a);
}
int ret = heavy_function(a);
hash_put(h, ret);
return ret;
}
キャッシュに近いが、計算結果は変わらない
= 一貫性は気にしなくて良い
分割統治法+メモ化
小問題間に依存がある場合、小問題をメモ化することで計算量を削減する
引用: https://xkcd.com/399/
この例では 14 回の関数呼び出しが必要
f(N) の計算結果は変わらない=>メモ化
int fibs[100] = {0};
int
fib(int n) {
int ret;
if (fibs[n] > 0) return fibs[n];
ret = fib(n - 1) + fib(n - 2);
fibs[n] = ret;
return ret;
}
1 ... N-1 まで問題を分割できる->分割統治法
int fibs[100] = {0};
fibs[0] = 0;
fibs[1] = 1;
int
fib(int n) {
for (i = 2; i <= n; i++) {
if (fibs[i] ==0)
fibs[i] = fibs[i - 1] + fibs[i - 2];
}
return fibs[n];
}
動的計画法は、
メモ化を使いながら表(分割された処理結果)を埋めていくイメージ
通常探索する空間は膨大になる(全探索)
探索する範囲を制限することで計算量を抑える
有名所だと TopCoder
Pros
Cons
引用: https://twitter.com/chokudai/status/1016244862344036352