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

講義「アルゴリズムとデータ構造」

N/A
N/A
Protected

Academic year: 2021

シェア "講義「アルゴリズムとデータ構造」"

Copied!
24
0
0

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

全文

(1)

講義「アルゴリズムとデータ構造」

第7回 探索のためのデータ構造(2)

大学院情報科学研究院 情報理工学部門 情報知識ネットワーク研究室

喜田拓也

(2)

今日の内容

木を探索するアルゴリズム(木の巡回)

幅優先探索( breadth-first-search; BFS ) 深さ優先探索( depth-first-search; DFS ) 行きがけ順( preorder traversal ;前順)

通りがけ順( inorder traversal ;中順)

帰りがけ順( postorder traversal; 後順)

ポイント:

再帰呼び出しによる DFS の実現

スタックによる実現= DFS ,キューによる実現= BFS

(3)

木の巡回とは

与えられた根付き木 𝑇𝑇 のすべての頂点をちょうど一度 ずつ訪問(処理)すること

あるいは,木の各頂点がちょうど一度ずつ現れるような,

𝑇𝑇 の頂点の並びのこと 様々な応用がある!

• 人工知能分野における最適解の探索

• 数式の処理

• 言語解析や意味理解

• HTML や XML データ等の処理

二分探索木にあるすべて

の要素を小さい順に列挙

するとか

(4)

木の巡回の種類

巡回の考え方:

基本は,根から出発して下方へ全ての頂点を訪問する 木の探索には大きく分けて2種類ある

• 幅優先探索( breadth-first-search; BFS )

探索の深さ(レベル) 𝑑𝑑 = 0, 1, 2, … を大きくしながら,

その深さに属する節点を処理する

• 深さ優先探索( depth-first-search; DFS )

いま処理している節点から,できるだけ深い方へ処理

をすすめていき,葉に到達したら手前に戻ることを繰り

返す

(5)

幅優先探索( BFS )

1 A 2

4 5

3

6 7

10 11

B E

C D F H

G I

8 J 9 K

BFS は,根から近い順番(深さが浅い順)に木を探索する方法 探索が解に到達したとき,その経路は最短なものとなる

節点の中に書かれた 数字は BFS で訪れる順

BFS で訪れた順にノードのデータを書き出すと次のようになる

A B E C D F H J K G I

(6)

キューによる BFS の実現

【 BFS のアルゴリズム】

空のキュー 𝑆𝑆 を用意する

キュー 𝑆𝑆 に木 𝑇𝑇 の根を登録( enqueue )する キュー 𝑆𝑆 が空でない間,以下の処理を繰り返す

1. キュー 𝑆𝑆 から要素(節点) 𝑣𝑣 を取り出す( dequeue ) 2. 節点 𝑣𝑣 を処理(出力)する

3. 節点 𝑣𝑣 の左の子があればキューに登録する 4. 節点 𝑣𝑣 の右の子があればキューに登録する

キューは,入れた順番に取り出される

ことを思い出そう!

(7)

深さ優先探索( DFS )

1 A 2

3 5

8

9 10

6 7

B E

C D F H

G I

11 J 4 K

DFS は,根から順に遷移できなくなるまで(葉まで)進み,

遷移できなくなったら手前(親)に戻ることを繰り返す

バックトラックは DFS を利用した解の探索手法(うまくやると速い)

節点の中に書かれた 数字は DFS で訪れる順

DFS で訪れた順にノードのデータを書き出すと次のようになる

A B C K D G I E F H J

(8)

DFS の際の節点の処理順について

DFS において,巡回の処理順には次の三つの方法がある 行きがけ順(前置順,前順, preorder )

通りがけ順(中置順,中順, inorder ) 帰りがけ順(後置順,後順, postorder )

1 A 2

3 5

8

9 10

6 7

B E

C D F H

G I

11 J 4 K

さっきの「訪れた順」( A B C K D G I E F H J )は行きがけ順

(9)

行きがけ順( preorder )

各節点 𝑣𝑣 は,最初に訪問したときに処理される 内部節点は上から来たときに処理される

葉は訪問したときに処理される

結果として,木の節点は上から下向きに処理される

