データ構造とアルゴリズム 第5回 スタック ~ データ構造(2)~ 1 前回の解答 ROOT 0093 0091 番地 内容 0094 009D 0000 番地 0106 内容 0093 番地 010E 内容 0095 ROOT 00FF 0100 0107 0108 010F 010A 0100 0091 0108 0094 0110 0096 0101 0106 0109 010E 0111 0112 0102 009C 010A 009A 0112 0098 0103 0104 010B 010C 0113 0114 0104 009D 010C 009B 0114 0099 0105 0000 010D 0102 0115 0000 2 前回の復習と補足 抽象データ型としての「 」 一定の型の要素を0個以上一列に並べたもの 参照,挿入(Insert), 削除(Delete)の操作を行える リストの実現方法 配列を用いる方法 : 「要素の配列」と「最後の要素 の位置を示す変数」で実現 ポインタを用いる方法 : 「要素」と「次のセルを指す ポインタ」でセルを構成し,セルを順次つなぐことで, 連結リストを作成し,実現 3 本日の内容:スタック スタック 抽象データ型としてのスタック 配列によるスタックの実現 単方向リストによるスタックの実現 スタックの利用例 ポーランド記法,逆ポーランド記法 再帰呼び出し(リカーシブ・コール,recursive call) 5 スタック 宿題をする 本を調べる 電話に出る 心の中の スタック 本 宿題 宿題 宿題 時間の経過 6 スタック(Stack) スタック:要素の挿入や削除がいつも一方の端 ( )からしかできないリスト ( )要素ほど,先に出る 別名 スタックの (top) → (last-in first-out) push down list スタックの → (bottom) a4 a3 a2 a1 7 抽象データ型としてのスタック 抽象データ型: データ構造+操作 スタック 操作 先頭への要素の挿入 要素を 並べたもの 先頭からの要素の取出し 8 スタックの操作 (S ):空スタックSを準備しその先 頭の位置を返す (x, S ):要素xをSの先頭に入れる (S ):先頭の要素を削除する <同時にSの先頭の要素を返す場合もある> 9 単方向リストによるスタックの実現 Push, Popを行うために使用する へのポインタ init an a1 an-1 スタックの底の要素が格納され ているセルのnextは 10 スタックの型定義(単方向リスト版)と PUSHのプログラム例(p33) /* セルを表わす構造体の定義 */ struct cell セルのデータ構造はリストのときと同じ。 { ここではint型の要素としたが、どのような int element 型でも良い。 struct cell *next }; … main() struct cell型への … ポインタを返す関数 struct cell *push(int x, struct cell *init) { struct cell *q, *r; r = (struct cell *)malloc(sizeof(struct cell)); q = init; init =r; r->element = x; r->next = q; return(init); } 11 Pushの実現(単方向リスト版) Pushが始まる直前 ( ) Push(x, S) [struct cell *push(int x, struct cell *init)] ① ② ③ ④ ⑤ 新しいセル用のメモリを確保し、rはそのセルを指すポインタとする initをqに代入する(=initが指すアドレスをqに保持しておく) initにrを代入する(=initが新しい要素のセルを指すようにする) 新しいセルに要素xを代入 r->nextが,前の先頭要素のセルを指すようにする init an an-1 a1 r ① r = (struct cell *)malloc(sizeof(struct cell)); 12 Pushの実現(単方向リスト版) Push(x, S) ① ② ③ ④ ⑤ 新しいセル用のメモリを確保し、rはそのセルを指すポインタとする qにinitを代入する(=前の先頭要素へのポインタをqに保持しておく) initにrを代入する(=initを新しい要素用セルへのポインタとする) 新しいセルに要素xを代入 r->nextが,前の先頭要素のセルを指すようにする init q ③ init = r; r ② q = init; an an-1 a1 13 Pushの実現(単方向リスト版) Push(x, S) ① ② ③ ④ ⑤ 新しいセル用のメモリを確保し、rはそのセルを指すポインタとする qにinitを代入する(=前の先頭要素へのポインタをqに保持しておく) initにrを代入する(=initを新しい要素用セルへのポインタとする) 新しいセルに要素xを代入 r->nextが,前の先頭要素のセルを指すようにする init q an an-1 a1 ④ r->element = x; r ⑤ r->next = q; 14 POPのプログラム例 (p33) スタックが空でない場合は … struct cell型へのポインタを返 main() す関数 … 空の場合はエラーメッセージ struct cell *pop(struct cell *init) { struct cell *q; if(init !=NULL) { q = init; init = init->next;free(q); return(init); } else { printf(“Error: Stack is empty.\n”); exit(1); } return; } exit文はプログラムを終了させる標準関数で,この関数が0を返せば正常終了,それ以外を返せば異常終了と 判断される.exit文を使用する場合には,#include <stdlib.h>が必要である. 15 Popの実現(単方向リスト版) init Popが始まる直前 initはあるリストの先頭要素を指す. an an-1 a1 Pop(S) init ① qにinitを代入する(=前の先頭要素へのポインタをqに保持しておく) ② initを前の先頭要素の次(=前の2番目)へのポインタとする ③ 削除するセルの領域を解放 ④ initがNULLならエラーメッセージを表示して関数を終了 init ① q = init; q an an-1 a1 16 Popの実現(単方向リスト版) Pop(S) initがNULLではない場合以下を実行 ① ② ③ ④ init qにinitを代入する(=前の先頭要素へのポインタをqに保持しておく) initを前の先頭要素の次(=前の2番目)へのポインタとする 削除するセルの領域を解放 initがNULLならエラーメッセージを表示して関数を終了 q an an-1 ② init = init->next; a1 17 Popの実現(単方向リスト版) Pop(S) initがNULLではない場合以下を実行 ① qにinitを代入する(=前の先頭要素へのポインタをqに保持し ておく) ② initを前の先頭要素の次(=前の2番目)へのポインタとする ③ 削除するセルの領域を解放 ④ initがNULLならエラーメッセージを表示して関数を終了 init q ③ free(q); an an-1 a1 ④ printf(“Error: stack is empty.¥n”);exit(1); 18 スタックの型の定義(配列版) #define N 100 /* 配列の最大サイズ */ struct stack /* 構造体stackの定義 */ { int top; int element[N]; }; … main() … データ構造はリスト(配列版)のときと基本的には同じ. タグ名,メンバー名を変更しただけ. 19 配列によるpushのプログラム例(p34) void push(int x,struct stack *S) { if(S->top >=N || S->top < 0) S->top = N; if(S->top == 0) { printf(“Error: Stack is full.\n”);exit(1); } else { S->top = S->top-1; S->element[s->top]=x; } return; } 20 配列によるスタックの実現 element [0] topはスタックの先頭位置を示すint型変数 ポインタ型変数ではない (矢印の根元に注意!教科書は一貫性がない。 図2.5はいいが、図2.9はポインタと同じ?!) [1] an( top [N-1]に,スタックの底を 固定する点がポイント. 要素を挿入/削除するたびに リスト全体を上げ下げしな くて済む. ) an-1 a2 [N-1] a1( ス タ ッ ク が 伸 び る 方 向 ) 21 Pushの実現 (配列版) N=7の場合 初期状態 element Stack型:スタックの要素が入る配列element スタックの先頭位置を示すtop から成る構造体で、elementには既に要素あり [0] [1] [2] (つまりtopの先頭アドレスを指す) [3] Sはstack型を指すポインタ 準備ができたら S top 5 push関数を呼ぶ void push(int x,struct stack *S) [4] C [5] B2 A [6] A A1 22 Pushの実現 (配列版) ①if(S->top >=N || S->top < 0) S->top = N; element element [0] [1] S [2] top ? [3] [4] S [5] top topが7以上又はtopが負 ならスタックは空 その場合はtop=7とする ? [6] [0] [1] [2] [3] [4] S top [5] [6] 23 Pushの実現 (配列版) topが0ならば、スタックは満杯。 エラーメッセージを表示して関数 top を終了 ② N=7の場合 element [0] A7 [1] A6 [2] A5 [3] A4 if(S->top == 0) [4] A3 { printf(“Error: Stack is full.\n”); [5] A2 exit(1); } [6] A1 24 Pushの実現 (配列版) それ以外の場合: topを1減らす topが指しているelementの位置に 要素xを挿入 N=7の場合 element [0] [1] [2] [3] ③ else { S->top = S->top-1; S->element[s->top]=x; } top [4] top 5 [5] A [6] A 25 配列によるpopのプログラム例(p34) void pop(struct stack *S) { if(S->top < N) { S->top = S->top + 1; } else { printf(“Error: Stack is empty.\n”); exit(1); } return; } 26 Popの実現 (配列版) N=7の場合 element topが7未満なら要素はある その場合は topを1増やす [0] [1] [2] ① if(S->top < N) { S->top = S->top + 1; } [3] [4] C 5 ① top [5] B B [6] A 27 Popの実現 (配列版) それ以外(1未満N以上)なら エラーメッセージを表示して関数を終了 N=7の場合 element [0] [1] [2] else { printf(“Error: Stack is empty.\n”); exit(1); } [3] [4] C [5] B 7 ? [6] A 28 配列によるスタックの実現(参考) [0] elements a1 ( ) [1] a2 スタックの底を,配列 のインデクス[0]に固 定してもよい. この場合,上下を逆 にする点に注意. an-1 top an( ) ス タ ッ ク が 伸 び る 方 向 [MAXLENGTH-1] 29 練習問題 Push Pop 次の手順で操作を行うと スタックS1,S2の中身は 最終的にどのようになるか? Push Pop ① Push(A,S1) ② Push(B,S2) ③ Push(C,S1) ④ D=Pop(S1)で削除した要素 ⑤ Push(D, S2) ⑥ Pop(S1) S1 S2 30 練習問題 Push Pop Push Pop ① Push(A,S1) ② Push(B,S2) ③ Push(C,S1) ④ D=Pop(S1)で削除 した要素 ⑤ Push(D, S2) ⑥ Pop(S1) S1 S2 31 練習問題 Push Pop Push Pop ① Push(A,S1) ② Push(B,S2) ③ Push(C,S1) ④ D=Pop(S1)で削除 した要素 ⑤ Push(D, S2) ⑥ Pop(S1) S1 S2 32 練習問題 Push Pop Push Pop ① Push(A,S1) ② Push(B,S2) ③ Push(C,S1) ④ D=Pop(S1)で削除 した要素 ⑤ Push(D, S2) ⑥ Pop(S1) S1 S2 33 練習問題 Push Pop Push Pop ① Push(A,S1) ② Push(B,S2) ③ Push(C,S1) ④ D=Pop(S1)で削除 した要素 ⑤ Push(D, S2) ⑥ Pop(S1) S1 S2 34 スタックの利用例1:算術式の評価 大切 予備知識: 数式の表記法 前置表現(prefix notation) ポーランド記法(polish notation) 中置表現(infix notation) 通常の数式表記法 後置表現(postfix notation) 逆ポーランド記法(reverse polish notation) ポーランド記法,逆ポーランド記法では括弧 [“(”と“)”]を使わずに数式を表現できる 35 歴史 ポーランド記法は,ポーランドの論理学者 Jan Łukasiewicz(1878-1956) が1920年に 提案 出典http://www.calculator.org/Lukasiewicz.html 1957年には,オーストラリアの計算機科学 者Charles L. Hamblin (1922-1985) がNew South Wales University of Technologyで, スタックを実装 出典http://www.csc.liv.ac.uk/~peter/hamblin.html 36 A+Bの表記法 前置表現[ ] 演算子を変数の に置く 例:+AB 足すことのAB 演算子の優先順位は存在しない.括弧が不要. 中置表現 演算子を変数の に置く 例:A+B A足すB 演算子の優先順位が存在する.括弧が必要. 後置表現[ ] 演算子を変数の に置く 例:AB+ AとBを足す 演算子の優先順位は存在しない.括弧が不要. 37 後置表現への変換 (A–B) * (C+D) (式1) 演算子 (式2) (A–B) (C+D)* (式1) 演算子 (式2) AB- (C+D)* AB–CD+* 個々の式を 後置表現に する 後置表現 38 その他の数式の例 前置表現 ポーランド記法 中置表現 通常の表記法 後置表現 逆ポーランド記法 A+B+C (A-B)*(C+D) 4*7-(5+3) A-B÷C 39 練習問題 ア イ ウ エ 逆ポーランド記法では,例えば,式(A-B)*Cを AB-C*と表現する.次の式を逆ポーランド記法で 表現したものはどれか (A+B)*(C-D÷E) AB+CDE÷-* AB+C-DE÷* AB+EDC÷-* BA+CD-E÷* 40 スタックの利用例1:算術式の評価 ◆ ◆ ◆ スタックを使うと,逆ポーランド記法で書かれた 式を左から順に読んで演算処理できる. 処理手順 一要素ずつ読み込み,要素が“数”ならスタック にPush “演算子”ならスタックから2つの要素をPopして 演算し,演算結果をスタックにPush 最終的にスタックに式の演算結果が残る 42 算術式の評価例1 例:6+4*7 後置表現⇒6 4 7 * + 4*7 7 4 6 6+28 28 6 34 43 算術式の評価例2 例2:6+{4*(7+4)-3} ⇒6474+*3-+ 7+4 4 7 4 6 4*11 11 4 6 44-3 44 6 3 44 6 6+41 41 6 47 44 利用例2:再帰呼び出し (p.35) (リカーシブ・コール,recursive call) 手続きの中から,さらに 再帰呼び出しを利用することで,複雑な処理を非 常に単純なコードで実装できる場合がある 再帰呼び出しを行なうプログラムの実行時にはス タックメモリが消費される 45 nの階乗(再帰を用いない場合) n! = n×(n-1)×(n-2)× … ×2×1 int fact(int n) { int result = 1; for( ; n >= 1; n--) { result *= n; } return result; } 結果を格納する変数 を用意 46 nの階乗 以下のように表現することができる n (n 1)! (n 0) n! 1 (n 0) 3! 階乗を求める関数を 呼び出す 3×2! 階乗を求める関数を 呼び出す 2×1! 階乗を求める関数を 呼び出す 1×0! 階乗を求める関数を 呼び出す 1 47 再帰呼び出しを用いた実装(p34) スタック メモリ int fact(int n) { if (n<=0) { printf(“Illegal input n = %d¥n”, n); exit(1) 関数factの中で,関 } 数fact自身を呼出す else { if (N==1) return(1); else return(n*fact(n-1)); } return; } 関数factを呼び出すたびに引数 と戻り番地が積まれていく 戻り番地 nの値 戻り番地 nの値 戻り番地 nの値 49 再帰呼び出しを使用する時の注意 良くない例 int fact(int n) { return n * fact(n - 1); } スタック メモリ 注意1 終了条件を,正しく記述しないと 無限に繰り返される 注意2 実行時に与えるデータのサイズが大 きいと,スタックメモリが満杯になり, プログラムが異常終了する 50 スタックのまとめ スタック 抽象データ型としてのスタック 連結リストによるスタックの実現 配列によるスタックの実現 スタックの利用例 ポーランド記法,逆ポーランド記法 再帰呼び出し(リカーシブ・コール,recursive call) 51
© Copyright 2024 ExpyDoc