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

61
アルゴリズムとデータ構造 講義スライド 4 スタック・キュー リスト 茨城大学 工学部 知能システム工学科 井上 康介 E2棟801号室

Upload: others

Post on 29-Jan-2020

0 views

Category:

Documents


0 download

TRANSCRIPT

Page 1: アルゴリズムとデータ構造 - Ibarakibiorobot2.ise.ibaraki.ac.jp/inoue/ad/material/slide-4.pdfアルゴリズムとデータ構造 講義スライド4 スタック・キュー

アルゴリズムとデータ構造講義スライド 4

スタック・キュー リスト

茨城大学 工学部 知能システム工学科 井上 康介

E2棟801号室

Page 2: アルゴリズムとデータ構造 - Ibarakibiorobot2.ise.ibaraki.ac.jp/inoue/ad/material/slide-4.pdfアルゴリズムとデータ構造 講義スライド4 スタック・キュー

第5章 データ構造

授業の最初で述べたとおり,大量・複雑なデータを扱う上では,データを構造化・組織化する必要がある.

どのようなデータ構造 (data structure) を採用するかによって,問題解決のアルゴリズムも異なってくる.

つまり,アルゴリズムとデータ構造とは密接な関係にあり,よいデータ構造を選ぶことがよいプログラムを作ることにつながる.

特に重要なデータ構造として,リスト,ツリー,グラフが上げられる.ツリー (木) は第6章,グラフは第7章で扱う.

この章では,スタック (stack: 棚),キュー (queue: 待ち行列),リスト (list) を説明する.

2p.213

Page 3: アルゴリズムとデータ構造 - Ibarakibiorobot2.ise.ibaraki.ac.jp/inoue/ad/material/slide-4.pdfアルゴリズムとデータ構造 講義スライド4 スタック・キュー

データ構造とは

代表的なデータ構造として,以下のものが挙げられる:

表については,ふつうの配列で扱うことができるので,ここでは扱わない.

良いデータ構造とは何かの基準は一概には言えないが,データの追加・削除・検索が効率よく行えること,および,複雑な構造が簡潔に表現できることなどがある.

3

1.表 (table: テーブル)2.棚 (stack: スタック)3.待ち行列 (queue: キュー)4.リスト (list)5.木 (tree: ツリー)6.グラフ (graph)

p.214~215

Page 4: アルゴリズムとデータ構造 - Ibarakibiorobot2.ise.ibaraki.ac.jp/inoue/ad/material/slide-4.pdfアルゴリズムとデータ構造 講義スライド4 スタック・キュー

スタック (後入れ先だし)

キュー (先入れ先出し)

テーブル (ただの配列)

これから学ぶデータ構造

4

a[0]

a[1]

a[2]

a[3]

a[4]

a[5]

a[0][0] a[0][1]

a[1][0] a[1][1]

a[2][0] a[2][1]

a[3][0] a[3][1]

a[4][0] a[4][1]

a[5][0] a[5][1]

Page 5: アルゴリズムとデータ構造 - Ibarakibiorobot2.ise.ibaraki.ac.jp/inoue/ad/material/slide-4.pdfアルゴリズムとデータ構造 講義スライド4 スタック・キュー

リスト (一本鎖)

ツリー (分岐を含む)

これから学ぶデータ構造

5

データ データ データ

次のデータへのポインタ 終端は NULL

データ

データ

データ データ

データ データ

Page 6: アルゴリズムとデータ構造 - Ibarakibiorobot2.ise.ibaraki.ac.jp/inoue/ad/material/slide-4.pdfアルゴリズムとデータ構造 講義スライド4 スタック・キュー

グラフ・ネットワーク (ループも含む)

これから学ぶデータ構造

6

秦病院 油縄子 Bulldog

茨大カワチ

カスミ

秦病院 油縄子 Bulldog

茨大カワチ

カスミ

10分

5分

7分

6分

4分

3分

グラフ (接続関係だけ) ネットワーク (区間に属性)

Page 7: アルゴリズムとデータ構造 - Ibarakibiorobot2.ise.ibaraki.ac.jp/inoue/ad/material/slide-4.pdfアルゴリズムとデータ構造 講義スライド4 スタック・キュー

スタック

スタック (stack) は,一時的データ置き場 (buffer) として利用される代表的なデータ構造の一つである.

データを棚に上から順に積んでいき,取り出すときも上から取り出す.データを放り込む操作を プッシュ(push),取り出す操作を ポップ (pop) という.

このように,取り出せるのは最後に

入れたデータからである.

このような方式を LIFO (Last In First

Out: 後入れ先出し) と呼ぶ.

スタックの実装は,1次元配列を

用いて行われる.

7

Data-0Data-1Data-2

スタック (棚)

pushpop

p.216~218 (前半)

Page 8: アルゴリズムとデータ構造 - Ibarakibiorobot2.ise.ibaraki.ac.jp/inoue/ad/material/slide-4.pdfアルゴリズムとデータ構造 講義スライド4 スタック・キュー

スタック

スタックは,データを格納する配列と「現在いくつのデータが入っているか」を示すスタックポインタから実現される.

配列を stack[],スタックポインタを sp とするとき,次のようになる.

sp==4 での push は「あふれ」

sp==0 での pop は「空っぽ」

これらはエラー処理

8

Data-A

sp= 0123stack[0]

Data-B

stack[1]

stack[2]

stack[3]

Data-CData-Dプッシュポップ

4

Page 9: アルゴリズムとデータ構造 - Ibarakibiorobot2.ise.ibaraki.ac.jp/inoue/ad/material/slide-4.pdfアルゴリズムとデータ構造 講義スライド4 スタック・キュー

スタック (ソースコード)

p.217.

プリプロセッサでスタックのサイズ MaxSize を定義.

スタックの実体である配列 stack[] はグローバル変数として定義.スタックポインタ sp もグローバル変数として定義し,0 に初期化している (ほんとうは,グローバル変数は極力使わないことが望ましい).

