• 検索結果がありません。

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

N/A
N/A
Protected

Academic year: 2021

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

Copied!
18
0
0

読み込み中.... (全文を見る)

全文

(1)

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

1

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

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

1

1

2005

2005

7

7

22

22

酒居敬一

酒居敬一

(

(

sakai.keiichi@kochi

sakai.keiichi@kochi

-

-

tech.ac.jp

tech.ac.jp

)

)

http://www.info.kochi

(2)

再帰的アルゴリズム

• 再帰は重要なアルゴリズムの概念である.

とくに参照型を用いた柔軟なデータ構造を

扱う場合には,基本的に再帰的アルゴリ

ズムを用いるしかない.ここでは,再帰的

アルゴリズムを詳細に検討し,その動作の

理解をする

(3)

漸化式からの計算

• 階乗

f(0) = 1, f(x) = x! = x*f(x‐1)

• フィボナッチ数列

f(0) = 1, f(1) = 1, f(x) = f(x‐2) + f(x‐1)

• いずれも再帰的に関数を呼ぶ形に書ける

再帰呼び出しの場合 f(0)やf(1)で停止

(4)

public class Factorial {

public static int factorial(int aTarget) {

if(0 > aTarget) throw new IllegalArgumentException(); System.out.println("factorial(" + aTarget + ")に入ります"); if(0 == aTarget){

System.out.println("factorial(0) から出ます: 1"); return 1;

}

int total = aTarget * Factorial.factorial(aTarget - 1);

System.out.println("factorial(" + aTarget + ") から出ます: " + total); return total;

} }

public static int factorialWithoutRecursion(int aTarget) {

if(0 > aTarget) throw new IllegalArgumentException(); if(0 == aTarget){

return 1; }

int total = aTarget;

for(int count = aTarget - 1; 0 < count; count--){ total *= count; } return total; }

階乗を求めるプログラム

末尾再帰なので再帰

呼び出しをループに

変換することは容易

(5)

public class Fibonatti {

public static int fibonatti(int aTarget) {

if(0 > aTarget) throw new IllegalArgumentException(); System.out.println("fibonatti(" + aTarget + ") に入ります"); if((0 == aTarget) || (1 == aTarget)){

System.out.println("fibonatti(" + aTarget + ") から出ます: 1"); return 1;

}

int total = fibonatti(aTarget - 2) + fibonatti(aTarget - 1);

System.out.println("fibonatti(" + aTarget + ") から出ます: " + total); return total;

}

} public static int fibonattiWithoutRecursion(int aTarget) {

if(0 > aTarget) throw new IllegalArgumentException(); if((0 == aTarget) || (1 == aTarget)){

return 1; }

int old1 = 1, old2 = 1, total = 0;

for(int count = 2; count <= aTarget; count++){ total = old1 + old2;

old2 = old1; old1 = total; } return total; }

f(0)から順にたどれば

結果を求めることがで

きる

f(x)に必要とされるの

はf(x‐1)とf(x‐2)なので

変数を

2つ追加すれば

足る

(6)

[sakai@star 13]$ java FactorialTest 720 factorial(6)に入ります factorial(5)に入ります factorial(4)に入ります factorial(3)に入ります factorial(2)に入ります factorial(1)に入ります factorial(0)に入ります factorial(0) から出ます: 1 factorial(1) から出ます: 1 factorial(2) から出ます: 2 factorial(3) から出ます: 6 factorial(4) から出ます: 24 factorial(5) から出ます: 120 factorial(6) から出ます: 720 [sakai@star 13]$

