アルゴリズムとデータ構造 4月27日分

アルゴリズムとデータ構造
1章の復習
第2章 リスト構造
5月10日
情報知能学科
白井英俊
計算量:時間と空間
•
時間計算量、もしくは時間複雑度(time
complexity)
そのアルゴリズムを実行するのに、最悪の場合、どのくらい時
間がかかるかを、入力の大きさの関数として表す
•
空間計算量、もしくは空間複雑度(space
complecity)
そのアルゴリズムを実行するのに、最悪の場合、どのくらい
記憶領域が必要かを、入力の大きさの関数として表す
計算量(複雑度)=時間計算量+空間計算量
時間計算量の常識的な評価
• 教科書p.8
O(1) --- 理想的に速い
O(log n) --- 非常に速い
O(n) --- 速い
O(n log(n)) --- 速いほう
O(n2) --- かなり遅いが、許されないほどではない
O(n3) --- かなり遅い。許される限界。
O(cn ) --- (cは1より大きい定数)。指数オーダ
(exponential order)。とっても遅い。
計算量の演習1
• 教科書p.11
1.1 次の5種類の計算時間を、nを横軸にとって、一つ
のグラフに重ねて表せ。そして、そのグラフから
オーダの大小関係が読み取れることを観察せよ。
f1(n) = 10000 n
f2(n) = 10000 n log(n)
実際にグラフを書く前に、
これらのオーダがどうなるか
f3(n) = 100 n**2
考えておいてください
f4(n) = 10 n**3
f5(n) = 2**n
計算量の演習1の解答
• GnuPlotを用いる。ただし、xをnと「読み替える」 (1≦x≦30 のグラフ。x=30
では f5 > f2 > f1 > f4 > f3のように見えるが…)
1
1
1
e
e
e
+
+
+
0
0
0
1
0
0
0
f
1
(
x
)
f
2
(
x
)
f
3
(
x
)
f
4
(
x
)
f
5
(
x
)
9
8
1
e
+
0
0
7
1
e
+
0
0
6
1
0
0
0
0
0
1
0
0
0
0
1
0
0
0
1
0
0
1
0
1
5
1
0
1
5
2
0
2
5
3
0
計算量の演習1の解答(続)
f5を除いて3000まで表示させると、 f4 > f3 > f2 > f1 となる
ことが分かる
1
1
e
e
+
+
0
0
1
1
2
f
1
(
x
)
f
2
(
x
)
f
3
(
x
)
f
4
(
x
)
0
1
e
+
0
0
8
1
e
+
0
0
6
1
0
0
0
0
1
0
0
1
0
5
0
0
1
0
0
0
1
5
0
0
2
0
0
0
2
5
0
0
3
0
0
0
計算量の演習1 の解答(まとめ)
つまり、nが十分大きければ、
n < n*log(n) < n2 < n3 < 2n
であることが分かる
f1(n) = 10000 n
オーダ:O(n)
f2(n) = 10000 n log(n)
O(n log(n))
f3(n) = 100 n**2
O(n2 )
f4(n) = 10 n**3
O(n3 )
f5(n) = 2**n
O(2n )
計算量の演習2
• 教科書p.11
1.2 ステップ数が f(n)=100000n, g(n)=10n**3,
h(n)=2**nの3つのアルゴリズムにn=100の大きさの
データを与えたとする。1ステップが10**(-9)秒で
実行できるコンピュータで走らせたときの計算時間
は次のどれに近いか?
1ミリ秒、1秒、1分、1時間、1日、1ヶ月、1年、1世紀
計算量の演習2の解答
問題:ステップ数がf(n)=100000n, g(n)=10n**3, h(n)=2**nの3
つのアルゴリズムにn=100の大きさのデータを与えたとする。
1ステップが10**(-9)秒で実行できるコンピュータで走らせたとき
の計算時間は?
210 =1024 ≒103
答え: f(100)=100000*100 = 107
だから2100=(210)10= (103)10
g(100)=10*100**3 =107
1ステップが10-9秒なので、f もgも107*10-9 =1*10-2秒 (10ミリ秒)
h(100)=2**(100) ≒ 1030
1ステップが10-9秒なので、h(100)*10-9 ≒ 1030*10-9 =1*1021秒
1年=60*60*24*365≒ 3.2*107 秒, 1世紀=100年=3.2*109秒
計算量の演習3
• 教科書p.11
1.4 次の式のオーダを、最も忠実で簡単な式を用いて
表せ。
(1) 6.5n3 + 8n2 + 5
(2) 5 n log(n) + 3 n2
(3) 2.5 n √n + 3.6 n log(n)
(4) 5 n + 8 log(n)
(5) 2n + 5 n 5
(6) 800 n log(n) + n
計算量の演習3の解答
• 問題: 次の式のオーダを、最も忠実で簡単な式を
用いて表せ。
(1) 6.5n3 + 8n2 + 5
(2) 5 n log(n) + 3 n2
(3) 2.5 n √n + 3.6 n log(n)
(4) 5 n + 8 log(n)
(5) 2n + 5 n 5
(6) 800 n log(n) + n
注意:2nのような「2」は定数倍
ではない。nの関数に『掛け算』
されている『数』が無視の対象
解法: (1)定数倍を無視
(2)最も大きくなるnの項だけを残す
計算量とオーダ
• ここまでで何か質問は?
• アルゴリズムが与えられた時に、『計算量』を
求めることは、5章に入ったら…
4月の講義のまとめ
• 「アルゴリズム」
• 「アルゴリズム」の計算量
時間複雑度、空間複雑度
オーダ : log(n), n, n*log(n),n**2, n**3, 2**n, n!
• 「アルゴリズム」をプログラムとして実現することにより、アル
ゴリズムの検証を行う ー プログラムを書くだけではなく、い
ろいろな例で実験する
• そのために、Rubyの復習が必要(代入、条件分岐、繰り返し
、変数/定数、配列、クラス)
アルゴリズムをプログラムとして実現する
• 再帰関数:プログラムを書く、プログラムを読む(動きを追う)
アルゴリズムの例
入力
• 問題(教科書p.1): n個の実数x1, x2, …, xnが
与えられて、その中の最大値を求める
出力
• 素朴版(p.1)
x1, x2, …, xnを順にみていく。その途中でいつ
も、「それまでに見た数の最大値」を覚えてお
く。最後まで見終わったとき、「それまでに見
た数の最大値」が求めるべき最大値
14
アルゴリズムの例(続)
最低限、このように書けるようになろう!
• より明確な記述(p.2)
入力: 正の整数nとn個の実数値x1, x2, …, xn
出力: x1, x2, …, xnの中の最大値
手続き:
「変数y に x1の値を代入する」
1. y ← x1 (yは「それまでの最大値」を記録)
2. i = 2,3,…,nに対して順に次を行う:
xi > y ならば y ← xi
3. yを出力する
15
アルゴリズムの例(続)
MAXはアルゴリズムの名前。カッコのな
かは入力「引数」を書く。
• 「プログラム」風(p.3)
Algorithm MAX(n, x1, x2, …, xn):
入力(Input): 正整数 n と n 個の実数x1, x2, …, xn
出力(Output): x1, x2, …, xnの中の最大値
手続き(Procedure):
ここでの記法についてはウェブページの
「教科書のアルゴリズムの記述とRUBY言
1. y ← x1 ;
語の対応」参照
2. for i ← 2 until n do
if xi > y then y ← xi ;
3. return y; このような記法のアルゴリズムから
プログラムを実現できるように!
16
新しい概念:ポインタ(pointer)
• ポインタの元々の意味は「指さし」。
• 「変数」の役割は、 「オブジェクトを指す」
(オブジェクトを「値」として持っているわけではない)
例えば、変数xに配列[1,2,3] を代入したとする。
その結果、「変数」の値として配列[1,2,3]が入るのではなく、
配列そのものは別の場所に作られ、その場所への『手がか
り』が「変数の値」になる。
このような手がかり(記憶番地、アドレス)をポインタという。
Rubyの変数の『値』は、みなポインタ
•
参考: プログラミング言語Cでは、int *x のように書いて、変数xは、整数オブジェクトへのポイ
ンタ(アドレス)を値とする、というように宣言する
ポインタの詳細
• アドレス(記憶番地):コンピュータの記憶場所
ここでは少し拡張して「データの格納場所の手が
かり」(場所の情報)と考える
例: 配列を変数に代入
変数名
x
記憶場所b
記憶場所a
アドレス
配列:かなりの場
所を必要とする複
雑なデータ
ポインタによる説明
コンピュータの内部:
x = [1, 2, 3]
y=x
x
xの記憶場所
x[1] = 5
5
y
yの記憶場所
p y => [1,5,3]
x.shift
p y => [5, 3]
配列:
[ 1, 2, 3 ]
注意:本当は「配列」は、かなり
複雑な形をしているのだけど、
簡略化しています
クラスとインスタンス
Rubyのオブジェクトには
数、文字列、配列、…などがあった
これは、プログラミング言語としてはかなり豊か
これらを使いこなせば、いろいろなことができる。でも…
問題に適したデータ構造を考えないといけないことが
ある。
そのために、クラス定義とインスタンス生成の方法は
知っておかないといけない
(本講義では これから多用します)
Rubyにおけるクラス定義
• オブジェクト指向
プログラムが扱うデータ(オブジェクト)と、それをど
のように扱うかという手続き(関数)とを「組」として表
す
• クラスーオブジェクトの「種類」
• クラス定義
1) どのようなパーツ(部品)があるかを宣言:インス
タンス変数
2) 手続き(関数)の宣言:インスタンスメソッド
クラス定義の例
• 名簿の一つの項目を「クラス」として定義する
• 名簿の項目(Item)には、(少なくとも)次のパーツ(部
品)をもつと考えられる
1)名前 2)住所 3)生年月日 4)性別 5)年齢
name address birth_date sex
age
これを図示すると…
クラス名 Item
name
address
部品名
birth_date
sex
age
具体的な値がはい
る(ポインタ)
クラス定義の例(続)
• 右に図示した名簿の項目
(Item)に対応した「クラス」:
Item
name
大文字から始める
class Item
address
def initialize(n,a,b,s)
birth_date
@name = n
sex
@address = a
age
@birth_date = b
@sex = s
Initialize は重要なインスタンスメソッド。
@age = Time.now.year - b 「インスタンス」作成の時に説明
end
end
@つきの変数が「インスタンス変数」
これが「パーツ(部品)名」になる
クラス定義の例(続)
ただ、このままだと、部品にアクセス(取り出し、参照)しにくいので
…
Item
class Item
def initialize(n,a,b,s)
name
@name = n
address
@address = a
birth_date
@birth_date = b
sex
@sex = s
age
@age = Time.now.year - b
end
attr_accessor :name, :address, :birth_date, :sex, :age
end
(1) attr_accessor につづけて
(2) インスタンス変数名の前にコロン( : )
インスタンス生成
• 鯛焼きに例えて言えば、
クラス定義は、鯛焼きを焼く金型(鉄板)
これだけでは、鯛焼きは食べられない…
鯛焼きを食べるには、「材料を用意して、鯛焼き
を作る」必要がある。
それをするのが、「インスタンス生成」
「Taro Yamada」の名簿項目を作る:
注:ここでは、生年月日ではな
く「生まれた年(西暦)」としよう
Item.new(“Taro Yamada”, “Toyota”, 1990, “male”)
インスタンス生成(続)
Item.new (“Taro Yamada”, “Toyota”,1990, “male”)
これにより、initializeメ
ソッドが呼び出される
その引数の n, a, b, sがそれ
ぞれ Item.new の実引数と
結び付けられ、
次に
の文が実行
されて、次に示すようなItem
クラスのインスタンスができ
あがる:
class Item
def initialize(n, a, b, s)
@name = n
@address = a
@birth_date = b
@sex = s
@age = Time.now.year - b
end
attr_accessor :name, :address,
:birth_date, :sex, :age
end
インスタンス生成(完成)
Item.new(“Taro Yamada”, “Toyota”,1990, “male”)
Itemクラスのインスタンス
name
address
“Taro Yamada”
“Toyota”
birth_date
1990
sex
“male”
age
20
注:Time.now.year が
2010を返すとすれば
演習1:クラスとインスタンス
注:補助資料を参照してください:
演習1.1.Itemクラス定義を用いて、以下の例を参考に、自分に
当てはめて名簿項目(Itemインスタンス)を生成せよ(注意:女
性の場合、性別は”female”)
mydata = Item.new(“Taro Yamada”, “Toyota”, 1990, “male”)
演習1.2.上を実行後 mydata.age の値を表示させよ。
演習1.3. さらに mydata.age = 30 を実行させた後の
mydata.birth_date と mydata.age の値を表示させよ。
クラス定義(追加):インスタンスメソッド
そのインスタンスだけに適用される関数(メソッド)の定義:
class Item
def initialize(n,a,b,s)
@name = n ; @address = a
@birth_date = b ; @sex = s
@age = Time.now.year - b
end
attr_accessor :name, :address, :birth_date, :sex, :age
def show(sep = “, ”)
return “Name: “+@name+sep+” Sex: “+@sex+sep+”Age: “+@age
end
end
注:(sep=“,”) は、メソッド(関数)の引数が省略できること、また省
略された場合に”,”が値となることを表すRubyの記法
演習2:クラスとインスタンス
注:補助資料を参照してください:
演習2(発展):
(1) 複素数を表すクラスを定義せよ。そのクラスの名
前を Complex とし、パーツは二つ、 real (reと略す、
実部) と imaginary (im と略す、虚部)。
(2) 補助資料の例にならって、複素数同士の引き算
と割り算をするメソッドを付け加えよ。
(3) インスタンスを2つ以上つくり、計算がちゃんと行
われることを確かめよ。
複素数について
• 2つの複素数を (a+bi), (c+di)とすると(a,b,c,dは
実数, iは虚数)
足し算: (a+bi) + (c+di) = (a+c) + (b+d)i
引き算: (a+bi) - (c+di) = (a-c) + (b-d)i
掛け算: (a+bi) * (c+di) = (a*c - b*d)+(b*c + a*d)i
割り算: (a+bi) / (c+di) = (a*c + b*d)+(b*c - a*d)i
c2 + d2
c2 + d2
参考:
(c –di)は(c+di)の共役複素数
(a+bi)
(c+di)
(a+bi)
(c - di)
(c+di) * (c - di)
(a*c + b*d) (b*c -a*d)i
c2 + d2 + c2 + d2
(予習)リストの必要性とポインタ
• 名簿のような「表」を記憶することを考える
普通の方法: 1行分記憶するのに必要な記憶の大
きさをkバイトとして、コンピュータ上に固定した記憶
場所を確保して使う
0
1行k バイト
k
2k
…
リストの必要性とポインタ(続)
• 表方式の特徴:検索が容易
n件目のデータは、n*k~(n+1)*k-1の範囲
に入っている
• 表方式の欠点:データの削除や挿入の手間
が大きい
例:1番目のデータを削除したら、その
後のデータをすべて一行ずつ前に移動し
なければならない
リストの必要性とポインタ(続)
• リスト(線形リスト):表方式の欠点を解消
– データの挿入、削除の手間が軽減
– ただし、記憶場所が多く必要、データの検
索(アクセス)の手間がかかる、という欠点あ
り
• リスト(線形リスト)の基本単位:セル(Cell)
data
next
データ部 ポインタ
データ部: データの記憶用
ポインタ部:別なセルのアドレス
セル(Cell)
• 線形リスト構造を表現する基本単位セル
例えて言えば、連結器つきの車両
つながったセルの図