プッシュ関数 int push(int n); とポップ関数 int

pop(int *n); では,int型の返値を返す.

メイン関数では,while文でループさせてユーザからの文字入力を繰り返し受けている.

int getchar(void) は,キーボードから文字を読み込み,int型に変換して返す関数.

9

Page 10: アルゴリズムとデータ構造 - Ibarakibiorobot2.ise.ibaraki.ac.jp/inoue/ad/material/slide-4.pdfアルゴリズムとデータ構造 講義スライド4 スタック・キュー

スタック (ソースコード)

while文のかっこ内でキーボードから1文字を読み込む.

読み込んだ文字が 'i' あるいは 'I' であるときは,ユーザからデータを入力して,それをスタックにプッシュする.

スタックが既にいっぱいの場合, それ以上プッシュできない.このとき,関数 push() は -1 を返す.

読み込んだ文字が 'o' あるいは 'O' であるときは,スタックからデータを1つポップする.

スタックが空の場合は取り出せないので関数 pop() は-1 を返す.

プッシュもポップも,正常に実行した場合は返値は 0

となる.

10

Page 11: アルゴリズムとデータ構造 - Ibarakibiorobot2.ise.ibaraki.ac.jp/inoue/ad/material/slide-4.pdfアルゴリズムとデータ構造 講義スライド4 スタック・キュー

スタック (ソースコード)

さて, ポップを行ったとき,データが正しく取り出せれば0 を,スタックが空のときは -1 を返す.このために「返値」という情報チャンネルは使われてしまう.

では, 2つめの情報「ポップにより取り出したデータ」をどうやって呼出側に返せばよいだろうか?

データを返すために,ここでは ポインタ渡し (参照渡し)という方法が使われている (通常は 値渡し).

ポップしたデータを入れる変数は,main関数のローカル変数 n であり,main関数で pop() を呼び出すとき,引数としてその n へのポインタ (n のアドレス) を pop()

に渡す.

pop() 内では,もらったアドレスにデータを書き込む.

11

Page 12: アルゴリズムとデータ構造 - Ibarakibiorobot2.ise.ibaraki.ac.jp/inoue/ad/material/slide-4.pdfアルゴリズムとデータ構造 講義スライド4 スタック・キュー

スタック (ソースコード)

それぞれの関数を見ていこう.

push() では,最初にサイズあふれ (sp >= MaxSize) が起きていないかチェックしている.

起きていない場合は,配列の sp の位置に,値渡しで受け取ったデータ n を書き込み,sp を1つ増やす.正常終了なので,この場合は返値として 0 を返す.

サイズあふれが起きていた場合,何もせずに -1 を返す.

値渡しでデータを受け取る際,main関数のローカル変数n がコピーされ,これが関数に渡されている.従って,関数 push() の中で n に変更を加えたとしても,main関数内の n の値には影響しない.

12

Page 13: アルゴリズムとデータ構造 - Ibarakibiorobot2.ise.ibaraki.ac.jp/inoue/ad/material/slide-4.pdfアルゴリズムとデータ構造 講義スライド4 スタック・キュー

スタック (ソースコード)

一方,pop() では,main関数内のローカル変数 n のポインタを受け取る.

関数 pop() 内においては,n はそのポインタ (アドレス) を示す変数であり, そのアドレスに書かれた情報を読み書きするときは *n としてアクセスする.

関数では,まずスタックが空 (sp==0) でないかチェックし,空でないときは sp を1だけ減らしてから 配列の sp

番要素を *n に書き込み,正常終了を示す返値 0 を返す.

空の場合は何もせずに異常終了の返値 -1 を返す.

13

Page 14: アルゴリズムとデータ構造 - Ibarakibiorobot2.ise.ibaraki.ac.jp/inoue/ad/material/slide-4.pdfアルゴリズムとデータ構造 講義スライド4 スタック・キュー

関数 pop()の引数はポインタ渡しによる n.これはmain()関数内のローカル変数 n への参照 である.

スタックからのポップ

12

25

-51

stack

sp

3

c

?

n

?

main()

n

int pop(int *n)

?

グローバル変数 関数

’o’

2int

0

?-51

int *n

pop(&n)

Page 15: アルゴリズムとデータ構造 - Ibarakibiorobot2.ise.ibaraki.ac.jp/inoue/ad/material/slide-4.pdfアルゴリズムとデータ構造 講義スライド4 スタック・キュー

キュー

スタックでは,データを入れたのとは逆の順番でデータを取り出していた (LIFO方式).このやり方は,まさに再帰呼出で紹介したような用途に向く.一方,例えば役所の窓口に人がどんどん来る場合を考えよう.

LIFO方式では,最後に列に加わった人から処理を行うが,これは言うまでもなく不公平である.列に並んだ人々を,最初に並んだ人から順番に捌いていく必要がある.これを FIFO (First In First Out: 先入れ先出し) という.

このモデルを キュー (queue: 待ち行列) という.

例)PCがインターネットで通信するとき,ネットからは続々とデータが流入するが,処理が追いつかない場合がある.このとき,データを一時的にキューに置いておき,手が空き次第,先に来たデータから順に処理する.

15p.222~224

Page 16: アルゴリズムとデータ構造 - Ibarakibiorobot2.ise.ibaraki.ac.jp/inoue/ad/material/slide-4.pdfアルゴリズムとデータ構造 講義スライド4 スタック・キュー

キュー

実装は,スタックと同様,1次元配列.

待ち行列の先頭位置を head,終端を tail とする.

tail の位置にデータを入れ,head から出す.

キューのサイズは, (配列サイズ)ー1 になる.

16

これが限界!

Data-A

Data-B

Data-C

Data-D

Data-E

Data-F

Data-G

Data-H

queue[0]

queue[1]

queue[3]

queue[2]

