第8回 スタックとキュー - CreW 慶應義塾大学

第10回 スタックとキュー
~データを収納する便利なデータ構
造~
学習目標

スタック、キューの概念を説明できる



スタック、キューを利用して問題解決ができる
スタックを使ったプログラムが書ける
キューを使ったプログラムが書ける
10.1 商品の補充と販売



10.1.1 商品の補充と販売をする
10.1.2 最初に入れたものを最初に取り出
す
10.1.3 配列を使ってキューを実装する
10.1.1 商品の補充と販売をする

①「商品」クラス


「商品種類」は商品の種類のことでした
今日は実際の「商品」を扱います
商品
- 製造年月日
ただし、商品の変数は
「製造年月日」だけとします。
商品の補充と販売をする

②商品を収納しておくクラス設計
商品保管庫には、どんな機能が必要でしょう
か?

Main
商品保管庫
+ main()
1
+ 追加()
削除()
1+
+ 表示()
商品
- 製造年月日
1
0..n
10.1.2 最初に
入れたものを最初に取り出す

今回考える2種類の収納パターン

スタック


後入れ先出し方式
キュー

先入れ先出し方式
後入れ先だし方式(スタック)
商品保管庫
補充する
販売する
•後から入れたものは、列の先頭に挿入される
•取り出すときは、列の先頭から取り出される
※昔のコンビニのジュース販売方式
これでは、列の最後に入れられたら、取り出される
機会がほとんどなくなってしまいます
先入れ先出し方式(キュー)
商品保管庫
補充する
販売する
•後から入れたものは、列の最後に挿入される
•取り出すときは、列の先頭から取り出す
※いまのコンビニのジュース販売方式
古いものから取り出されるので、商品を販売するの
に向いています
10.1.3
配列を使ってキューを作る

①プリミティブな方法
挿入時は配列番号順に入れていく
[0]
[1]
[2]
[3]
[4]
[5]
追加 追加 追加 追加
カーソル
カーソル
カーソル
カーソル
プリミティブな方法(2)
取り出し時は配列の先頭を削除して、
配列の中身をひとつずつずらす
[0]
[1]
[2]
[3]
[4]
[5]
追加 追加 追加
カーソル
カーソル
カーソル
この方法の問題点

削除するたびに要素の移動が起こってしま
う



1万個入っていたら削除するたびに1万個の
大移動が起こる
効率が悪いね
どうしたら効率の良いキューになります
か?
③円環状に
することにより効率を上げる
追加するときは追加カーソルがあるところに追加する
追加したら、追加カーソルを一つずらす
[0]
[1]
[2]
[3]
[4]
[5]
追加 追加 追加 追加
カーソル
カーソル
カーソル
カーソル
円環キュー (2)
削除するときは削除カーソルがあるところから削除する
削除したら、削除カーソルを一つずらす
[0]
[1]
[2]
[3]
削除 削除 削除 削除
カーソル
カーソル
カーソル
カーソル
[4]
[5]
追加
カーソル
円環キュー (3)
追加カーソルが配列
の最後まできたら、
配列の一番初めに戻す
ラップアラウンド
(最初に戻るの意)といいます
※削除も同様
[0]
[1]
[2]
[3]
追加 追加 追加
削除
カーソル
カーソル
カーソル
カーソル
[4]
[5]
追加
追加
カーソル
カーソル
円環キュー (4)
追加カーソルと削除カーソルが同じ位置にあるときは、
要素が入っていないことを示す
[0]
[1]
[2]
[3]
削除 追加
カーソル
カーソル
[4]
[5]
円環キューの実装例
例題10-2
(ItemStock.java)
public class ItemStock {
private int ARRAY_SIZE = 6;//配列で円環キューを実装するときは1サイズ分大きくする。
//すなわちこれは5つしか入らない。
private Item[] itemArray = new Item[ARRAY_SIZE];//商品を格納する配列
private int addCursor = 0;//追加カーソル
private int removeCursor =0;//削除カーソル
/**
* 商品を補充する
*/
public void supply(Item insertItem){
itemArray[addCursor] = insertItem;//追加カーソルの位置に挿入する
addCursor++; //追加カーソルを右にずらす
if (addCursor >= ARRAY_SIZE){//配列の最後にきたら
addCursor = 0;//ラップアラウンドする
}
}
・・・
}
キューのその他の使い道




銀行の待ち行列
プリンタの待ち行列
コンピュータのプロセスの待ち行列
(プライオリティーキュー)
etc…
10.2 投入された金の管理


