アルゴリズムとデータ構造1

アルゴリズムとデータ構造1
2005年6月28日
酒居敬一([email protected])
http://www.info.kochi-tech.ac.jp/k1sakai/Lecture/ALG/2005/index.html
スタック(LIFO)
プッシュ
スタックポインタ
スタックポインタ
ポップ
public class MyStack
{
private MyStack()
{
}
public MyStack(int aMaxSize)
{
this.maxSize = aMaxSize;
this.stackArray = new Object[aMaxSize];
this.stackPointer = 0;
}
public void printAll()
{
System.out.println("スタックの内容");
int count = 1;
int position = this.stackPointer - 1;
while(0 <= position) {
System.out.println(count + "\t" + this.stackArray[position]);
++count;
--position;
}
System.out.println();
}
private int maxSize;
private Object[] stackArray;
private int stackPointer;
}
public void push(Object anObject)
{
if(this.isFull()){
System.err.println("エラー: スタックはいっぱいです");
return;
// スタックオーバーフロー
}
this.stackArray[this.stackPointer] = anObject;
this.stackPointer++;
}
public boolean isFull()
{
return (this.stackPointer == this.maxSize);
}
public Object pop()
{
if(isEmpty()){
System.err.println("スタックは空です");
return null;
// スタックアンダーフロー
}
--this.stackPointer;
Object popObject = this.stackArray[this.stackPointer];
this.stackArray[this.stackPointer] = null;
return popObject;
}
public boolean isEmpty()
{
return (0 == this.stackPointer);
}
ハノイの塔
• 一回に一枚の円盤しか動かしてはいけない。
• 移動の途中で円盤の大小を逆に積んではいけない。
常に大きい方の円盤が下になるようにする。
• 棒以外のところに円盤を置いてはいけない。
レジスタベースとスタックベース
普通に見かけるプロセッサ
スタックマシン(Java VMなど)
レジスタファイル+演算器
スタック+演算器
レジスタベースとスタックベース
Postscript
•
•
•
•
プログラミング言語ではなくて、ページ記述言語
オペランドスタック、辞書スタックを持つ
オブジェクトはリテラル、エグゼキュータブル
A-WSでgsというコマンドで実行できる
• push/pop以外のスタック処理がある
• index
指定位置の内容をコピーしてpush
• rotate
指定位置までのスタックを配列とみて回転
• [] , {}, () スタックから配列オブジェクトを切り出せる
(リンゴ)
(ミカン)
(サクランボ)
stack
=
(バナナ)
(ブルーベリー)
(イチゴ)
stack
(グレープフルーツ)
stack
count -1 1 {pop =} for
quit
PostScript記述
教科書96ページ
のプログラムと
ほぼ同じ動作
[sakai@star ]$ gs
GS>
GS>(リンゴ)
GS<1>(ミカン)
GS<2>(サクランボ)
GS<3>stack
サクランボ
ミカン
リンゴ
GS<3>=
サクランボ
GS<2>(バナナ)
GS<3>(ブルーベリー)
GS<4>(イチゴ)
GS<5>stack
イチゴ
ブルーベリー
バナナ
ミカン
リンゴ
(右上に続く…)
(左下から続く)
GS<5>(グレープフルーツ)
GS<6>stack
グレープフルーツ
イチゴ
ブルーベリー
バナナ
ミカン
リンゴ
GS<6>count -1 1 {pop =} for
グレープフルーツ
イチゴ
ブルーベリー
バナナ
ミカン
リンゴ
GS>
GS>quit
[sakai@star ]$
RPN(逆ポーランド記法)
普通は 1 + 2 と入力して 3 という答えを得ます
同じ答えを得るために、RPNで書くと 1 2 + となります。
普通の書き方の(1 + 2) × (3 + 4) をRPNにすると 1 2 + 3 4 + × となります。
ありがちなプログラミング言語では規則をもとに構文解析を行っている
演算子には優先順位がある
括弧は優先度を変える
変わった言語、変わったプロセッサというものがありまして
Forth, PostScriptといった言語、インテル x87 数値演算プロセッサ
これらはスタックを基本的なデータ構造としている
(1 + 2) × (3 + 4)
RPNでは 1 2 + 3 4 +
(1 + 2) × (3 + 4) ÷(5×6 – 7×8)
RPNでは 1 2 + 3 4 + × 5 6 × 7 8 × - ÷
PostScriptでは、三角関数まで定義されている。
GS>1 2 add 3 4 add mul =
21
GS>1 2 add 3 4 add mul 5 6 mul 7 8 mul sub div =
-0.807692
GS>30 sin =
0.5
GS>45 cos =
0.707107
RPNで記述するとき、日本語で数式の動作を
読み上げることにかなり近い順序になる
/* 可変長引数を持つ関数 */
int printf(const char *fmt,…);
/* それの呼び出し */
printf(%d %f \n”, int_number, dbl_number);
C言語では引数はスタックに積まれて、
関数に渡される。呼び出し側の責任で
引数を積んだり降ろしたりする。
呼ばれた関数にわかっていること
1. 呼ばれた関数のスタックフレームの大きさ
2. 関数の戻り先(呼び出し元PC)
3. 第1引数がconst char *fmtであること
つまりfmtが読める→以降の引数がわかる
呼ばれた関数の
スタックフレーム
PC
fmt
int_number
dbl_number
呼んだ関数の
スタックフレーム
← TOS
/* 可変長引数を持つ関数 */
int execl(const char *path, const char *arg, ...);
/* それの呼び出し */
execl(“/bin/ls”, “ls”, “-l”, “/home/sakai”, NULL);
呼ばれた関数にわかっていること
1. 呼ばれた関数のスタックフレームの大きさ
2. 関数の戻り先(呼び出し元PC)
3. 第1引数が“/bin/ls”であること
4. 第2引数が”ls”であること
5. 最後の引数は必ずNULLであること
スタックに何らかのマークをpushし、
TOSからマークまでをリストとして渡す
呼ばれた関数の
スタックフレーム
PC
“/bin/ls”
“ls”
“-l”
“/home/sakai”
NULL
呼んだ関数の
スタックフレーム
← TOS
% PostScriptにおける配列の切り出し
% [1 2 3 4 5 6 7 8 9 10] という配列を定義
GS>[
GS<1>1
GS<2>2
GS<3>3
GS<4>4
GS<5>5
GS<6>6
GS<7>7
GS<8>8
GS<9>9
GS<10>10
GS<11>]
GS<1>==
[1 2 3 4 5 6 7 8 9 10]
GS>
PostScriptでは、配列が定義できる。
スタック上の「マーク」と「マークまでを配列とする」
オペレータを組み合わせて使う。
このオペレータはマークまでをすべてpopし、
配列オブジェクトとしてふたたびpushする
[ オブジェクトの並び ] は通常の配列
{ オブジェクトの並び } は実行可能な配列(手続き)
(文字の並び) は文字の配列(文字列)
いずれも配列の基本的な性質は継承している
コンパイラとインタプリタ
• 高級言語で記述されたプログラムを,機械
語あるいはアセンブリ言語のプログラムに
翻訳する
• 翻訳の過程はおおむね次のようになる
•
•
•
•
•
字句解析
構文解析
意味解析
エラー診断
コード生成
• インタプリタではコード生成しないで即実行
PostScriptインタープリタ
•
•
•
•
PostScript言語を解釈し即実行
PostScriptはスタックベースの言語
構文解析が無い
字句解析によりオブジェクトを分類
• 実行可能な名前(オペレータ)
• リテラル(名前オブジェクト)
• それ以外のオブジェクト
• オペレータは辞書と呼ばれる連想記憶に定義
• 実行可能な名前は辞書から取り出され実行される
– 名前による参照が行われるため、束縛(binding)は動的
• オペランドスタックの他に辞書を置くスタックが存在する
(ハードウェア)スタックプロセッサ
• 0オペランド、1オペランド形式の命令
• 演算対象のひとつはTOSなので命令語長が短い
• レジスタ割り付けの必要性がない
.lp:
fld
fld
fld
fld
fmul
fxch
fmul
fxch
fmul
fxch
fmul
fxch
fadd
dword [eax+ecx*4]
; xr, 0.4054, istep
dword [eax+ecx*4+4]
(左下から続く)
dword [eax+ecx*4+8]
fxch
st2 istep; 2x, 0x, 3x+, 1x
dword [eax+ecx*4+12] ; 3, 2, 1, 0,
0.4054,
fadd st0, st4
st0, st5
fxch st3
; 1x, 0x, 3x+, 2x+
st1
; 2, 3x, 1, 0
fadd st0, st4
st0, st5
fxch st1
; 0x, 1x+, 3x+, 2x+
st2
; 1, 3x, 2x, 0
fadd st0, st4
st0, st5
fxch st2
; 3x+, 1x+, 0x+, 2x+
st3
; 0, 3x, 2x, 1x
fistp dword [edx+ecx*4+12]
st0, st5
fistp dword [edx+ecx*4+4]
st1
; 3x, 0x, 2x, 1x
fistp dword [edx+ecx*4]
st0, st4
fistp dword [edx+ecx*4+8]
(右上へ続く)
add ecx, 4
jnz
.lp
スタックベースのVLIWチップ