queue[4]

queue[5]

tail head

tail

tail

tail

tail

tail

head

head

head

head

head

Page 17: アルゴリズムとデータ構造 - Ibarakibiorobot2.ise.ibaraki.ac.jp/inoue/ad/material/slide-4.pdfアルゴリズムとデータ構造 講義スライド4 スタック・キュー

キュー (ソースコード)

p.223.

プリプロセッサで配列サイズ MaxSize を100としている.よって,容量は99となる.

キューの実体である配列 queue[],および先頭・末端位置 head,tail はグローバル変数として定義.

キューにデータを入れる関数は int queuein(int n),データを取り出す関数は int queueout(int *n).

main関数では,スタックの場合と同様,キーボードから1文字ずつの入力を繰り返し受け付け,それが 'i' または'I' のときはデータを入れ,'o' または 'O' の時はデータを出している.

17

Page 18: アルゴリズムとデータ構造 - Ibarakibiorobot2.ise.ibaraki.ac.jp/inoue/ad/material/slide-4.pdfアルゴリズムとデータ構造 講義スライド4 スタック・キュー

キュー (ソースコード)

キューがいっぱいの時に更にデータを入れようとするとqueuein() が -1 を返す,キューが空の時に取り出しをしようとすると queueout() が -1 を返す点はスタックと同じ.また,queueout() において,取り出したデータを返すために参照渡しを使っている点も同じ.

キューにデータを入れる関数 queuein() においては,キューがいっぱいであるかどうかを,tail の次の位置がhead と同じかどうかによって調べている.

「次の位置」を求めるために,(tail+1)%MaxSize という計算をしている.MaxSize が 100 とするとき,もしもtail が配列最後の位置 99 にあったとすれば,次の位置は先頭 0 であり,それはこの計算で求まる.

18

Page 19: アルゴリズムとデータ構造 - Ibarakibiorobot2.ise.ibaraki.ac.jp/inoue/ad/material/slide-4.pdfアルゴリズムとデータ構造 講義スライド4 スタック・キュー

キュー (ソースコード)

tail の次の位置が head と重ならなければ,キューにはまだ余裕がある.このとき,tail の位置にデータ n を書き込み,tail を1つ進める.

queueout() では,最も古いデータを取り出す.その位置は head である.

最初にキューが空かどうかを調べる.head == tail ならキューは空っぽなので取り出せない.

head 位置のデータ queue[head] を,ポインタ変数 *n

に代入することで main関数に返した後,head を一つ進める.

head の「次の位置」は,(head+1)%MaxSize である.

例)配列サイズが100のとき,99の次は(99+1)%100=0.

19

Page 20: アルゴリズムとデータ構造 - Ibarakibiorobot2.ise.ibaraki.ac.jp/inoue/ad/material/slide-4.pdfアルゴリズムとデータ構造 講義スライド4 スタック・キュー

リストとは

リスト (list) とは,データを鎖のようにつなぎ合わせて管理するデータ構造である.

各データは次のデータを指すポインタ を持っている.このポインタで次々とデータをつないでいく (リンクする) ことによって,任意の数のデータを扱える.

一方,データを配列で扱う場合は,あらかじめ決められた配列サイズを超えるデータは格納できない.

リストでは,データを新たに格納するたびに,そのデータのための記憶領域を新規に確保していくので,サイズの制限がない (もちろんコンピュータ上のメモリサイズ以上は格納できないが).

といっても理解しづらいので,絵的に理解しよう.

20p.228~229

Page 21: アルゴリズムとデータ構造 - Ibarakibiorobot2.ise.ibaraki.ac.jp/inoue/ad/material/slide-4.pdfアルゴリズムとデータ構造 講義スライド4 スタック・キュー

リストとは

リスト構造においてつながれる各データ要素 (ノード:node) は,レコード によって構成される.

ノードはデータを入れるデータ部 の他に,次のノードのアドレスを指し示すポインタ部 を含む.

最後尾のポインタ部は NULL としておく.

プログラムからリスト構造にアクセスするため,リストの先頭へのポインタ head を変数として記録しておく.

21

A B

データ部 ポインタ部

ノード

C

head

Page 22: アルゴリズムとデータ構造 - Ibarakibiorobot2.ise.ibaraki.ac.jp/inoue/ad/material/slide-4.pdfアルゴリズムとデータ構造 講義スライド4 スタック・キュー

リストとは

リストのノードはレコードであるから,C言語においては構造体を用いて実装することができる.

このように,自分自身と同じ構造体へのポインタを持つ構造体を 自己参照構造体 (self-referential structure) という.

教科書 p.228 のコードに誤植.tfieled ではなくtfield型です.

22

struct tfield {

char name[20];

char tel[20];

struct tfield *pointer;

};

データ部

ポインタ部

struct tfield {

char name[20];

char tel[20];

struct tfield *pointer;

};

Page 23: アルゴリズムとデータ構造 - Ibarakibiorobot2.ise.ibaraki.ac.jp/inoue/ad/material/slide-4.pdfアルゴリズムとデータ構造 講義スライド4 スタック・キュー

リストとは

前述の通り,リスト構造では,必要に応じてノードを追加・削除することができる.

このためにはノードのためのメモリ領域を動的に確保・解放 しなくてはならない.

この目的で使われるのがメモリの動的確保 という方法である.

C言語ではメモリの動的確保のための関数が2つ用意されている.これらの間には微妙な違いがあるがほとんど同じ.ここでは malloc() を使う.

23

void *malloc(size_t size);

void *calloc(size_t nmemb, size_t size);

Page 24: アルゴリズムとデータ構造 - Ibarakibiorobot2.ise.ibaraki.ac.jp/inoue/ad/material/slide-4.pdfアルゴリズムとデータ構造 講義スライド4 スタック・キュー

リストとは