[sakai@star 13]$ java FibonattiTest 8 fibonatti(5) に入ります fibonatti(3) に入ります fibonatti(1) に入ります fibonatti(1) から出ます: 1 fibonatti(2) に入ります fibonatti(0) に入ります fibonatti(0) から出ます: 1 fibonatti(1) に入ります fibonatti(1) から出ます: 1 fibonatti(2) から出ます: 2 fibonatti(3) から出ます: 3 fibonatti(4) に入ります fibonatti(2) に入ります fibonatti(0) に入ります fibonatti(0) から出ます: 1 fibonatti(1) に入ります fibonatti(1) から出ます: 1 fibonatti(2) から出ます: 2 fibonatti(3) に入ります fibonatti(1) に入ります fibonatti(1) から出ます: 1 fibonatti(2) に入ります fibonatti(0) に入ります fibonatti(0) から出ます: 1 fibonatti(1) に入ります fibonatti(1) から出ます: 1 fibonatti(2) から出ます: 2 fibonatti(3) から出ます: 3 fibonatti(4) から出ます: 5 fibonatti(5) から出ます: 8 [sakai@star 13]$

(7)

再帰的アルゴリズム

ハノイの塔

• 一回に一枚の円盤しか動かしてはいけない。

• 移動の途中で円盤の大小を逆に積んではいけない。

常に大きい方の円盤が下になるようにする。

• 棒以外のところに円盤を置いてはいけない。

(8)

public class Disk {

public Disk(int aSize) {

if(1 > aSize){

throw new IllegalArgumentException(); }

this.size = aSize; }

public int size() {

return this.size; }

private int size = 0; }

円盤オブジェクトと

(9)

public int size() {

return this.stack.size(); }

public boolean put(Disk aDisk) { if(this.stack.isEmpty()){ this.stack.push(aDisk); return true; } int topSize = ((Disk)this.stack.peek()).size(); if(aDisk.size() < topSize){ this.stack.push(aDisk); return true; } return false; }

private Stack stack; }

