情報工学概論 (アルゴリズムとデータ構造) 第4回 実習用環境 • C言語 – 学習用C言語開発環境 http://9cguide.appspot.com/ • Fortran 90 – Self-extracting Windows x86 http://www.g95.org/downloads.shtml http://ftp.g95.org/g95-MinGW.exe 2 集合を扱うデータ構造 • • • • 集合:数学,計算機科学において基本的 動的集合:要素が追加/削除/変更される 集合に対して行う操作によってデータ構造を変える 行いたい操作によって最適なデータ構造が決まる 行う操作 データ構造 挿入,削除, 存在判定 挿入 最小要素の取出し 辞書 プライオリティー キュー 3 動的集合の基本 • 各要素はオブジェクトとして表現される • オブジェクトはキーと付属データからなる • 集合の操作で扱うフィールドがあってもよい – アルゴリズム内部でのみ用いられる – 他のオブジェクトへのポインタなど • キーは全順序を持つとする場合もある 4 動的集合に関する操作 1. 集合に関する情報を返す質問 (query) • • • • • SEARCH(S,k): key[x] = k である S の要素 x へのポ インタを返す.存在しなければ NIL. MINIMUM(T): 全順序集合 T において,最小のキー を持つ要素を返す MAXIMUM(T): 全順序集合 T において,最大のキー を持つ要素を返す SUCCESSOR(T,x): キーが x のキーの次に大きな 要素を返す.x が最大なら NIL. PREDECESSOR(T,x): キーが x のキーの次に小さ 5 な要素を返す.x が最小なら NIL. 動的集合に関する操作 2. 集合を変える修正操作 (modifying operation) • • • • INSERT(S,x): 集合 S に要素 x を加える. DELETE(S,x): x へのポインタが与えられたとき,S から x を取り除く. SUCCESSOR,PREDECESSORは同じキー が複数ある集合にも拡張される 集合操作を実行するのにかかる時間は集合の サイズで測る 6 11.1 スタックとキュー • 動的集合,挿入と削除をサポートする • スタック (stack) では,DELETEでは最後に 挿入された要素が取り除かれる – 後入れ先出し (last-in, first-out; LIFO) という • キュー (queue) では,最初に挿入された要素 が取り除かれる – 先入れ先出し (first-in, first-out; FIFO) という 7 スタック (Stack) • INSERT, DELETEの代わりにPUSH, POPと呼 ぶ – PUSH(S,x): 集合 S に要素 x を加える. – POP(S): S から最後に PUSH された要素を削除し, その要素を返す PUSH(S,15) POP(S) 15 692 9 2 6 15 8 配列によるスタックの実装 • 最大 MAX 要素を格納できるスタックを実装 • スタックを表す構造体 – top: 最後に挿入された要素の格納場所(= 要素数) – S: 要素を格納するサイズMAXの配列(へのポインタ) – MAX: 配列 S のサイズ typedef int data; • 要素は S[1..top] に格納される – S[1]: スタックの底 – S[top]: スタックの最上部 – top MAX typedef struct { int top; int MAX; data *S; 9 } stack; 実装例 • PUSH(S,x) – topを1増やし,x を配 列に入れる – topがMAXを超えたら エラー – O(1) 時間 void PUSH(stack *s,data x) { if (s->top == s->MAX) { printf("オーバーフロー\n"); exit(1); } s->top = s->top + 1; s->S[s->top] = x; } • POP(S) – スタックが空ならエラー – サイズを1減らし,最上部 の要素を返す – O(1) 時間 data POP(stack *s) { if (STACK_EMPTY(s)) { printf("アンダーフロー\n"); exit(1); } s->top = s->top - 1; return s->S[s->top + 1]; 10 } その他の関数 • STACK_EMPTY(S) – スタックが空なら1を返す – O(1) 時間 int STACK_EMPTY(stack *S) { if (S->top == 0) return 1; else return 0; } • MAKE_STACK(size) – 最大サイズ size のス タックを作成する stack *MAKE_STACK(int size) { stack *s; data *S; s = malloc(sizeof(stack)); S = malloc((size+1)*sizeof(data)); s->top = 0; s->MAX = size; s->S = S; return s; } 11 キュー (Queue) • INSERT, DELETEの代わりにENQUEUE, DEQUEUEと呼ぶ • 人の並んだ列と同じ – ENQUEUEでは列の最後に追加 – DEQUEUEでは列の先頭を取り出す DEQUEUE(Q) Q 15 6 2 ENQUEUE(Q,x) 9 12 配列によるキューの実装 • 最大 MAX1 要素を格納できるキュー typedef struct { • スタックを表す構造体 int head; int tail; int MAX; data *Q; } queue; – Q: 要素を格納する配列 – MAX: 配列 Q のサイズ – head: キューの先頭の位置 – tail: 次に追加される位置 1 2 3 4 5 6 7 8 15 6 head 9 10 11 12 9 8 4 tail 13 環状バッファ (Ring Buffer) • 配列 Q の右端と左端はつながって輪になっている と考える • Q[MAX] の右は Q[1] だとみなす • 要素は Q[head..MAX] Q[1..tail1] に格納される • DEQUEUEの際に全要素を左にずらす必要がな い 1 2 3 5 tail 3 4 5 6 7 8 15 6 head 9 10 11 12 9 8 4 17 14 空のキューの作成 • MAKE_QUEUE(size) – size 個の要素を格納できる空のキューを作成 queue *MAKE_QUEUE(int size) { queue *q; data *Q; size = size+1; q = (queue *)malloc(sizeof(queue)); Q = (data *)malloc((size+1)*sizeof(data)); q->head = 1; q->tail = 1; q->MAX = size; q->Q = Q; return q; } 15 実装例 • ENQUEUE(Q,x) – x をQ[tail]に入れる – tailを1増やす – O(1) 時間 void ENQUEUE(queue *q,data x) { q->Q[q->tail] = x; if (q->tail == q->MAX) q->tail = 1; else q->tail = q->tail + 1; } • DEQUEUE(Q) – Q[head]を取り出す – headを1増やす – O(1) 時間 data DEQUEUE(queue *q) { data x; x = q->Q[q->head]; if (q->head == q->MAX) q->head = 1; else q->head = q->head + 1; return x; 注: オーバーフロー, アンダーフロー } 処理は省略してある 16 11.2 連結リスト (Linked Lists) • オブジェクトをある線形順序に並べて格納する データ構造 • 連結リストでの線形順序は,オブジェクトが含む ポインタで決定される • 双方向連結リスト (doubly linked list) L の要素 – キーフィールド key – ポインタフィールド prev, next – (付属データ) key head(L) 9 16 prev next NIL 4 17 • next(x): リスト中の x の直後の要素のポインタ – next(x) = NIL のとき,x は最後の要素 • prev(x): x の直前の要素のポインタ – prev(x) = NIL のとき,x はリストの最初の要素 • head(L): リストの先頭の要素のポインタ – head(L) = NIL のとき,リストは空 NIL head(L) 9 16 prev 4 18 リストの種類 • 一方向 (singly linked) と双方向 (doubly linked) – 一方向のとき,各要素は prev を持たない • 既ソート (sorted) と未ソート – 既ソート: リストの線形順序はキーの線形順序に対応 – 未ソート: 任意の順序 • 循環 (circular list) と非循環 – 循環: リストの先頭要素の prev はリストの末尾を指し, 末尾の next はリストの先頭を指す • 以下では未ソート双方向(連結)リストを扱う 19 双方向リストの構造体 • リストの要素 typedef struct lobj { struct lobj *prev; // 前の要素へのポインタ struct lobj *next; // 後の要素へのポインタ data key; // キー } lobj; • 双方向リスト typedef struct { lobj *head; } dllist; // 先頭要素のポインタ 20 省略記法 #define HEAD(L) (L->head) #define KEY(x) (x->key) #define NEXT(x) (x->next) #define PREV(x) (x->prev) #define NIL NULL 21 連結リストの探索 • LIST_SEARCH(L, k): リスト L に属する,キー k を持つ最初の要素のポインタを返す • キー k を持つ要素が存在しなければ NIL を返す lobj *LIST_SEARCH(dllist *L, data k) • (n) 時間 { lobj *x; x = HEAD(L); while (x != NIL && KEY(x) != k) { x = NEXT(x); } return x; } head(L) 9 16 4 22 連結リストへの挿入 • LIST_INSERT(L, x): x を L の先頭に挿入 – x のキーは既にセットされているとする • O(1) 時間 x 9 head(L) head(L) 9 void LIST_INSERT(dllist *L, lobj *x) { NEXT(x) = HEAD(L); if (HEAD(L) != NIL) PREV(HEAD(L)) = x; HEAD(L) = x; PREV(x) = NIL; } 16 4 16 4 23 連結リストからの削除 • LIST_DELETE(L, x): L から x を削除 • O(1) 時間 void LIST_DELETE(dllist *L, lobj *x) { if (PREV(x) != NIL) NEXT(PREV(x)) = NEXT(x); else HEAD(L) = NEXT(x); if (NEXT(x) != NIL) PREV(NEXT(x)) = PREV(x); } head(L) 9 head(L) 9 x 16 4 4 24
© Copyright 2024 ExpyDoc