関数 malloc() は,引数 size で与えられたバイト数のメモリ領域をそのプログラムのために確保し, 確保された領域の先頭アドレスを返値として返す.

例えば,int型の配列 (要素数10) の領域を確保して配列 a[] を作るには,以下のようにする.

ただし,sizeof(型名) は,その型のサイズ (バイト数) を表す.

24

void *malloc(size_t size);

int *a;

a = (int *)malloc(sizeof(int)*10);

Page 25: アルゴリズムとデータ構造 - Ibarakibiorobot2.ise.ibaraki.ac.jp/inoue/ad/material/slide-4.pdfアルゴリズムとデータ構造 講義スライド4 スタック・キュー

リストとは

メモリ動的確保関数 malloc() を使って,ノードの動的確保を以下の関数で記述できる.

この関数を用いて,

とすると,tfield型のデータ領域を新たに確保し,

それへのポインタが pに代入される.

25

struct tfield *talloc(void)

{

return (struct tfield *)malloc(sizeof(struct tfield));

}tfield型ポインタへの型キャスト tfield型のサイズ

struct tfield *p;

p = talloc();

p

初期化前では,何が入っているかは分からない

? ?

Page 26: アルゴリズムとデータ構造 - Ibarakibiorobot2.ise.ibaraki.ac.jp/inoue/ad/material/slide-4.pdfアルゴリズムとデータ構造 講義スライド4 スタック・キュー

リストとは

ノード内部のデータの参照方法は以下の通り.

下記のように,ノードへのポインタ pによってノードが指定されている場合,名前欄は p->name,電話番号欄はp->tel,ポインタ欄は p->pointerにより参照される.(*p).nameとかと同じ意味.

一方, ノードがポインタでなく実体で記述されている

場合,つまり struct tfield node; として指定されている場合は,それぞれの欄は node.name,node.tel,node.pointer として参照される.

何を言っているか分からない人は十分復習すること.

26

inoue 0123-45-6789

p p->name p->tel p->pointer

Page 27: アルゴリズムとデータ構造 - Ibarakibiorobot2.ise.ibaraki.ac.jp/inoue/ad/material/slide-4.pdfアルゴリズムとデータ構造 講義スライド4 スタック・キュー

教科書p.230の例題 34は,ユーザが名前と電話番号を次々に入力したとき,そのデータが入力されたのと逆順で並ぶような リストを作成する手順である.

リストの先頭ノードを示すポインタを head とする.

初期状態は空のリストであり,このとき head は NULL.

データ追加手順は以下の通り.

(1)ノード 1つを新規生成し,p で指し示す:p = talloc();

(2) p が指すノードにデータを書き込む

(3) p が指すノードのポインタ部に head をコピーp->pointer = head;

(4) headから p が指すノードを指す:head = p;

p

head

入力とは逆順なリストの作成

27

Sato

copy

copy

Page 28: アルゴリズムとデータ構造 - Ibarakibiorobot2.ise.ibaraki.ac.jp/inoue/ad/material/slide-4.pdfアルゴリズムとデータ構造 講義スライド4 スタック・キュー

phead

SatoOta

次以降も同じ手順を繰り返すことで,入力とは逆順のリストを生成できる.

(1)ノード 1つを新規生成し,p で指し示す:p = talloc();

(2) p が指すノードにデータを書き込む

(3) p が指すノードのポインタ部に head をコピー:p->pointer = head;

(4) head から p が指すノードを指し示す:head = p;

手順 (3)と (4)の順序を取り違えるとまずいことになる.

入力とは逆順なリストの作成

28

p

head SatoOta ?

Page 29: アルゴリズムとデータ構造 - Ibarakibiorobot2.ise.ibaraki.ac.jp/inoue/ad/material/slide-4.pdfアルゴリズムとデータ構造 講義スライド4 スタック・キュー

入力とは逆順なリストの作成

これを繰り返すと,入力されたのとは逆の順番のリスト構造ができあがる.

29

A

head

B

head

ABC

head

ABCD

Page 30: アルゴリズムとデータ構造 - Ibarakibiorobot2.ise.ibaraki.ac.jp/inoue/ad/material/slide-4.pdfアルゴリズムとデータ構造 講義スライド4 スタック・キュー

入力とは逆順なリストの作成 (ソース)

p.231.

動的メモリ確保関数 malloc() を使うため stdlib.h

をインクルードする.

main関数では,ローカル変数として,リストの先頭を示すポインタ head,新規ノードのポインタ p を宣言,head を NULL で初期化している.

次の while文では,まず talloc() により新規ノードを作成してそのアドレスを p に代入し,ユーザから入力された2つの文字列を p->name,p->tel に代入.

もしユーザから入力終了信号を受けた場合は, ループを抜ける.そうでないときは, (1) 新ノードのポインタ部に旧リストの先頭アドレス (head) を代入,(2) headの指し示す先を新ノードに変更.

30

Page 31: アルゴリズムとデータ構造 - Ibarakibiorobot2.ise.ibaraki.ac.jp/inoue/ad/material/slide-4.pdfアルゴリズムとデータ構造 講義スライド4 スタック・キュー

入力とは逆順なリストの作成 (ソース)

while文を抜けた時点で,ユーザから入力されたデータ群を逆順に保存したリスト構造ができている.

その次の処理は,リスト構造の内容を表示する.

最初にポインタ変数 p に head をコピー.これにより,p

は先頭ノードのアドレスを示す.

次の while文は p が NULL でない限り繰り返す.

まず,p が指し示すノードの内容を表示.

次に,p = p->pointer とすることで,ポインタ変数 p

が,今表示したノードの次のノードのアドレスを指し示すようにする.つまり,ここで p は「視点」の役割.

末尾のノードのポインタ部は NULL なので,これにより p

に NULL が入って while文を抜けることになる.

31 次ページで説明