import java.util.Stack; public class Tower {

public Tower() {

this.stack = new Stack(); }

public Disk get() {

return (Disk)this.stack.pop(); }

public Disk get(int anIndex) {

return (Disk)this.stack.get(anIndex); }

塔オブジェクトと

(10)

public Hanoi(int aDisks) {

this.disks = aDisks;

for(int count = aDisks; 0 < count; count--){ this.tower[0].put(new Disk(count)); }

this.printAll(); }

public void doHanoi() {

this.doHanoi(this.disks, 0, 1, 2); }

private void doHanoi

(int aDisks, int aFrom, int aTo, int anOther) {

if(1 == aDisks){

Disk disk = this.tower[aFrom].get(); this.tower[aTo].put(disk);

this.printAll(); } else {

this.doHanoi(aDisks - 1, aFrom, anOther, aTo); Disk disk = this.tower[aFrom].get();

this.tower[aTo].put(disk); this.printAll();

this.doHanoi(aDisks - 1, anOther, aTo, aFrom); }

} public class Hanoi

{

private Tower[] tower = new Tower[] {

new Tower(), new Tower(), new Tower() };

private int disks; }

(11)

private void printAll() { int[] towerSizes = { tower[0].size(), tower[1].size(), tower[2].size() };

int towerSizeTotal = (towerSizes[0] + towerSizes[1] + towerSizes[2]); int[] sizes = {towerSizes[0], towerSizes[1], towerSizes[2]};

for(int count = 0; count < towerSizeTotal; count++){ for(int tcount = 0; tcount < 3; tcount++){

if((towerSizeTotal - towerSizes[tcount]) <= count){ Disk disk = this.tower[tcount].get(--sizes[tcount]); System.out.print("¥t");

for(int disks = 0; disks < disk.size();disks++){ System.out.print("-"); } System.out.print("¥t"); }else{ System.out.print("¥t|¥t"); } } System.out.println(""); } System.out.println("---"); System.out.println(""); }

(12)

通りがけ順の走査

二分木を次のルールで走査

1. 自分の左部分木をたどる

2. 自分を訪ねる

3. 自分の右部分木をたどる

4. 親の頂点に戻る

二分探索木を走査すると

横倒しの木を表示できる

29

31

21

19

7

48

30

24

14

32

20

29

20

32

48

30

24

14

7

19

21

31

(13)

public void printTreeRecursive() {

this.printTreeRecursive(this.root, 0); }

private void printTreeRecursive(MyNode aLocalRootNode, int depth) {

if(null == aLocalRootNode) return;

this.printTreeRecursive(aLocalRootNode.getRight(), depth+1); for(int i=0; i < depth; i++){

System.out.print("¥t"); } System.out.println(aLocalRootNode.getData().toString()); this.printTreeRecursive(aLocalRootNode.getLeft(), depth+1); } (48'メロン) (32'バナナ) (31'スイカ) (30'イチゴ) (29'リンゴ) (28'ブドウ) (24'ブルーベリー) (23'マンゴー) (21'レモン) (20'ミカン) (19'ナシ) (17'モモ) (14'サクランボ) (7'グレープフルーツ)

再帰呼び出し版

木の根が左にある、

左から右へ生えている

木を表示するプログラム

(29, "リンゴ") (20, "ミカン") (14, "サクランボ") (32, "バナナ") (30, "イチゴ") (24, "ブルーベリー") ( 7, "グレープフルーツ") (21, "レモン") (48, "メロン") (31, "スイカ") (19, "ナシ") (17, "モモ") (23, "マンゴー") (28, "ブドウ")

再帰呼び出し

(14)

(48'メロン) (32'バナナ) (31'スイカ) (30'イチゴ) (29'リンゴ) (28'ブドウ) (24'ブルーベリー) (23'マンゴー) (21'レモン) (20'ミカン) (19'ナシ) (17'モモ) (14'サクランボ) (7'グレープフルーツ)

this.printTreeRecursive((17, "モモ"), 4); 右部分木探索中

this.printTreeRecursive((23, "マンゴー"), 4); 右部分木探索中

this.printTreeRecursive((17, "モモ"), 4);出力

this.printTreeRecursive((23, "マンゴー"), 4);出力

this.printTreeRecursive((23, “マンゴー”), 4); 左部分木探索中

this.printTreeRecursive((17, “モモ”), 4); 左部分木探索中

this.printTreeRecursive(( 7, "グレープフルーツ"), 3); 右部分木探索中

this.printTreeRecursive((21, "レモン"), 3); 右部分木探索中

this.printTreeRecursive((31, "スイカ"), 3); 右部分木探索中

this.printTreeRecursive((19, "ナシ"), 3); 右部分木探索中

this.printTreeRecursive((28, "ブドウ"), 3); 右部分木探索中

this.printTreeRecursive(( 7, "グレープフルーツ"), 3);出力

this.printTreeRecursive((21, "レモン"), 3);出力

this.printTreeRecursive((31, "スイカ"), 3);出力

this.printTreeRecursive((19, "ナシ"), 3);出力

this.printTreeRecursive((28, "ブドウ"), 3);出力

this.printTreeRecursive((31, “スイカ”), 3); 左部分木探索中

this.printTreeRecursive((28, “ブドウ”), 3); 左部分木探索中

this.printTreeRecursive((21, “レモン”), 3); 左部分木探索中

this.printTreeRecursive((19, “ナシ”), 3); 左部分木探索中

this.printTreeRecursive(( 7, “グレープフルーツ”), 3); 左部分木探索中

this.printTreeRecursive((14, "サクランボ"), 2); 右部分木探索中

this.printTreeRecursive((30, "イチゴ"), 2); 右部分木探索中

this.printTreeRecursive((24, "ブルーベリー"), 2); 右部分木探索中

this.printTreeRecursive((48, "メロン"), 2); 右部分木探索中

this.printTreeRecursive((14, "サクランボ"), 2);出力

this.printTreeRecursive((30, "イチゴ"), 2);出力

this.printTreeRecursive((24, "ブルーベリー"), 2);出力

this.printTreeRecursive((48, "メロン"), 2);出力

this.printTreeRecursive((48, “メロン”), 2); 左部分木探索中

this.printTreeRecursive((30, “イチゴ”), 2); 左部分木探索中

this.printTreeRecursive((24, “ブルーベリー”), 2); 左部分木探索中

this.printTreeRecursive((14, “サクランボ”), 2); 左部分木探索中

this.printTreeRecursive((32, "バナナ"), 1); 右部分木探索中

this.printTreeRecursive((20, "ミカン"), 1); 右部分木探索中

this.printTreeRecursive((32, “バナナ”), 1); 出力

this.printTreeRecursive((32, “バナナ”), 1); 左部分木探索中

this.printTreeRecursive((20, “ミカン”), 1); 出力

this.printTreeRecursive((20, “ミカン”), 1); 左部分木探索中

this.printTreeRecursive((29, "リンゴ"), 0); 右部分木探索中

this.printTreeRecursive((29, "リンゴ"), 0);出力

this.printTreeRecursive((29, “リンゴ”), 0); 左部分木探索中

(15)

再帰呼び出しの除去

¾再帰呼び出しでは同じ関数を呼ぶ

¾一時変数は、名前が同じだけで、実体は別

¾実体は関数エントリ時に確保される

¾関数から抜けるときに開放される

¾最も最後に呼ばれた関数が最初に抜ける

¾つまり

LIFO、スタック

¾一時変数や途中経過を退避する領域が

あればループにより実現できる

(16)

退避すべきデータ

部分木の根

今何をしているか?

再帰呼び出しでは、プログラムの流れとして

保持していた。つまり

CPUのプログラムカウンタ

1. 右部分木の訪問

2. 自分の値の表示

3. 左部分木の訪問

4. 親の頂点に戻る

ループに変換するにはこれをスタックに退避

探索中の頂点の深さは計算できるので退避不要

(17)

public void printTreeLoop() {

MyNode node, currentRootNode = this.root; int depth = 0, todo = 0;

Stack stack = new Stack(); while(true){ switch(todo++){ case 0: node = currentRootNode.getRight(); if(node != null){ stack.push(currentRootNode); currentRootNode = node; stack.push(new Integer(todo)); todo = 0; depth++; } break; case 1:

for(int i=0; i < depth; i++){ System.out.print("¥t"); } System.out.println(currentRootNode.getData().toString()); break; // 続く…

ループ版

木の根が左にある、

左から右へ生えている

木を表示するプログラム

(18)

case 2: node = currentRootNode.getLeft(); if(node != null){ stack.push(currentRootNode); currentRootNode = node; stack.push(new Integer(todo)); todo = 0; depth++; } break; case 3: if(stack.empty()){ return; } todo = ((Integer)stack.pop()).intValue(); currentRootNode = (MyNode)stack.pop(); depth--; } } }

一時退避場所にはスタックを使っている

次にすべきこと、部分木の根、を保持する

1. 右部分木の訪問

2. 自分の値の表示

3. 左部分木の訪問

4. 親の頂点に戻る

参照

関連したドキュメント

テューリングは、数学者が紙と鉛筆を用いて計算を行う過程を極限まで抽象化することに よりテューリング機械の定義に到達した。

実際, クラス C の多様体については, ここでは 詳細には述べないが, 代数 reduction をはじめ類似のいくつかの方法を 組み合わせてその構造を組織的に研究することができる

(問5-3)検体検査管理加算に係る機能評価係数Ⅰは検体検査を実施していない月も医療機関別係数に合算することができる か。

これはつまり十進法ではなく、一進法を用いて自然数を表記するということである。とは いえ数が大きくなると見にくくなるので、.. 0, 1,

ここで, C ijkl は弾性定数テンソルと呼ばれるものであり,以下の対称性を持つ.... (20)

近年は人がサルを追い払うこと は少なく、次第に個体数が増える と同時に、分裂によって群れの数

 本資料は、宮城県に所在する税関官署で輸出通関又は輸入通関された貨物を、品目別・地域(国)別に、数量・金額等を集計して作成したもので

本資料は、宮城県に所在する税関官署で輸出通関又は輸入通関された貨物を、品目別・地域(国)別に、数量・金額等を集計して作成したもので