1 A 2

3 5

8

9 10

6 7

B E

C D F H

G I

11 J 4 K

行きがけ順での節点の処理順: A B C K D G I E F H J

(10)

通りがけ順( inorder )

各頂点 𝑣𝑣 は,途中で訪問する時(左から右の子へ移る 時)に処理される

結果として,木の節点は,左端から右端へ並んだ順で 出力される

7 A 3

2 5

9

8 10

4 6

B E

C D F H

G I

11 J 1 K

通りがけ順での節点の処理順: K C B G D I A F E H J

二分木でない場合(3つ以上の

子がある場合),間にある子と親

の順番は実装の仕方による

(11)

帰りがけ順( postorder )

各頂点 𝑣𝑣 は,最後に訪問する時(下から上へ戻る時)に 処理される

結果として,木の節点は,下から上向きに処理される

葉のデータを集約しながら全体のデータを得るときに使う

11 A 6

2 5

10

7 8

3 4

B E

C D F H

G I

9 J 1 K

帰りがけ順での節点の処理順: K C G I D B F H J E A

(12)

先生,憶えるのが大変です・・・

1

2 3

2

1 3

3

1 2

親と二つの子からなる木を考えたとき,

親の順番に注目すると憶えやすいです

行きがけ順

( preorder )

通りがけ順

( inorder )

帰りがけ順

( postorder )

(13)

スタックによる DFS の実現

【 DFS のアルゴリズム(行きがけ順)】

空のスタック 𝑆𝑆 を用意する

スタック 𝑆𝑆 に木 𝑇𝑇 の根を登録( push )する

スタック 𝑆𝑆 が空でない間,以下の処理を繰り返す

1. スタック 𝑆𝑆 から要素(節点) 𝑣𝑣 を取り出す( pop ) 2. 節点 𝑣𝑣 を処理(出力)する

3. 節点 𝑣𝑣 の右の子があればスタック 𝑆𝑆 に登録する 4. 節点 𝑣𝑣 の左の子があればスタック 𝑆𝑆 に登録する

通りがけ順,帰りがけ順にするにはどうすればいいでしょうか?

スタックは,最後に入れたものが先に

取り出されることを思い出そう!

(14)

再帰処理を用いた DFS (行きがけ順)