Page 32: アルゴリズムとデータ構造 - Ibarakibiorobot2.ise.ibaraki.ac.jp/inoue/ad/material/slide-4.pdfアルゴリズムとデータ構造 講義スライド4 スタック・キュー

リストの内容表示について

前のページの,リストの内容表示の部分を説明する.

今回は,変数 pは「視点」として使う.

32

p=head;

while(p!=NULL){

printf("%15s%15s¥n",p->name,p->tel);

p=p->pointer;

}

p

名3 電3 名2 電2 名1 電1

head

作ったリスト構造

名2 電2

表示

名3 電3

表示

名1 電1

表示

pがもともと指し示していたノードのポインタ部の情報をpにコピーすると言うことは…

視点pはもう一つ次のノードを指し示す!

ループを抜けて終了

※ 講義スライドでは,いちいち変数pから矢印を描くのは煩雑なので,pが示しているノードの上に単に「p」と表記することが多いです.

Page 33: アルゴリズムとデータ構造 - Ibarakibiorobot2.ise.ibaraki.ac.jp/inoue/ad/material/slide-4.pdfアルゴリズムとデータ構造 講義スライド4 スタック・キュー

ノードへのポインタの表示方法

今後,先ほどのポインタ変数 p のように,様々なノードを指し示すポインタについては,上の図のようにいちいち矢印で指し示さず,下の図のようにノードに “p” と示すだけにする.p が指し示すノードを「ノード p」などと書く.

33

head

p

Iida Ota Sato

head

p

Iida Ota Sato

p

p=p->pointer;

Page 34: アルゴリズムとデータ構造 - Ibarakibiorobot2.ise.ibaraki.ac.jp/inoue/ad/material/slide-4.pdfアルゴリズムとデータ構造 講義スライド4 スタック・キュー

head

A B

入力順のリストの作成

ここまでの話は,入力順と逆順のリストの作成.では,入力順のリストはどうすれば作れるか?

途中まで作ったリストの最後尾のさらにあとに新規ノードを追加していけばよい.

仕上げとして,最後に作ったノードのポインタ部を NULL にする.

34

head

A B

C D

Page 35: アルゴリズムとデータ構造 - Ibarakibiorobot2.ise.ibaraki.ac.jp/inoue/ad/material/slide-4.pdfアルゴリズムとデータ構造 講義スライド4 スタック・キュー

head

A B

old

head

入力順のリストの作成

面倒な問題:ノード作成手順が「最初のノード」と「次以降のノード」で異なる.

最初のノードへのポインタ:head から

次以降のノードへのポインタ:既存リスト最後尾から

最後尾ノードへのポインタを old とすると…

35

A

C

ノード作成: head=talloc();

データ記入: head->name, head->tel へ

ノード作成: p=talloc(); old->pointer=p;

データ記入: p->name, p->tel へ

pコードが異なる

Page 36: アルゴリズムとデータ構造 - Ibarakibiorobot2.ise.ibaraki.ac.jp/inoue/ad/material/slide-4.pdfアルゴリズムとデータ構造 講義スライド4 スタック・キュー

教科書 p.232~233 のソースコード (Dr34_1) 前半部

ムダhead

入力順のリストの作成 (ソースコード)

36

head=talloc();

scanf("%s %s",head->name,head->tel);

old=head;

while (p=talloc(),scanf("%s %s",p->name,p->tel)!=EOF){

old->pointer=p;

old=p;

}

old->pointer=NULL;

前処理

後処理

A B

old

C

pold oldp p

EOF

( 最後尾ノードをポインタ old でポイントしておく)

Page 37: アルゴリズムとデータ構造 - Ibarakibiorobot2.ise.ibaraki.ac.jp/inoue/ad/material/slide-4.pdfアルゴリズムとデータ構造 講義スライド4 スタック・キュー

先ほどのソース・コードのデータ入力部は…

データ入力処理が scanf() 一つだけならいいが,より複雑

な処理の場合,これは避けたい.

これを避けるための一つの方法がダミー・ノードである.

先頭ノードはデータを格納せず,ダミーとする.

ダミー・ノード

37

head=talloc();

scanf("%s %s",head->name,head->tel);

old=head;

while (p=talloc(),scanf("%s %s",p->name,p->tel)!=EOF){

old->pointer=p;

old=p;

}

old->pointer=NULL;

前処理での入力処理

ループ内での入力処理

Page 38: アルゴリズムとデータ構造 - Ibarakibiorobot2.ise.ibaraki.ac.jp/inoue/ad/material/slide-4.pdfアルゴリズムとデータ構造 講義スライド4 スタック・キュー

教科書 p.234~235 のソースコード (Dr34_2) 前半部

head

ダミー・ノード (ソースコード)

38

head=talloc();

scanf("%s %s",head->name,head->tel);

old=head;

while (p=talloc(),scanf("%s %s",p->name,p->tel)!=EOF){

old->pointer=p;

old=p;

}

old->pointer=NULL;

A

old

B

pold oldp p

C

ダミー・ノード 実際のデータ

old

入力処理はこれに統一

Page 39: アルゴリズムとデータ構造 - Ibarakibiorobot2.ise.ibaraki.ac.jp/inoue/ad/material/slide-4.pdfアルゴリズムとデータ構造 講義スライド4 スタック・キュー

教科書 p.234~235 のソースコード (Dr34_2) 後半部(リスト内容の表示)

head

ダミー・ノード (ソースコード)

39

p=head->pointer;

while (p!=NULL){

printf("%15s%15s¥n",p->name,p->tel);

p=p->pointer;

}

A B

pp p

C

ダミー・ノード 実際のデータ

A B C

p: NULL

head->pointer ( 視点は第2ノードからスタートする)

Page 40: アルゴリズムとデータ構造 - Ibarakibiorobot2.ise.ibaraki.ac.jp/inoue/ad/material/slide-4.pdfアルゴリズムとデータ構造 講義スライド4 スタック・キュー

リストへの挿入

