情報処理(10) 2.コンピュータの仕組みkishou.u-gakugei.ac.jp › lectures › computer › lec02.pdf1 コンピュータとは •ノイマン型コンピュータ
コンピュータ基礎演習 ー探索、整列ー
-
Upload
stephen-daugherty -
Category
Documents
-
view
40 -
download
0
description
Transcript of コンピュータ基礎演習 ー探索、整列ー
線形探索と二分探索 線形探索法 (linear search)
データを最初から順番に探索する例) {2, 4, 5, 8, 9, 11, 6, 7, 15, 20} から 15 を探す 最初の要素から始めて9回目→計算量 O(n)
二分探索法 (binary search) データをあらかじめソートしておき,中央の要素から
検索する.
二分探索 (binary search) {2, 4, 5, 6, 7, 8, 9, 11, 15, 20} から 15を探す
{2, 4, 5, 6, 7, 8, 9, 11, 15, 20}1回目
二分探索 (binary search) {2, 4, 5, 6, 7, 8, 9, 11, 15, 20} から 15を探す
{2, 4, 5, 6, 7, 8, 9, 11, 15, 20}2回目
二分探索 (binary search) {2, 4, 5, 6, 7, 8, 9, 11, 15, 20} から 15を探す
{2, 4, 5, 6, 7, 8, 9, 11, 15, 20}
探索回数 3回計算量 log2(n) O(log n)
3回目
整列 (sort) 内部整列 (internal sort)
主記憶上で行う整列 外部整列 (external sort)
外部記憶装置(テープ等)上で行う
整列アルゴリズムの優劣→比較回数と交換回数の大小
安定な整列 (stable sort) 同じ値を持つデータ間の順序関係が整
列の前後で保たれている
例)整列前: 7 9 5A 4 8 5B 2 整列後: 2 4 5A 5B 7 8 9 (安定) 整列後: 2 4 5B 5A 7 8 9 (不安定)
単純な整列(バブルソート) 配列の後ろから先頭に向かって走査し,もし隣り合う二つ
の要素の大小関係が逆であったら入れ替える
1回目 20 6 55 74 3 13 45 30 87 46 20 6 55 3 74 13 45 30 87 46 20 6 3 55 74 13 45 30 87 46 20 3 6 55 74 13 45 30 87 46 3 20 6 55 74 13 45 30 87 46
単純な整列(バブルソート) 配列の後ろから先頭に向かって走査し,もし隣り合う二つ
の要素の大小関係が逆であったら入れ替える
2回目 3 20 6 55 74 13 45 30 87 46 46 87 30 46 30 45 13 30 13 74 13 55 6 13 6 20
単純な整列(バブルソート) 配列の後ろから先頭に向かって走査し,もし隣り合う二つ
の要素の大小関係が逆であったら入れ替える
2回目 3 6 20 13 55 74 30 45 46 873回目 3 6 13 20 30 55 74 45 46 874回目 3 6 13 20 30 45 55 74 46 875回目 3 6 13 20 30 45 46 55 74 876回目 3 6 13 20 30 45 46 55 74 877回目 3 6 13 20 30 45 46 55 74 878回目 3 6 13 20 30 45 46 55 74 879回目 3 6 13 20 30 45 46 55 74 87
バブルソート(疑似コード)
for (i←0..n-1) for (j←n-1..i) if !(a[j-1] ≦ a[j]) swap(a[j-1],a[j])
整列アルゴリズムが満たす事後条件 i,j[i < j] a[i] a[ j]
計算量は O(n2)
右辺値の交換
バブルソート(C言語)
for (i←0..n-1) for (j←n-1..i) if !(a[j-1] ≦ a[j]) swap(a[j-1],a[j])
void bubble_sort(int a[],int n) { int I,j,t; for (int I=0; I<n-1; ++I) for (int j=n-1; j>I; --j) if (a[j-1] > a[j]) { t = a[j]; a[j] = a[j-1]; a[j-1] = t; }}
swap(a[j-1],a[j]) に相当する
単純な整列(選択ソート) 未整列部分から最小の要素を選び出し,それを未整
列部分の先頭と入れ替える
整列アルゴリズムが満たす事後条件 i,j[i < j] a[i] a[ j]
a[0] a[1] a[i –1] a[i] a[n –1]整列済 未整列部分
未整列部分から最小値を取り出し続けると下記の条件を満たす
選択ソート
整列前 20 6 55 74 3 45 13 87 46 301回目 3 6 55 74 20 45 13 87 46 302回目 3 6 55 74 20 45 13 87 46 30
入替
選択ソート
整列前 20 6 55 74 3 45 13 87 46 301回目 3 6 55 74 20 45 13 87 46 302回目 3 6 55 74 20 45 13 87 46 303回目 3 6 13 74 20 45 55 87 46 304回目 3 6 13 20 74 45 55 87 46 305回目 3 6 13 20 30 45 55 87 46 746回目 3 6 13 20 30 45 55 87 46 747回目 3 6 13 20 30 45 46 87 55 748回目 3 6 13 20 30 45 46 55 87 749回目 3 6 13 20 30 45 46 55 74 87
選択ソート(C言語)
void selection_sort(int a[],int n) { int i,j,t,lowest,lowval; for (i = 0; i<n-1; ++i) { lowest = i; lowval = a[i]; for (j = i+1; j < n; ++j) if (a[j] < lowval) { lowval = a[j]; lowest = j; } t = a[I]; a[I] = a[lowest]; a[lowest] = t; }}
argmin の計算部分
単純な整列(挿入ソート) 配列の一部分が整列済みの時に,残りの要素
を一つずつ整列済みの中に挿入する整列前 20 6 55 74 3 45 13 87 46 301回目 6 20 55 74 3 45 13 87 46 302回目 6 20 55 74 3 45 13 87 46 303回目 6 20 55 74 3 45 13 87 46 304回目 3 6 20 55 74 45 13 87 46 305回目 3 6 20 45 55 74 13 87 46 306回目 3 6 13 20 45 55 74 87 46 307回目 3 6 13 20 45 55 74 87 46 308回目 3 6 13 20 45 46 55 74 87 309回目 3 6 13 20 30 45 46 55 74 87
挿入ソート(疑似コード)
for (i←0..n-1) j←i while (! (a[j-1] ≦ a[j]) ) swap( a[j-1], a[j] ) j←j-1
計算量:外側ループ O(n) 内側ループ O(n)合計: O(n2)
計算量 (complexity) 時間計算量 (time complexity)
アルゴリズムがデータに対してどれくらい時間がかかるかを示す
空間計算量 (space complexity) アルゴリズムがデータに対してどれくらい記憶領域を必要
とするかを示す
時間計算量と空間計算量はトレードオフの関係にあることが多い
計算機にとって記憶資源は潤沢にあるので,通常時間計算量の方が重きを置かれることが多い
オーダー記法O 計算量 T(n) の上界値を評価するとき, O(f
(n)) という記法を用い,オーダー f(n) と読む.
ある正定数 c と n0 が存在して, n0以上の n に対して,常に T(n) ≦cf(n) が成立するという意味n0 の役割は有限個の例外を許すことにある.
c,n0 c > 0, n n0 T(n) cf (n)
オーダーの演算 T1 = O( f (n)), T2 = O(g(n))
T1 + T2 = O(max( f (n),g(n)))T1 T2 = O( f (n) g(n))
例) T1 = O(n2), T2 = O(n3)T1 + T2 = O(n3)T1 T2 = O(n5)
最悪計算量と平均計算量 同じアルゴリズムでも入力するデータに応じ
て計算量が変化する.そこで,客観的に測定し評価する必要がある.
最悪計算量 worst case complexity 全ての入力パターンに対して最大の計算量を要す
るものに基づいて定める 平均計算量 average complexity
全ての入力パターンとその入力の生起確率に基づいて計算量の平均を求める
再帰 再帰的 (recursive) な構造とは,自分自身 (n次 ) を定義するのに,自分自身より 1次低い集合 (n-1次 ) を用い,さらにその部分集合は,より低次の部分集合を用いて定義するということを繰り返す構造.このような構造を一般的に再帰 (recursion) と呼ぶ.
再帰呼び出し 関数の中で自分自身を関数として呼び
出すこと
int A(int n) { if (n == 1) return 1; /* 再帰呼び出しの停止条件 */ else return A(n-1)+2*n-1;}
a1,a2,例)数列 a1 = 1
an = an – 1 + 2n – 1
再帰呼び出し例 階乗 (n!) の計算プログラム
f0 = 1fn = n fn – 1
#include <stdio.h>int factorial(int n) { if (n == 0) return 1; else return n*factorial(n-1);}int main() { int n; for (n=0; n<10; ++n) printf( “ %2d! = %d\n”, n, factorial(n) ); return 0;}
階乗 (n!) の計算実行例 0! = 1 1! = 1 2! = 2 3! = 6 4! = 24 5! = 120 6! = 720 7! = 5040 8! = 40320 9! = 362880
10! = 3628800 11! = 39916800 12! = 479001600 13! = 1932053504 14! = 1278945280 15! = 2004310016 16! = 2004189184 17! = -288522240 18! = -898433024 19! = 109641728
実行: PowerBook G4, MacOS X 10.2.5, gcc 3.1
演算結果が正しくない13! = 622702080014! = 8717829120015! = 130767436800016! = 2092278988800017! = 355568742809600018! = 6402373705728000019! = ?
int が 32ビットなら,正の数は231 = 2147483648までしか表現できない
演習課題 フィボナッチ数列を再帰を用いて求める
f1 = f2 = 1fn = fn – 1 + fn – 2
#include <stdio.h>int fibonacci(int n) { if (n == 1 || n == 2) return 1; else return fibonacci(n-1)+fibonacci(n-2);}int main() { int n; for (n=0; n<20; ++n) printf( “ %2d! = %d\n”, n, fibonacci(n) ); return 0;}
ADT Stack (スタック ) データの集まり 一番上の要素しか操作できない イメージ的には物を上に積んだ状態
スタックに可能な操作・一番上の要素の値を見る (top)・一番上の要素を取り除く (pop)・上に要素を積む (push)・スタックが空? (isEmpty)・スタックの要素数 (size)
連結リスト (Linked list) データの集まり データが一覧表(リスト)のように連なっている構造 連なりの表現には効率のため,ポインタが用いられることが多い もちろん,配列とインデックスでも実装できる(リスト容量が制限)
データ データ データ
DATA[0] DATA[1] DATA[2] … DATA[N]
データ 未使用 データ … データ
IDX[0] IDX[1] IDX[2] … IDX[N]
2 -1 N … EOD
連結リストの操作 要素の挿入 (insert) 要素の削除 (erase) リストの連結( concatenate ) リストの分断 (split) 要素の探索 (find) 空リストの生成 (create) 要素数 (size) 先頭要素を見る (front) 最後尾要素を見る (tail)
連結リストの操作 空リストの生成 (create)
要素のないリスト なぜ必要か?
要素をすべて削除したり,分断で空になったりする どう表す?
ヘッダを付加して表現 ヘッダのみの場合は空リストとして扱う
A B D
header
連結リストの操作 空リストの生成 (create)
要素のないリスト なぜ必要か?
要素をすべて削除したり,分断で空になったりする どう表す?
ヘッダを付加して表現 ヘッダのみの場合は空リストとして扱う
A B D
header
構造体とポインタを用いた連結リストの実装
構造体を利用する利点 データと次のデータへのポインタが同時に管理できる
ポインタを利用する利点 ヒープ領域を利用して動的にメモリを使用できる 固定のサイズに依存しない 要素の位置指定に利用できる
struct CELL { struct CELL *next; /* 次のデータへのポインタ */ DATATYPE data; /* 格納されるデータ */};
構造体とポインタを用いた連結リストの実装 空リストの生成 (create)
ヘッダを作成して初期化する
struct CELL myList; /* List を表すヘッダ変数 */myList.next = NULL; /* 空を示す */myList.data = 0; /* header の data は使用されないが, 一応 int 型として話を進める */
リストの生成はよく使う&要素の生成にも利用できるので関数にする
構造体とポインタを用いた連結リストの実装 空リスト ( 要素 ) の生成 (create)
ヘッダを作成して初期化する 作成したヘッダをポインタとして返す
struct CELL *create(DATATYPE data) { struct CELL *p = (struct CELL*)malloc(sizeof(struct CELL)); p->next = NULL; p->data = data; return p;}
struct CELL の格納領域をヒープ領域に確保する
空リスト(要素)生成の実装(C言語)
struct CELL *create(DATATYPE data) { struct CELL *p = (struct CELL*)malloc(sizeof(struct CELL)); p->next = NULL; p->data = data; return p;}
struct CELL の格納領域をヒープ領域に確保する
間違ったコーディング例)struct CELL *create(DATATYPE data) { struct CELL p; /* 局所変数 */ p.next = NULL; p.data = data; return &p; /* 関数からでた途端に局所変数は有効でなくなる */}
要素挿入の実装 指定された要素の次の位置に挿入する
要素の指定にはポインタを利用する 挿入する要素もポインタで指定する
void insert( struct CELL *pos, struct CELL *val ) { val->next = pos->next; pos->next = val;}
BA
posval
C
要素挿入の実装 指定された要素の次の位置に挿入する
要素の指定にはポインタを利用する 挿入する要素もポインタで指定する
void insert( struct CELL *pos, struct CELL *val ) { val->next = pos->next; pos->next = val;}
BA
posval
C
要素挿入の実装 指定された要素の次の位置に挿入する
要素の指定にはポインタを利用する 挿入する要素もポインタで指定する
void insert( struct CELL *pos, struct CELL *val ) { val->next = pos->next; pos->next = val;}
BA
posval
C
要素削除の実装 指定された要素の次を削除する
要素の指定にはポインタを利用する 削除された要素のポインタを返す
struct CELL *erase( struct CELL *pos ) { struct CELL *p = pos->next; if (p != NULL) { pos->next = p->next; p->next = NULL; } return p;}
BA
pos
C
要素削除の実装 指定された要素の次を削除する
要素の指定にはポインタを利用する 削除された要素のポインタを返す
struct CELL *erase( struct CELL *pos ) { struct CELL *p = pos->next; if (p != NULL) { pos->next = p->next; p->next = NULL; } return p;}
BA
pos
C
p
要素削除の実装 指定された要素の次を削除する
要素の指定にはポインタを利用する 削除された要素のポインタを返す
struct CELL *erase( struct CELL *pos ) { struct CELL *p = pos->next; if (p != NULL) { pos->next = p->next; p->next = NULL; } return p;}
BA
pos
C
p
tail の実装
A B
header
リストの最後尾を返す
struct CELL *tail( struct CELL *pos ) { while (pos->next != NULL) pos = pos->next; return pos;}
pos
tail の実装
A B
header
リストの最後尾を返す
struct CELL *tail( struct CELL *pos ) { while (pos->next != NULL) pos = pos->next; return pos;}
pos
tail の実装
A B
header
リストの最後尾を返す
struct CELL *tail( struct CELL *pos ) { while (pos->next != NULL) pos = pos->next; return pos;}
pos
tail の実装
A B
header
リストの最後尾を返す
struct CELL *tail( struct CELL *pos ) { while (pos->next != NULL) pos = pos->next; return pos;}
リスト連結の実装 二つのリストのヘッダを渡す 連結されたヘッダを返す
A B
D
header
C
struct CELL *concatenate( struct CELL *L1, struct CELL *L2 );
リスト連結の実装 二つのリストのヘッダを渡す 連結されたヘッダを返す
A B
D
header
C
struct CELL *concatenate( struct CELL *L1, struct CELL *L2 ) {struct CELL *p = tail( L1 );insert( p, L2->next ); L2->next = NULL;return L1;
};
L1
L2
リスト連結の実装 二つのリストのヘッダを渡す 連結されたヘッダを返す
A B
D
header
C
struct CELL *concatenate( struct CELL *L1, struct CELL *L2 ) {struct CELL *p = tail( L1 );insert( p, L2->next ); L2->next = NULL;return L1;
};
L1
L2
p
リスト連結の実装 二つのリストのヘッダを渡す 連結されたヘッダを返す
A B
D
header
C
struct CELL *concatenate( struct CELL *L1, struct CELL *L2 ) {struct CELL *p = tail( L1 );insert( p, L2->next ); L2->next = NULL;return L1;
};
L1
L2
p
リスト連結の実装 二つのリストのヘッダを渡す 連結されたヘッダを返す
A B
D
header
C
struct CELL *concatenate( struct CELL *L1, struct CELL *L2 ) {struct CELL *p = tail( L1 );insert( p, L2->next ); L2->next = NULL;return L1;
};
L1
L2
p
要素探索の実装 要素を比較し等価な要素を探索 最初に見つかった要素の位置を返す なければ NULL を返す
struct CELL *find( struct CELL *p, DATATYPE val );
A B
headerp
C
要素探索の実装 要素を比較し等価な要素を探索 最初に見つかった要素の位置を返す なければ NULL を返す
struct CELL *find( struct CELL *p, DATATYPE val ) {p = p->next;while (p != NULL)
if (isEqual(p.data, val)) return p; else p = p->next;return p;
}
A B
headerp
C
要素探索の実装 要素を比較し等価な要素を探索 最初に見つかった要素の位置を返す なければ NULL を返す
struct CELL *find( struct CELL *p, DATATYPE val ) {p = p->next;while (p != NULL)
if (isEqual(p.data, val)) return p; else p = p->next;return p;
}
A B
headerp
C
要素探索の実装 要素を比較し等価な要素を探索 最初に見つかった要素の位置を返す なければ NULL を返す
struct CELL *find( struct CELL *p, DATATYPE val ) {p = p->next;while (p != NULL)
if (isEqual(p.data, val)) return p; else p = p->next;return p;
}
A B
headerp
C
要素探索の実装 要素を比較し等価な要素を探索 最初に見つかった要素の位置を返す なければ NULL を返す
struct CELL *find( struct CELL *p, DATATYPE val ) {p = p->next;while (p != NULL)
if (isEqual(p.data, val)) return p; else p = p->next;return p;
}
A B
headerp
C
要素探索の実装 要素を比較し等価な要素を探索 最初に見つかった要素の位置を返す なければ NULL を返す
struct CELL *find( struct CELL *p, DATATYPE val ) {p = p->next;while (p != NULL)
if (isEqual(p.data, val)) return p; else p = p->next;return p;
}
A B
headerp
C
要素探索の実装 要素を比較し等価な要素を探索 最初に見つかった要素の位置を返す なければ NULL を返す
struct CELL *find( struct CELL *p, DATATYPE val ) {p = p->next;while (p != NULL)
if (isEqual(p.data, val)) return p; else p = p->next;return p;
}
A B
headerp
C
要素探索の実装 要素を比較し等価な要素を探索 最初に見つかった要素の位置を返す なければ NULL を返す
struct CELL *find( struct CELL *p, DATATYPE val ) {p = p->next;while (p != NULL)
if (isEqual(p.data, val)) return p; else p = p->next;return p;
}
A B
headerp
C
要素探索の実装 要素を比較し等価な要素を探索 最初に見つかった要素の位置を返す なければ NULL を返す
struct CELL *find( struct CELL *p, DATATYPE val ) {p = p->next;while (p != NULL)
if (isEqual(p.data, val)) return p; else p = p->next;return p;
}
A B
headerp
C
要素探索の実装 要素を比較し等価な要素を探索 最初に見つかった要素の位置を返す なければ NULL を返す
struct CELL *find( struct CELL *p, DATATYPE val ) {p = p->next;while (p != NULL)
if (isEqual(p.data, val)) return p; else p = p->next;return p;
}
A B
headerp
C
等価判定関数 isEqual の実装 等価判定
二つのデータが等しいかどうか 演算子 = は基本データ型にしか利用できない
DATATYPE の中身で等価判定関数は異なる(実装,設計により異なる)
等価とはどういうことか定義が必要
int isEqual(DATATYPE d1, DATATYPE d2); d1 と d2 が等価ならば !0 そうでないならば 0 を返す
循環リストの操作 要素の挿入・削除
連結リストの場合と同様 要素の追跡 (trace)
基本的に連結リストと同様 最後尾が NULLでは判定できないので工夫
が必要→追跡を開始した要素に到達したら終了