10.2.1 投入された金の管理
10.2.2 最後に入れたものを最初に取り出
す
10.2 投入された金の管理

①「金」クラス

硬貨一つ一つをオブジェクトと考えます
金
- 価値
ただし、金の変数は
「値(単位:円)」
だけとします。
値は100,10などの整数が入ります。
②クラス設計

金勘定

投入された金を収納する勘定が必要


本来は勘定に記入するものだが今回は直接金を
入れてしまうことにします。
勘定にはどんな機能が必要ですか?
勘定
Main
+ main()
1
+ 追加()
1 + 削除()
+ 表示()
金
- 価値
1
0..n
Undoしたい

投入したお金をUndoできる自動販売機を
作って欲しい
※実際の自動販売機でこの例のようなケースは、
あまりありませんが、一般のソフトウエアでの
Undo機能は重要な機能ですね
勘定の仕様

1.
2.
3.
一般的なシナリオ
100円,10円,50円の順番でお金を入れる
(この時点で投入金額は160円)
Undoを実行すると、最後に入れたお金
が戻る。(50円が取り消され、投入金額
が110円になる)
もう一度Undoすると10円が戻る
スタック VS キュー


スタックVSキュー
考えてみましょう!
Undoを実現するには追加と逆順で
削除する必要があります。
この場合、スタックを使うべきです。

10.2.2 最後に
入れたものを最初に取り出す

スタック
勘定
投入する
Undoする
10.2.3 配列を使って
スタックを実装する
スタックへのお金の追加
[0]
[1]
[2]
[3]
[4]
追加&削除
追加&削除
追加&削除
追加&削除
カーソル カーソル カーソル カーソル
[5]
配列でスタックを作る(2)
スタックからお金の削除(Undo)
[0]
[1]
[2]
[3]
[4]
追加&削除
追加&削除
追加&削除
追加&削除
カーソル カーソル カーソル カーソル
[5]
Javaでスタックを実装する

実装コード

実装されていないところは、今回の練習問題
スタックのその他の使い道




算術式のパース(解析)
XML,HTMLなどの入れ子構造の解析
食堂のトレー置き場
etc
算術式のパース

問題:
3+4*5
(4 + 5) * (8 - 4)
などをコンピュータはどうやって計算する
か?
答え


コンピュータはそのまま計算できない。
→特別な記法に変換する必要がある。
例)
3+4*5
中置記法
→
345*+
後置記法
(逆ポーランド記法)
後置記法で書かれた式を計算
する

ルール




左から一文字ずつ読む
数字がきたらスタックにつむ
演算子がきたらスタックの上から2つの数字を
取り出して演算する
演算したら結果をスタックの上に積む
これを式の最後まで繰り返す
後置記法で書かれた式を計算
する
計算対象
345*+
5
20
4
23
3
スタック
中置記法と後置記法
中置記法
後置記法
A+B-C
AB+C-
A*B/C
AB*C/
A+B*C
ABC*+
A*B+C
AB*C+
A*(B+C)
ABC+*
A*B+C*D
AB*CD*+
(A+B)*(C-D)
AB+CD-*
((A+B)*C)-D
AB+C*D-
後置記法と日本語



3+4*5
345*+
→3に4と5をかけたものを足す
5*8+7*3
58*73*+
→5と8をかけたものと7と3をかけたものを足
す
日本語と語順が同じ。
→日本語とコンピュータは相性がいい。
中置記法を後置記法に変換す
る

ルール



左から一文字ずつ読む
数字がきたら、結果に書き出す
かっこがきたら



かっこ以外の演算子がきたら



開きかっこの場合→スタックに積む
閉じかっこの場合→開きかっこを見つけるまで、スタックから
演算子を取り出して、結果に書き出す
スタックが空なら→スタックに積む
スタックを覗いて、スタック演算子よりも優先順位の低い演算
子だったら、結果に書き出す。そうでなければ、スタックに積
む
式を最後まで読み取ったら、スタックに入っているもの
をすべて出力する
中置記法を後置記法に変換す
る
対象
3+4*5
出力
345*+
*
+
スタック
10.3
インタラクティブなプログラム


10.3.1 キーボード入力
10.3.2 キーボード入力のメソッドはどこに
配置する?
10.3.1 キーボード入力

今回のプログラムは、少しインタラクティブ
に、コンソール(標準入力)から文字を読み
込むプログラムにします
(Example6_2Item.java,Example6_2Mon
ey.java)
プログラム
文字の
読み込み
Javaでコンソール
から文字を読み込む方法