リスト構造は,データ列の途中の任意の位置においてデータを挿入・削除するのに適している.

配列の場合,挿入・削除に伴って他のデータの移動が必要となるが,リストではつなぎ換えだけでよい.

40

? ?

p.236~240

B

A C Dリストの場合

配列の場合a[0] a[1] a[2] a[3] a[4] a[5] a[6] a[7] a[8]

A C D

B

Page 41: アルゴリズムとデータ構造 - Ibarakibiorobot2.ise.ibaraki.ac.jp/inoue/ad/material/slide-4.pdfアルゴリズムとデータ構造 講義スライド4 スタック・キュー

リストへの挿入

教科書の例では,既存のリストの中から,特定のデータ (キーデータ) を逐次探索で探し出し,そのノードの次の位置にデータを挿入する.

例えば Bのデータの次にCを挿入する場合:

視点 pを headで初期化.pの位置のデータがBなら発見.そうでないなら視点を右へずらす (p=p->pointer;).

発見したらp->pointerのところにノードを挿入.

41

A B D

head p p

- 新規ノードnを作成

- そのポインタ部を次のノードへ

- pのポインタ部を新しいノードへ

ノード挿入手順:

n

??C

n=talloc(); scanf("%s",n->name);

n->pointer=p->pointer;

p->pointer=n;

Page 42: アルゴリズムとデータ構造 - Ibarakibiorobot2.ise.ibaraki.ac.jp/inoue/ad/material/slide-4.pdfアルゴリズムとデータ構造 講義スライド4 スタック・キュー

リストへの挿入 (ソース)

p.236-238.

リストの先頭位置を示すポインタ head はグローバル変数として宣言(struct tfield の型定義の部分).

リストの生成は関数 genlist()にまとめられている.ここでは逆順リストの生成を行っている.

リスト内容表示は関数 displist()にまとめられている.

ノードの挿入を行っているのは関数 link()である.引数は char型へのポインタ keyであり,キーデータの文字列に対応している.(keyというポインタ (アドレス) は,char型の配列の位置を表しており, その配列に文字コード列が入っている. )

42

key 'I' 'n' 'o' 'u'

73 110 111 117key の実体 (配列) は main関数のローカル変数である!

key[0]key[1]key[2]key[3]

Page 43: アルゴリズムとデータ構造 - Ibarakibiorobot2.ise.ibaraki.ac.jp/inoue/ad/material/slide-4.pdfアルゴリズムとデータ構造 講義スライド4 スタック・キュー

リストへの挿入 (ソース)

関数 link() の中で行っている処理を見てみよう.

ローカル変数としてノードへのポインタpとnがある.

最初にノードを一つ作成し,それへのポインタをnに代入.その中に入れるデータをキーボードから入力している.

次に p を視点として,head で初期化.

while (p!=NULL) のループを回し,いま視点を置いているノードの name欄が key と一致するかどうかをstrcmp() によって調べる.

合致した場合は,視点位置 p の次にノード n を挿入.まず,n のポインタを p の次 (p->pointer) に向ける.次に,p のポインタを n に向ける.

合致しない場合は視点を次へ送る (p=p->pointer).43

Page 44: アルゴリズムとデータ構造 - Ibarakibiorobot2.ise.ibaraki.ac.jp/inoue/ad/material/slide-4.pdfアルゴリズムとデータ構造 講義スライド4 スタック・キュー

リストへの挿入 (ソース)

キーデータが存在しなかった場合, 最後のノードで合致しないことが分かった後,p=p->pointer; の処理によって,p には NULL が入る.

これにより while文の条件 (pが NULLでない限り続けろ) が満たされなくなり,while文を抜ける.

すると, 関数の最後でエラーメッセージを表示して終了する.つまり,新しいノード n は作りっぱなしで放置されてしまう.

(見つかったノード p がリストの最後尾であった場合も,新規ノード n のポインタを n->pointer=p->pointer

とするので,n->pointer はきちんと NULL になる.)

44

Page 45: アルゴリズムとデータ構造 - Ibarakibiorobot2.ise.ibaraki.ac.jp/inoue/ad/material/slide-4.pdfアルゴリズムとデータ構造 講義スライド4 スタック・キュー

リストへの挿入 (キーデータ不在時)

キーデータが見つからないときはリストの先頭へデータを追加するように書き直したコードが p.238-240.

単に,先程の whileループを抜けたところでエラーメッセージを出すだけではなく, そこに先頭への挿入手順を書き加えればよい.

先頭への挿入手順 (復習):(1) 新しいノード n のポインタ部を,既存リストの先頭位置 (それまでの head) に向ける.(2) head を新しいノード n に向ける.

45

head

Keisatsu 110

Kyuukyuu 119

n

Page 46: アルゴリズムとデータ構造 - Ibarakibiorobot2.ise.ibaraki.ac.jp/inoue/ad/material/slide-4.pdfアルゴリズムとデータ構造 講義スライド4 スタック・キュー

リストからの削除

既に存在するリストからキーデータを逐次探索で探し出し,そのノードをリストから削除する関数 del()を作る.

ダミー・ノードを用いない場合は,先頭データを削除する場合 と 先頭以外の場合 とで手順が異なるため,場合分けが必要である.

視点2つ,p と old を用意し,双方 head で初期化.

while(p!=NULL) のループで,視点 p を回す.

キーデータが先頭 (A) だったとき (キーデータ発見の時点で p==head)

46

A B C

p,oldhead

head を p->pointer に向ける.

ほんとは不要になったノードの消去を!

p.243~247

Page 47: アルゴリズムとデータ構造 - Ibarakibiorobot2.ise.ibaraki.ac.jp/inoue/ad/material/slide-4.pdfアルゴリズムとデータ構造 講義スライド4 スタック・キュー

リストからの削除

キーデータが先頭ではなかったとき (例えば C)