void preorder(node *v) {

if (v == null) return; // 再帰の終端条件 v を出力する ;

preorder(v->left); // 左の子を処理 preorder(v->right); // 右の子を処理 }

void main() {

node *root = 𝑇𝑇 の根へのポインタ ; preorder(root);

}

根からスタート

頂点 v に来たら,まず自分自身を出力し,

次に左と右の部分木を順に処理する

(15)

再帰処理を用いた DFS (通りがけ順)

void preorder(node *v) {

if (v == null) return; // 再帰の終端条件 preorder(v->left); // 左の子を処理

v を出力する ;

preorder(v->right); // 右の子を処理 }

void main() {

node *root = 𝑇𝑇 の根へのポインタ ; preorder(root);

}

根からスタート

頂点 v に来たら,まず左の部分木を処理し,

次に自分自身を出力してから,右の部分

木を処理する

(16)

再帰処理を用いた DFS (帰りがけ順)

void preorder(node *v) {

if (v == null) return; // 再帰の終端条件 preorder(v->left); // 左の子を処理

preorder(v->right); // 右の子を処理 v を出力する ;

}

void main() {

node *root = 𝑇𝑇 の根へのポインタ ; preorder(root);

}

根からスタート

頂点 v に来たら,まず左と右の部分木を 処理し,最後に自分自身を出力する

これら三つのアルゴリズムを見て

何か気づきませんか?

(17)

まとめ

木を探索するアルゴリズム(木の巡回)

幅優先探索( breadth-first-search; BFS )

⇒ キューを使って実現できる

深さ優先探索( depth-first-search; DFS )

⇒ スタックを使って実現できる

⇒ 再帰呼び出しを使うと簡単に記述できる!

行きがけ順( preorder traversal ;前順)

通りがけ順( inorder traversal ;中順)

帰りがけ順( postorder traversal; 後順)

(18)

おまけ: 二分探索木の実装例

typedef struct BinarySearchTreeNode { int data;

struct BinarySearchTreeNode *left;

struct BinarySearchTreeNode *right;

} bst_node;

bst_node *insert(bst_node *root, int data) { if(root==NULL) {

root = (bst_node *)malloc(sizeof(bst_node));

if(root==NULL) {

printf("memory allocation error¥n");

exit(EXIT_FAILURE);

} else {

root->data = data;

root->left = root->right = NULL;

} else { }

if(data < root->data)

root->left = insert(root->left, data);

else if (data > root->data)

root->right = insert(root->right, data);

} return root;

bst.h

EXIT_FAILURE は <stdlib.h> で

二分探索木は、データの集合 に対して、効率よい検索を提 供するデータ構造

各ノードは高々2個の子供を 持ち、自身が保持する値は、

左の子孫より大きく、右の子孫 より小さい

5

8 1

6 11

(19)

おまけ: 二分探索木の実装例(つづき)

bst_node *find(bst_node *root, int data) { if(root==NULL) return NULL;

else if(data < root->data)

return find(root->left, data);

else if(data > root->data)

return find(root->right, data);

else // case that root->data == data return root;

}

bst_node *find_min(bst_node *root) { if(root==NULL) return NULL;

else if(root->left==NULL) return root;

else return find_min(root->left);

}

bst_node *find_max(bst_node *root) { if(root==NULL) return NULL;

else if(root->right==NULL) return root;

else return find_max(root->right);

void inorder_search(bst_node *root) { if(root) {

inorder_search(root->left);

printf("%d¥n", root->data);

inorder_search(root->right);

} }

inorder_search は、木を深さ優先 探索しながら中間順(通りがけ順)で ノードを処理する

ここでは、単にノードの値を出力して いるが、木に格納した整数のソートを 行っているのと同じ結果になる

一番左にあるノードの値が最小値

一番右にあるノードの値が最大値

bst.h (つづき)

(20)

おまけ: 二分探索木の使用例

#include <stdio.h>

#include <stdlib.h>

#include "bst.h"

int main(void) { int d = 3;

bst_node *tmp=NULL;

bst_node *root=NULL;

root=insert(root,5);

root=insert(root,8);

root=insert(root,1);

root=insert(root,12);

root=insert(root,3);

printf("min=%d¥n", find_min(root)->data);

printf("max=%d¥n", find_max(root)->data);

if((tmp=find(root, d)) != NULL)

printf("%d was found¥n", tmp->data);

else printf("%d was not found¥n", d);

inorder_search(root);

return 0;

$ ./test_bst min=1

max=12

3 was found 1 3

5 8 12

3 5

1 1 4 8

5 12 2 3

NULL NULL

NULL NULL

NULL NULL

test_bst.c 実行結果

insert の戻り

値を必ず root

にセットする

(21)

プログラミング問題(迷路の最短路)

俺は現在,相棒と共にとあるダンジョン(迷宮)の奥深くに居る.

ダンジョンの最奥に隠されていた宝箱を開けた際,運悪く毒針の 罠にかかってしまった.解毒するには,地上で待っている仲間の もとまでたどり着く必要があった.

「・・・アスナ・・・すまない・・・」

「しっかりして!大丈夫よ.私このフロアの地図を持っているわ.

最短の経路で地上へ出るから!」

ダンジョンは大きさが 𝑁𝑁 × 𝑀𝑀 ( 𝑁𝑁 , 𝑀𝑀 < 100 )マスの迷路になって いる.迷路は通路( . )と壁( # )でできており,現在位置からは隣接 する上下左右の4方向の通路へと移動できる.

便宜的に,1マスの移動に1分かかるとする.また,毒が回りき

る時間 𝑇𝑇 (分)が与えられる.スタート( S )からゴール( G )にたどり着く

までの時間が 𝑇𝑇 分を超える場合は” dead ”と表示し,そうでなけれ

ばゴールまでの最短時間を表示するプログラムを書け.

(22)

問題の入力例

# # S # # # . . . . . . . . # # # . . # # # # # # . # . . . # . . . # . # . # . # . # # # # . # . . . # . # . . . . . . # . # . # . # # # # . # . . # # . # . . .

# # # . . # . . . # . . . . # . # . # . # . . # . # # # . # . # . . # . . . # # # # . # . # # # # . . . # . # . . . . # . # . # . # # # # # # . # . . . . . # #

# # # # # # # # . G #

例1( 𝑁𝑁 = 17, 𝑀𝑀 = 11, 𝑇𝑇 = 60 ) 例2( 𝑁𝑁 = 17, 𝑀𝑀 = 14, 𝑇𝑇 = 60 )

. . . # . . . . . . # . # # # . # # . # # # . . # . # . . . . # . # . # .

# # . # . . . . # . # . # . . . . # # # # # # . # . # . . # # # . . . . # . # . . . . . . # . S . . # . # . # #

# # . # . . . . # . # . . G

. . . # . # # # #

. # . # # # # # # . # . . #

. # . . # . . . #

. # . . # . . . # . # . . #

. # . . # # # # # . # # # #

. # . . . # . . . . .

. # . . . # # # # # .

. # # # # # # # # # # # # .

. . . .

(23)

プログラミング問題(シミの数を数える)

特に女性にとって,顔にできたシミは非常に疎ましい存在である.

お肌のシミが気になり始めた梅子のもとに,とあるセールスマンが やってきた.

「できたシミを,レーザーで治療できるのをご存知ですか?」

「えっ!本当に?」

「ええ.いまならシミの大きさに関わらず,一つあたり1,000円 で治療がうけられます.いまだけの特別ご奉仕価格です」

――これで鞠絵のような綺麗な頬になれるかも.でもいったい 幾らかかるのかしら――

梅子は美貌の友人を思い浮かべながら,さっそく手鏡に映った 頬のシミを数え始めたのであった.

便 宜 的 に , 手 鏡 の 大 き さ は 𝑁𝑁 × 𝑀𝑀

( 𝑁𝑁, 𝑀𝑀 ≤ 50 )で,シミ( S )は8近傍で隣接 しているとつながっているとします.この時,

シミの数を数えるプログラムを書きなさい.

* * *

* S *

S の8近傍とは

下の*の部分

(24)

問題の入力例

S S S . . . . . . S . . . S S S . . . . S S S . . . . . . S . . . . S S S . . S . S S S . . . . S . . S . . . S . S

. . . S . . . S S S S S . S S S S S . . . . . S . . . S . S . . . S S . . . . S . S . . . S . . . . S . . S . . . . S . . . . S . S . S . S S S S S . S . S . S . . . S . . . . . . . S . S S S S S . . . . S . . . S . . . . . . S . . S . . S . S S S S S . S . S . S . S S S S S . S . S . S . S S . .

例1( 𝑁𝑁 = 8, 𝑀𝑀 = 8 ) 例2( 𝑁𝑁 = 15, 𝑀𝑀 = 12 )

S はシミを表し,ピリオド「 . 」は きれいな肌を表す

例1の場合,シミの数は4

参照

関連したドキュメント

A sequence α in an additively written abelian group G is called a minimal zero-sum sequence if its sum is the zero element of G and none of its proper subsequences has sum zero..

Let G be a split reductive algebraic group over L. In what follows we assume that our prime number p is odd, if the root system Φ has irreducible components of type B, C or F 4, and

It is shown that if the data R satisfy the property of encoding a finite number of positive root systems, each corresponding to an Iwasawa nilpotent algebra, then the above

The metric induced on a null hypersurface by a neutral metric has degen- erate signature (0, +, −) and the null cone degenerates to a pair of totally null planes, called α−planes

The most far reaching generalisation of the Artin primitive root conjecture was considered by Lenstra [292], in the context of his research on Euclidean number fields: Let K be a

It implies that if the initial (M, φ) is c log-convergent (resp. overconvergent), then Dwork’s unit root zeta function in the ordinary rank one case can be expressed in terms

This latter group is precisely the automorphism group of the linear matroid determined by the given vectors.. ∗ Research supported by NSF

, n is called a recursive tree if the vertex labelled 1 is the root and, for all 2 ≤ k ≤ n, the sequence of vertex labels in the path from the root to k is increasing (Stanley