データ構造とアルゴリズム①qma/education/data/algo1...5...
TRANSCRIPT
1
データ構造とアルゴリズム①
馬 青
2
授業の実施方法及び評価
• 毎講義に出席チェックと演習を行い、出席状況・授業態度・演習への取り組みを総合して平常点とする。
• 最終評価は期末試験に平常点を加味して行う。100点満点のうち、期末試験が8割、平常点が2割である。
3
データ構造とは• 建築などで使う「構造」の意味は、梁と柱がどのような形で結合されているのか、その結合部はどのような方法で実現されているのかなど、を指す。
• 同様に、データ構造とは、データの構造のことであり、具体的にはデータどうしがどのような関係にあり(論理構造)、メモリ上にどのように保存されるか(物理構造:メモリ上の表現または配置とも言う)についてのことである。
• 論理構造:– (セル・)配列・連結リスト・スタック・キュー・木・グラフ
• 物理構造:– (配列を用いて)データを番地順に記憶する順配置
– (ポインタを用いて)データのつながりを実現するリンク配置
データ表現
4
なぜデータ構造?(1/5)
• 一般的に計算機で問題を解くのは、以下の段階を経て行われる。
実世界の問題 モデル
アルゴリズム+データ表現
プログラム+入力データ
解(出力データ)
モデリング(抽象化)
解法を求める
プログラミングプログラム実行
データ構造の話
データの論理構造
データの物理構造
5
なぜデータ構造?(2/5)• 理由1:モデルとデータ表現に適切なデータ構造を用いる
必要がある。たとえば、「ケーニッヒスベルグの橋の問題」(=「一筆書きできるかの問題」) :
川の両岸と中洲の間に七つの橋があるとき、陸地のある地点から出発し、すべての橋を全部かつちょうど一回ずつ渡って出発点に戻ることができるかどうか。
a
b d
c
×
1 2 3
4
5 6 7
Wikipediaより• 18世紀の初めごろにプロイセン王国の首都であるケーニ
ヒスベルク(現ロシア連邦カリーニングラード)という大きな町があった。この町の中央には、プレーゲル川という大きな川が流れており、七つの橋が架けられていた。あるとき町の人が、次のように言った。– 「このプレーゲル川に架かっている7つの橋を2度通らずに、全て
渡って、元の所に帰ってくることができるか。ただし、どこから出発してもよい」
• 町の人が言ったことはできるだろうか。
• 1736年、レオンハルト・オイラーは、この問題を以下のグラフに置き換えて考えた。
– このグラフが一筆書き可能であれば、ケーニヒスベルクの橋を全て1度ずつ通って戻ってくるルートが存在することになる。
– そして、オイラーは、このグラフが一筆書きできないことを証明し、ケーニヒスベルクの問題を否定的に解決した。
7
データ構造
c
a
b d1 2
5 6
34
7
2.計算機上のデータ表現・・・物理構造順配置(隣接行列、接続行列)、リンク配置(隣接リスト)
1.モデル(グラフ)・・・論理構造
節点
枝
8
モデル(グラフ)
• このような問題をグラフでモデル化すると、「グラフのある節点から出発してすべての枝をちょうど1回ずつ通ってもとの節点に戻ることができるか(一筆書き)」という問題になる。
• もとの節点に戻ることのできるグラフを「オイラー・グラフ」という。
• グラフがオイラー・グラフであるかどうかを判定する「オイラー定理」は後で述べる。
「解法」に当たる
論理構造
9
順配置:接続行列(incidence matrix)
1 1 1 0 0 0 0
1 1 0 1 1 1 0
0 0 0 0 1 1 1
0 0 1 1 0 0 1
a
b
c
d
節
点
枝1 2 3 4 5 6 7
プログラムでは、数字1,2,3,4で置き換えて使う。
物理構造
10
順配置:隣接行列(adjacency matrix)
0 2 0 1
2 0 2 1
0 2 0 1
1 1 1 0
節点
a b c da
b
c
d
節
点
物理構造
11
リンク配置:隣接リスト(adjacency list)
a b d
a d
a
b a
d c b d
物理構造
12
なぜデータ構造?(3/5)
• 理由2:メモリを有効利用するためには、どのようなデータ表現にするか、つまり、上記の例において、接続行列・隣接行列・隣接リストのどれを使うかを選択する必要がある。今の問題では、枝が多く可能な経路が多いため隣接行列のほうが効率がよい。一方、節点数に比べ、枝が少ないようなグラフは、接続行列・隣接リストを使うと、メモリが少なくて済む。
13
なぜデータ構造?(4/5)
• 理由3:データ処理を効率よくするためには、よいアルゴリズムを選ぶだけでなく、問題に適するデータ構造を選択する必要がある。たとえば表探索の例。線形探索より2分探索の方が速い。しかしこれはアルゴリズムだけで済む話ではない。つまり、配列に格納するデータを昇順または降順にあらかじめ並べておく必要がある。これもデータ構造の話になる。
14
なぜデータ構造?(5/5)
• 理由4:計算機で実世界の問題を取り扱うとき、データの保守管理(追加・削除・変更など)の効率化も重要。たとえば大規模名簿データの管理は、「配列」よりは本授業で紹介する「連結リスト」を用いた方が効率がよい。
15
オイラーグラフの定理
• グラフがオイラー・グラフであるための必要十分条件は、そのグラフのすべての節点の次数が偶数であることである。
*節点の次数:節点に接続している枝の数
解法ちなみに
16
オイラーグラフの判定アルゴリズム
入力: 節点数n, 枝数m, 接続行列I(nxmの2次元配列)出力: オイラー・グラフかどうか補助: i:節点番号, j:枝番号, D:各節点の次数を格納する1
次元配列1.{節点次数の計算}
1.1 {初期化}節点i=0,1,...,n-1についてD[i]=0とおく1.2 節点i=0,1,...,n-1について、
枝j=0,1,...,m-1について、枝jが節点iに接続しているなら(すなわち、I[i][j]>0)、D[i]=D[i]+1とおく
2.{節点次数は偶数か}i=0,1,...,n-1についてD[i]が偶数でなければ、「オイラー・グラフではない」と出力し、処理を終了する
3. 「オイラー・グラフである」と出力し、処理を終了する
ちなみに
17
授業内容
• 各種の基本的なデータ構造を紹介するとともに、「アルゴリズム及び実習」でふれていなかった文字列探索の各種アルゴリズムについても紹介する。
18
データ構造の最小単位:セル
• セルは一個のデータを保持し、各種のデータ構造の基本的な構成要素である。セルはプログラム言語で言うと、単純変数のようなものである。従って、各セルにはセル名またはセル番号が付けられ、これを用いてセルの保持するデータの変更を行う。例えば、c=5; c = c+1;など。以降、セルを要素、セル番号を添え字とも呼ぶ。
19
「成績リスト・住所リスト・・・」
• プログラミングで標題のようないろいろな「リスト」を扱うことは避けて通れない。
• このような「リスト」にはセルだけでは対応しきれない⇒以下の2種類のデータ構造が使われる。
– 配列
– 連結リスト
20
配列って何?(1/2)
• 配列はセルを順次並べた構造を持ち、そのセルには通常連続したセル番号が付けられる。
• セル名は配列名とセル番号で決まる。例えばaという配列のi番目のセルはa[i]になる。
論理構造の話
21
配列って何?(2/2)1次元配列aの場合
0 1 2 3 4 5 セル番号
a[0] a[1] a[2] a[3] a[4] a[5]配列名
セル番号
セル名
0 1 2 3 4 5
0
1
2
3
2次元配列aの場合
a[1][4]セル名
セル
セルを線上に並べているもの
セルを平面上に並べているもの 配列名 セル番号
論理構造の話
22
配列の物理構造
• 記憶装置の連続した番地に要素を並べて置くだけの簡単な方法を取る。配列では添え字を指定して要素の参照を行うが、これは記憶装置の番地に簡単な算術式で変換される。
配列の計算機上の実現
23
配列への基本操作(1/7)1)データの参照または探索
(a)セル番号(要素の添え字)iが分かっている場合、a[i]を直接参照すればよい。
(b)セル番号(要素の添え字)が分からなくて、探索したいデータが分かっている場合、配列の各セルを順に呼び出して、探索データと照合し、一致したときのセル番号が探索の解になる(線形探索)。
線形探索のCによる実現for(i=0; i<n; i++)
if(x==a[i]){no=i; break;} /* no: 探索データの番号 */
配列に対し、プログラム的にどのようなことができるか?
24
配列への基本操作(2/7)
2)データの更新
(a)セルの番号が分かっている場合、例えば、a[i]=a[i]+1のように直接更新すればよい。
(b)セルの番号が分からない場合、例えばデータ43を57に更新する場合は、まず43を格納しているセルの番号を探索し、探索の解(セル番号)が3だとすると、a[3]=57にすればよい。
Cによる実現は前の線形探索のプログラムに対し、xに43を与え、for文の直後に、a[no]=57;を追加すればよい。
25
配列への基本操作(3/7)
Cによる実現
x=43;for(i=0; i<n; i++) /* データ探索 */
if(x==a[i]){no=i; break;}a[no]=57;
26
配列への基本操作(4/7)
3)データの削除
例えば、配列(62, 45, 50, 30, 28, 87, 90)のデータ30を削除しようとすれば、そのデータを探索して、そのデータを削除する必要があるだけでなく、その後のセルのデータを一個ずつ前へずらす必要がある。
27
配列への基本操作(5/7)
62 45 50 30 28 87
削除とは
62 45 50 28 87
Cによる実現 注意:削除という操作はない!for(i=no; i<n-1; i++) /* no: 削除データの番号(no=3)、
n: 配列のセルの総数(n=7) */a[i]=a[i+1]; /* 削除データ以降のデータを前に
一つずつずらす*/
① ②
28
配列への基本操作(6/7)4)データの追加
追加操作の前に、追加データの以降のデータ
をまず右へ一個ずつずらしておく。ここで注意しなければならないのは事前に大きめの配列を用意しておく必要がある。
62 45 50 28 87
30
62 45 50 30 28 87
①②
③
29
配列への基本操作(7/7)Cによる実現
for(i=n; i>no; i--) /* no: 挿入位置(no=3)、n: 配列のセルの総数(n=7)*/
a[i]=a[i-1]; /* 挿入位置以降のデータを後ろに一つずつずらす*/
a[no]=30;
注意:データのシフトは末尾から前への順で行う!
30
連結リスト(1/2)
配列の特徴は、構造が簡単でデータ参照は容易であるが、データ削除や追加には操作が複雑である。そして、削除の場合はメモリの無駄が生じるし、追加を考えれば、逆に予め大きめのメモリを確保しなければならない。データが大量の場合処理時間もかかってしまう。
これらの欠点の原因は、セルとセルが連番で結び付けられているところにある。それで考案されたのが連結リストというデータ構造である。
31
連結リスト(2/2)
• 連番でないセルとセルを結びつける方法の1つはポインタを用いることである。
• ポインタとは次のセルの場所(アドレス)を格納するためのものである。
• 連結リストの構成要素のセルはデータ部+ポインタ部で構成される。
• したがって、連結リストとは、連番ではなく、ポインタで結びつけられたセルの集まりである。
32
配列vs.連結リスト(例)
0 新大阪
1 京都
2 名古屋
3 新横浜
4 東京
配列
セル
番号 セル
0 東京 -1
1 京都 3
2 新大阪 1
3 名古屋 4
4 新横浜 0
2
連結リスト(配列による実現)セル セル番号 データ部 ポインタ部
ヘッドセル
33
C言語による実現配列:char a[][16]={“新大阪”, “京都”, “名古屋”, “新横浜”, “東京”};
連結リスト(配列による実現):typedef struct data{
char s[16];int p;
}DATA;int main(void){
int head=2;DATA a[5];strcpy(a[0]. s, “東京”); a[0].p=-1;strcpy(a[1]. s, “京都”); a[1].p=3;strcpy(a[2]. s, “新大阪”); a[2].p=1;strcpy(a[3]. s, “名古屋”); a[3].p=4;strcpy(a[4]. s, “新横浜”); a[4].p=0;return 0;
}
34
連結リストの配列による実現
• メリットとしては、– C言語で言うほんとの意味でのポインタは使わない
– もちろん、malloc()関数や自己参照構造体も不要
– 要するに、高度なプログラミングが不要
• デメリットとしては、「配列」というデータ構造にデータを格納したときと同様、削除などがあった場合、メモリの無駄が生じるし、追加を考えれば、逆に予め大きめのメモリを確保しなければならない。ただし、追加や削除の操作は「配列」より容易である。
35
例:「名古屋」を削除
0 東京 -1
1 京都 3
2 新大阪 1
3 名古屋 4
4 新横浜 0
2
セル セル番号 データ部 ポインタ部
ヘッドセル
「京都のポインタ部に名古屋のポインタ部の値を入れるだけでよい」
36
例:「米原」を「京都」の次に追加
0 東京 -1
1 京都 3
2 新大阪 1
3 名古屋 4
4 新横浜 0
5 米原 3
2
セル セル番号 データ部 ポインタ部
ヘッドセル
5
①配列の最後に「米原 3」を追加する
②京都のポインタ部に値5入れる
37
連結リストの配列による実現は一般的な方法ではない
• メリットとしては、– C言語で言うほんとの意味でのポインタは使わない
– もちろん、malloc()関数や自己参照構造体も不要
– 要するに、高度なプログラミングが不要
• デメリットとしては、「配列」というデータ構造にデータを格納したときと同様、削除などがあった場合、メモリの無駄が生じるし、追加を考えれば、逆に予め大きめのメモリを確保しなければならない。ただし、追加や削除の操作は「配列」より容易である。
38
一般的に、「連結リスト」は、ポインタ変数を用いて実現する
a b c d
セルのアドレス
62 b 45 c 50 d 28
ポインタ部
a
使用せず
NULL
ヘッド(ルート):
先頭のセルのアドレスを格納するため。ポインタ部しかないセル
データ部
NULL値:「未定、未
知、不明」を表すもの
39
実際の計算機メモリ上では(データ部はint型の場合)
0x94a60080x94a6009
0x94a600f
0x94a60180x94a6019
0x94a601f
45
0x94a60180x94a6028
62
0x94a6038 0x94a6048
0x94a60280x94a6029
0x94a602f
50
0x94a60380x94a6039
0x94a603f
28
NULL値(ヌルポインタ空ポインタ)
0x94a60480x94a6049
0x94a604f
0x0000000
40
注意
• ヘッドセルを設けそのポインタ部に先頭セルのアドレスを入れる代わりに、ポインタ変数*headを使うことも可能(キューの連結リストによる実現を参照)
• しかし、(キューは先頭削除と末尾追加しかなく大丈夫だが)任意のセルの削除や追加を考えるとき、先頭セルかそうでないかの判断が必要でプログラミングが少し複雑になる。
参考
41
では、連結リストの場合のデータの格納は?
• 標題の言い方は厳密ではない。しかし、配列と対応させるために敢えて使った。
• 連結リストは配列のようにDATA a[5]のように宣
言(一括でメモリ確保)して使えるようなものではなく、プログラム中にセルを1つ1つ「宣言」(メモリ確保)して(作成して)いくしかない。
• したがって、標題のことを厳密には「連結リストの作成」という。
ポインタを用いた
42
配列へのデータの格納(読み込み)
例えば以下のように簡単にできる。
int main(void){
int a[5], i;for(i=0; i<4; i++){
printf(“データを入力:”);scanf("%d", &a[i]);
}return 0;
}
ちなみに
43
配列で実現する連結リストへのデータの格納
例:typedef struct data{
char s[16];int p;
}DATA;int main(void){
int i, head=2;DATA a[5];for(i=0; i<5; i++)
scanf(“%s %d”, a[i].s, &a[i].p);return 0;
}
0 東京 -1
1 京都 3
2 新大阪 1
3 名古屋 4
4 新横浜 0
2
セル セル番号 データ部 ポインタ部
ヘッドセル
ちなみに
44
演習問題
1.以下のグラフはオイラー・グラフかどうかを判断しなさい。また、接続行列と隣接行列を求めなさい。
c
a
b d1 5
2
4
3
6
45
演習問題
2.四つの要素(セル)から構成される配列a[0]=10, a[1]=15, a[2]=20, a[3]=40 に対し、 プログラム------------------------------------------------------------------------------------------no=1; /* no: ターゲットデータの番号 、つまり、データ15がターゲットである*/ n=4; /* 配列の要素の総数 */ for(i=no; i<n-1; i++) a[i]=a[i+1];
-----------------------------------------------------------------------------------------------
は配列aに対しどのような操作を行うものか。また、プログラムを実行した後のa[0]~a[3]の値を具体的に示しなさい。
3.
10 P0ヘッド
20 P1 30 P2 40 P3 50 P4 60 P5
上の図は5個のセルから構成される(単方向)連結リストである。セルの左上にはそのセルのアドレスを、右上にはポインタ部の
名前を表している。P0~P5の中身(値)を具体的に示しなさい。
46
4.以下の連結リストが与えられたとしてリストの各セルのデータ(都市名)をプログラムで順に出力するとしてその出力結果を示しなさい。また、(1)下記の連結リストから「新横浜」を削除後の連結リストを示しなさい。(2)下記の連結リストの「名古屋」→「東京」に「湯河原」を挿入後の連結リストを示しなさい。
演習問題
0 東京 4
1 名古屋 0
2 新大阪 5
3 京都 -1
4 新横浜 3
5 米原 1
2
ヘッドセル
セル番号 セル ポインタ
47
5.データを削除する関数void DataDel(int n, int a[], int x)を作成しなさい。ただし、nはデータ個数、xは削除データである。また、データ探索関数int DataSearch(int n, int a[], int x)はすでに存在しているとし、関数の戻り値は探索データの位置(つまり配列aの要素番号)とする(データが見つからないときは-1である)。
演習問題