キーデータと一致するノード (p) の一つ手前のノード(old) のポインタを,p の次のノード位置 (p->pointer) に向けかえる.

p, old は head で初期化していた.最初の位置 (A) では発見しなかった場合,old=p としてから p を一つ進める(p=p->pointer).

発見したら old のポインタを p->pointer へ向ける.

47

p, old p

CB DA

old,

キーデータが C の時:p

old->pointer = p->pointer;

Page 48: アルゴリズムとデータ構造 - Ibarakibiorobot2.ise.ibaraki.ac.jp/inoue/ad/material/slide-4.pdfアルゴリズムとデータ構造 講義スライド4 スタック・キュー

リストからの削除

発見したノード (p) が最後尾だとした場合も今のアルゴリズムできちんと処理可能.

48

A

A B C

p,old

ノードが複数の場合 (検索キー:C)

old,p p

old->pointer = p->pointer; = NULL

ノードが一つだけしかなかった場合 (検索キー:A)

p,old

head = p->pointer; = NULL

Page 49: アルゴリズムとデータ構造 - Ibarakibiorobot2.ise.ibaraki.ac.jp/inoue/ad/material/slide-4.pdfアルゴリズムとデータ構造 講義スライド4 スタック・キュー

リストからの削除 (ソース)

p.243-244.削除の関数は del().

49

void del(char *key)

{

struct tfield *p,*old;

p=old=head;

while (p!=NULL){

if (strcmp(key,p->name)==0){

if (p==head) head=p->pointer;

else old->pointer=p->pointer;

return;

}

old=p;

p=p->pointer;

}

printf("キーデータが見つかりません¥n");

}

キー文字列へのポインタを受け取っている

p:視点,old:視点の一つ前のノード

初期化

視点位置 p での照合

場合分けしてポインタ向けかえ抜ける

視点を右にずらす(oldは一個遅れて追従)

エラーメッセージ

Page 50: アルゴリズムとデータ構造 - Ibarakibiorobot2.ise.ibaraki.ac.jp/inoue/ad/material/slide-4.pdfアルゴリズムとデータ構造 講義スライド4 スタック・キュー

リストからの削除 (ダミー・ノード版)

先程の例では,削除されるべきノードが head に直結している場合と,次以降である場合とで,「head を変更する」場合と「old->pointer を変更する」場合の場合分けが必要となる.

前の議論と同様, この場合もダミー・ノードが有用.

これにより head 直後に対する場合分けがなくなるので,「一つ手前の視点」である old を使う必要がなくなる.

視点位置を p とするとその次を見る.「p の次のノードの名前欄」は p->pointer->name,「次のノードのポインタ欄」は p->pointer->pointer で参照できる.

ソースは p.246.p を head で初期化.p->pointer がNULL でない限り続ける while ループを回す.

50

Page 51: アルゴリズムとデータ構造 - Ibarakibiorobot2.ise.ibaraki.ac.jp/inoue/ad/material/slide-4.pdfアルゴリズムとデータ構造 講義スライド4 スタック・キュー

リストからの削除 (ダミー・ノード版)

ループ内部では,p->pointer->name (pの一つ先のノードのデータ部) と key との照合を行う.

合致しない場合は,単に p を1つ進める.

合致した場合,p->pointer を p->pointer->pointer

に向けかえる.つまり,pのポインタ部を p の二つ先につないでしまう.

51

? A B C

ダミー・ノード

p

検索データが B の場合:

p->pointer->name,つまり A を調べる A≠B より,合致せず

p

ここで合致:pを2つ先 (p->pointer->pointer) につなぐ

Page 52: アルゴリズムとデータ構造 - Ibarakibiorobot2.ise.ibaraki.ac.jp/inoue/ad/material/slide-4.pdfアルゴリズムとデータ構造 講義スライド4 スタック・キュー

メモリ領域の開放

教科書のやり方では,削除されたノードへのポインタを向け変えてすましている.つまり,削除されたノードのメモリ領域は,どこからも参照されないムダ領域として残る.

ノードはもともと,malloc() によって動的に確保されたもの.つまり, プログラムのためにコンピュータから分けてもらった領域である.

教科書のやり方では,ノードを削除してもそのノードのためのメモリ領域はプログラムのために確保され続けるため,大量にノードの作成・削除を繰り返すと,無駄に確保され続ける無用のメモリ領域 (ゴミ) が大量に生まれてしまう.

コンピュータを「ゴミ屋敷」のようにしないためには,きちんとゴミを処分しなくてはならない.即ち,不要になったメモリ領域を開放し,コンピュータに返してやる.

52

Page 53: アルゴリズムとデータ構造 - Ibarakibiorobot2.ise.ibaraki.ac.jp/inoue/ad/material/slide-4.pdfアルゴリズムとデータ構造 講義スライド4 スタック・キュー

メモリ領域の開放

malloc() を使って動的に確保したメモリを解放してシステム (コンピュータ) に返還する関数:free()

例えば,ポインタ p で参照されるノードを解放するには,free(p); とすればよい.これだけで,スライド上に大きな赤のバッテンで描いた消去処理が実現できる.

例えば教科書 p.244 のコードでは,以下を追加すればよい.

53

if (strcmp(key,p->name)==0)

if (p==head)

head=p->pointer;

else

old->pointer=p->pointer;

return;

}

free(p);}

free(p);}

お行儀の良いプログラミングを!

赤ペンなどで書き加えてください