Javaには、ファイルや標準入出力を簡単に
するためのクラス群が用意されています
「java.io」パッケージに入っています
InputStreamReader
+ read()
BufferedReader
+ readLine()
今回使うクラスの役割
読み込む役割のクラス
InputStreamReader
+ read()
「System.in」
バッファリングして
一行ずつ読み込む
役割のクラス
BufferedReader
+ readLine()
プログラム
実装方法
例題10-4
(Example10_4Item.java)
/**
* ユーザの入力を一行読み込む
* 変更不要
*/
public String getInput(){
String buf = null;//読み込んだ文字列を保存する
try{
//読み込むためのBufferedReaderクラスをインスタンス化する
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
buf = br.readLine();//一行読み込む
}catch(Exception ex){
ex.printStackTrace();//入出力エラーが起きた場合エラーを表示する
}
return buf;//読み込んだ文字列を返す
}
10.3.2 キーボード
入力のメソッドはどこに配置す
る?



①メインクラスに書く
②入力を受け付けるクラスを作る
③クラスメソッドを利用する
①メインクラスに書く

コンソールから読み込むメソッドを作ったの
はいいですが、両方のExampleに書かな
ければいけなくなりました
Example10_4Item
+ main()
+ getInput()
Example10_4Money
+ main()
+ getInput()
重複コードになってしまいました
②入力を
受け付けるクラスを作る

メソッドの分類によって、より意味が明確な
プログラムに
Example10_4Item
Example10_4Money
+ main()
+ getInput()
+ main()
+ getInput()
Input
+ getInput()
入力クラス

入力を受け付ける意味のクラスを作ること
によってコードの重複を防ぐことができる

ただし、様々な所で入力するコードを書くたび
に、Inputクラスをインスタンス化する必要があ
る
Input input = new Input();//ユーザからの入力を得るためのインスタンス
String menu = input.getInput()//コンソールからユーザの入力を得る
メモリの無駄使い
1つでも
いいのでは?
Input
インスタンス
getInput()
Example
プログラム
Input
インスタンス
getInput()
Example
プログラム
Input
インスタンス
getInput()
Example
プログラム
Input
インスタンス
getInput()
メモリの無駄使いです
無駄な
インスタンスは作りたくない

インスタンスは一つでもこと足ります

このような仕組みはどうしたらできますか?
getInput()
Input
インスタンス
getInput()
getInput()
Example
プログラム
Example
プログラム
Example
プログラム
③クラスメソッドを利用する

一般的なプログラムでは
Input
クラス
インスタンス化して
Input
インスタンス
getInput()
Example
プログラム
クラスメソッドにすると

クラスメソッドは直接呼び出すことができま
す
getInput()
Input
クラス
Example
プログラム
getInput()
Example
プログラム
getInput()
Example
プログラム
クラスメソッドにするには

メソッドにstatic修飾子をつけます
例題10-6
/**
(Input.java)
* ユーザの入力を一行読み込む
* 変更不要
*/
public static String getInput(){
String buf = null;//読み込んだ文字列を保存する
try{
//読み込むためのBufferedReaderクラスをインスタンス化する
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
buf = br.readLine();//一行読み込む
}catch(Exception ex){
ex.printStackTrace();//入出力エラーが起きた場合エラーを表示する
}
return buf;//読み込んだ文字列を返す
}
クラスメソッドを呼び出すには
インスタンスメソッドの呼び出し
Input input = new Input();//ユーザからの入力を得るためのインスタンス
String menu = input.getInput()//コンソールからユーザの入力を得る
「Example10_3Item.java,Example10_3Money.java」より抜粋
インスタンス変数名
クラスメソッドの呼び出し
String menu = Input.getInput()//コンソールからユーザの入力を得る
「Example10_4Item.java,Example10_4Money.java」より抜粋
クラス名
static


staticをメソッドに付けると、クラスメソッドに
なります
staticを変数に付けると、クラス変数になり
ます
Input
クラス
注意点

クラスメソッド、変数はプログラムのどこか
らでも呼べるので便利ですが、むやみやた
らに使うと、コードが複雑化します




あっちこっちに飛ぶプログラム
global変数は注意しないとバグの原因
基本はインスタンス生成で
ユーティリティークラスを作るときには便利
mainメソッドが
最初に呼ばれる仕組み
%java Example10_4
起動
main()
Java
バーチャルマシン
Example10_4
クラス