{

{

Page 54: アルゴリズムとデータ構造 - Ibarakibiorobot2.ise.ibaraki.ac.jp/inoue/ad/material/slide-4.pdfアルゴリズムとデータ構造 講義スライド4 スタック・キュー

いろいろなリスト構造

ここまでで扱ってきたリスト構造:head から順にノードを後ろへたどっていき,最後は NULL で終わる.このようなリストを 線形リスト (linear list) と呼ぶ.

リストにはこれ以外の形式もある.

循環リスト (circular list):線形リストの最後が NULL ではなく,先頭ノードへのポインタとなっている.従って,データの終端はない.

54p.248~253

線形リスト

循環リスト

A B C D

A B C D

Page 55: アルゴリズムとデータ構造 - Ibarakibiorobot2.ise.ibaraki.ac.jp/inoue/ad/material/slide-4.pdfアルゴリズムとデータ構造 講義スライド4 スタック・キュー

双方向リスト

また,線形リストでは常に前から後ろへ進むことしかできない.「次のノードへのポインタ (順ポインタ)」はあるが,「手前のノードへのポインタ (逆ポインタ)」はない.

順ポインタとともに,逆ポインタも付け加えたリストがあり,これを 双方向リスト (doubly-linked list) と呼ぶ.

ノードの構造体にはこれら2つのポインタ部, すなわち 順方向のポインタ right,逆方向のポインタ left が入る.

循環型でない場合は,両端の外側ポインタは NULL とし,両端ノードへのポインタ head と tail を用いる.

55

A B C

left right 逆ポインタ

順ポインタhead tail

Page 56: アルゴリズムとデータ構造 - Ibarakibiorobot2.ise.ibaraki.ac.jp/inoue/ad/material/slide-4.pdfアルゴリズムとデータ構造 講義スライド4 スタック・キュー

双方向リストの作成

教科書の例では,末端であるtailを起点にして「逆順リストの作成」を行い,左端をheadからつなぐことにより,逆順リストの生成手順を使って入力順リストを作っている.

ソースは p.249-250.

56

tailp

? ? ?A

p->left=tail;

tail=p;

? ? ?? ? ?

p=talloc();

p->left=tail;

tail=p;

p=talloc();

p->left=tail;

tail=p;

p=talloc();

p p

B C

head

head=p;

p=p->left;

p->right=head;

head=p;

p=p->left;

p->right=head;

head=p;

p=p->left;

p->right=head;

= NULL

Page 57: アルゴリズムとデータ構造 - Ibarakibiorobot2.ise.ibaraki.ac.jp/inoue/ad/material/slide-4.pdfアルゴリズムとデータ構造 講義スライド4 スタック・キュー

循環・双方向リスト

双方向リストの両端のノード同士を相互に接続すれば, 循環・双方向リストとなる (tail は不要となる).

ダミー・ノードが以下の2点で重要な役割を果たす.

(1) 先頭ノードに対する特別な処理の排除

(2) データ探索における番兵の役割

リストの作成時には,まず空きリストから初めて新しいノードを後ろへつなげていく.

57

ダミー・ノード

? A B

? ? ?

head

?

head tail

ダミー・ノード

Page 58: アルゴリズムとデータ構造 - Ibarakibiorobot2.ise.ibaraki.ac.jp/inoue/ad/material/slide-4.pdfアルゴリズムとデータ構造 講義スライド4 スタック・キュー

循環・双方向リスト

既存の循環・双方向リストに新たなノードを加える手順は以下のようになる (順番に注意).

(1) p (新ノード) の right を先頭ノード (head) へ.

(2) p の left を最後尾ノード (head->left) へ.

(3) 最後尾ノード (head->left) の right を p へ.

(4) 先頭ノード (head) の left を p へ.

教科書に誤植:p.252 一つめの A は,正しくは D .

58

?? ?

head

? ?? ?BA

p

この順番を間違うと何が起こるか考えてみること

p

初期状態

Page 59: アルゴリズムとデータ構造 - Ibarakibiorobot2.ise.ibaraki.ac.jp/inoue/ad/material/slide-4.pdfアルゴリズムとデータ構造 講義スライド4 スタック・キュー

自己再編成探索 (試験範囲外)

線形リストとして記録されたデータに対して逐次探索を行うことを考える.

逐次探索ではデータの先頭から1つ1つ調べていくので,後ろにあるものほど探索に時間がかかる.

ここで,一つの傾向を利用して探索効率を高めることを考える:自己再編成探索 (self re-organizing search)

傾向:一度使われたデータは再度使われる可能性が高い

そこで,データが探索されるたびに,探索されたデータを前の方に移動する.

例)PC上で漢字変換をした場合,直前に変換した漢字が今回の第1候補となる「学習機能」.

データの挿入・削除を頻繁に行う リストが適切

59p.266~269

Page 60: アルゴリズムとデータ構造 - Ibarakibiorobot2.ise.ibaraki.ac.jp/inoue/ad/material/slide-4.pdfアルゴリズムとデータ構造 講義スライド4 スタック・キュー

自己再編成探索 (探索データを先頭へ)

まず逐次探索を行う.視点 p と視点の手前 old を用意して head で初期化.

p の位置を照合.一致しないときは old を p のところへ引っ張ってきてから p を進める (p=p->pointer).

一致した場合,まず手前ノード (old) を p の次のノード(p->pointer) へつなげる.

次に,p のポインタを先頭ノード (head) へ向け,head

を p に向ける.

60

A B C D

head

探索データが Cの場合

poldp, , old p

Page 61: アルゴリズムとデータ構造 - Ibarakibiorobot2.ise.ibaraki.ac.jp/inoue/ad/material/slide-4.pdfアルゴリズムとデータ構造 講義スライド4 スタック・キュー

自己再編成探索 (探索データを1つ前へ)

探索データを1つだけ前に持って行くのは少し大変.

探索データと1つ前との入れ替えのために, 「2つ前」の位置も分かっていなければならない:ダミー・ノード, old1, old2 の利用

p で一致しないとき:old1 を old2 へ,old2 を p へ,p を次へ.

一致したら:(1) old1->pointer を q として保存,(2) old1 のポインタを p に,(3) old2 のポインタを p の次に,(4) p のポインタを q に.

61

A B C D

探索データが Cの場合

head pold2

?

old1, , old2 p

q見つかったのがAまたはDのときは?