430 likes | 662 Views
アルゴリズムとデータ構造 第 2 章 リスト構造 5 月 13 日分. 情報知能学科 白井英俊. 復習:クラスとインスタンス. Ruby のオブジェクトには 数、文字列、 配列 、 … などがあった これは、プログラミング言語としてはかなり豊か これらを使いこなせば、いろいろなことができる。でも …. 問題に適したデータ構造を考えないといけないことがある。 そのために、 クラス定義 と インスタンス生成 の方法は知っておかないといけない (本講義では これから多用します). 復習: Ruby におけるクラス定義. オブジェクト指向
E N D
アルゴリズムとデータ構造第2章 リスト構造5月13日分アルゴリズムとデータ構造第2章 リスト構造5月13日分 情報知能学科 白井英俊
復習:クラスとインスタンス Rubyのオブジェクトには 数、文字列、配列、…などがあった これは、プログラミング言語としてはかなり豊か これらを使いこなせば、いろいろなことができる。でも… 問題に適したデータ構造を考えないといけないことがある。 そのために、クラス定義とインスタンス生成の方法は知っておかないといけない (本講義では これから多用します)
復習:Rubyにおけるクラス定義 オブジェクト指向 プログラムが扱うデータ(オブジェクト)と、それをどのように扱うかという手続き(関数)とを「組」として表す クラスーオブジェクトの「種類」 クラス定義 1) どのようなパーツ(部品)があるかを宣言:インスタンス変数 2) 手続き(関数)の宣言:インスタンスメソッド
復習:クラス定義の例(続) Item 大文字から始める Initialize は重要なインスタンスメソッド。「インスタンス」作成の時に呼び出される @つきの変数が「インスタンス変数」 これが「パーツ(部品)名」になる • 右に図示した名簿の項目(Item)に対応した「クラス」: class Item def initialize(n,a,b,s) @name = n @address = a @birth_date = b @sex = s @age = Time.now.year - b end end
復習:クラス定義の例(続) Item • attr_accessor につづけて • インスタンス変数名の前にコロン( : ) ただ、このままだと、部品にアクセス(取り出し、参照)しにくいので… 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
復習:インスタンス生成 これだけでは、鯛焼きは食べられない… 鯛焼きを食べるには、「材料を用意して、鯛焼きを作る」必要がある。 それをするのが、「インスタンス生成」 「Taro Yamada」の名簿項目を作る: Item.new(“Taro Yamada”, “Toyota”, 1990, “male”) 注:ここでは、生年月日ではなく「生まれた年(西暦)」としよう • 鯛焼きに例えて言えば、 クラス定義は、鯛焼きを焼く金型(鉄板)
復習:演習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 の値を表示させよ。 演習の狙い:クラス定義に従って、パーツ(部品)をもったインスタンスが生成されることを確認。また、インスタンス変数にアクセスしたり、その値を書き換えることができることも確認。 mydata.age という「アクセス方法」が作れるのは、attr_accessor宣言のおかげ。
復習:演習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 参考: (a+bi) (a+bi) (c - di) (a*c + b*d) (b*c -a*d)i (c+di) (c+di) (c - di) c2+ d2c2+ d2 (c –di)は(c+di)の共役複素数 * +
復習:演習2:複素数 class Complex def initialize(x,y) end # initialize attr_accessor :re, :im end # class • 複素数を表すクラスを定義せよ。 そのクラスの名前を Complexとし、パーツは二つ、real (reと略す、実部) と imaginary (imと略す、虚部) (2) 補助資料の例にならって、複素数同士の引き算と割り算をするメソッドを付け加えよ。 @re = x @im = y インスタンス変数の前に@をつける! class Complex … def tasu(z) # 複素数同士の足し算 return Complex.new(@re + z.re, @im + z.im) end def hiku(z) # 複素数同士の引き算 end end return Complex.new(@re - z.re, @im - z.im)
復習:演習2:複素数 (3) インスタンスを2つ以上つくり、計算がちゃんと行われることを確かめよ a = Complex.new(1,-1) b = Complex.new(3,2) # print a.show, " + ", b.show, " = “; print (a.tasu(b)).show,"\n“ => 4.0 + 1.0 i print a.show, " - ", b.show, " = “; print (a.hiku(b)).show,"\n" => -2.0 - 3.0 i print a.show, " * ", b.show, " = “; print (a.kakeru(b)).show,"\n“ => 5.0 – 1.0 i print a.show, " / ", b.show, " = “; print (a.waru(b)).show,"\n“ => 0.077 + 0.385 i
クラス定義とインスタンス生成(まとめ) クラス名は大文字 class Complex # 複素数のクラス def initialize(x,y) # インスタンス生成メソッド @re = x @im = y end # initialize attr_accessor :re, :im # インスタンス変数へのアクセス # def tasu(z) # 複素数同士の足し算 return Complex.new(@re + z.re, @im + z.im) end def hiku(z) # 複素数同士の引き算 return Complex.new(@re - z.re, @im - z.im) end # end # class インスタンスを作る時のメソッド 生成の例: Complex.new(1,2) インスタンス変数は@つき attr_accessor につづけて インスタンス変数の前にコロン( : ) インスタンス固有のメソッドをクラス定義の中に書く。インスタンス変数を参照・書き換え可能
ちょっとbreak • さて、これでクラス定義とインスタンス変数の復習ができた。 • これから本格的にデータ構造を考えていく。 • 最初は「リスト構造」。これはCell(セル) という、「ポインタ」をパーツにもつ最小のデータ構造をつなぎあわせて作る。 • ここで、クラスが活躍する • ここまで質問は?
リスト構造の必要性とポインタ 名簿のような「表」を記憶することを考える 普通の方法: 1行分記憶するのに必要な記憶の大きさをkバイトとして、コンピュータ上に固定した記憶場所を確保して使う 0 1行k バイト k 2k …
表方式の特徴:検索が容易 n件目のデータは、n*k~(n+1)*k-1の範囲に入っている 表方式の欠点:データの削除や挿入の手間が大きい 例:1番目のデータを削除したら、その後のデータをすべて一行ずつ前に移動しなければならない リスト構造の必要性とポインタ(続)
リスト構造の必要性とポインタ(続) リスト(線形リスト)構造:表方式の欠点を解消 データの挿入、削除の手間が軽減 ただし、記憶場所が多く必要、データの検索(アクセス)の手間がかかる、という欠点あり • リスト(線形リスト)構造の基本単位:セル(Cell) data データ部 next ポインタ データ部: データの記憶用 ポインタ部:別なセルのアドレス
セル(Cell) 線形リスト構造を表現する基本単位 例えて言えば、連結器つきの車両 つながったセル(Cell)の図
リスト構造(教科書p.16) • リスト構造 ポインタを持つデータ構造 以下のような Cell (セル)クラスを考え、線形リスト構造を作る: クラス (Rubyのクラス) 概念図 Cell data データ部 next ポインタ class Cell definitialize(x,y) @data = x @next = y end end
線形リストの例 data next data next 青木 石川 ここには、隣のセルへのポインタ(アドレス)が入っている この印は、このセルに続くセルがないことを意味 b = Cell.new(石川, nil ) a = Cell.new(青木, b) nilは、このセルに続くセルがないことを意味 これは次のように書いてよい: a = Cell.new(青木, Cell.new(石川, nil ))
Cell について x = Cell.new(“a”, Cell.new(“b”, Cell.new(“c”,nil))) により作成された連結リストは: x data next data next data next “a” “b” “c”
クラスCellにメソッドを追加する • このままだと単にCellの構造(どんなパーツがあるか)を定義しただけ • Cellをつないだり、後続のCellにアクセスしたり、書き替えたりできるよう、メソッドを追加する クラス (Rubyのクラス) 概念図 Cell data データ部 next ポインタ class Cell definitialize(x,y) @data = x @next = y end attr_accessor :data, :next end
クラスCellにメソッドを追加する(続) Cellに3つのインスタンスメソッドを付け加える: • get(n) : 先頭のセルから数えてn番目のセルを返す(get) • insert(n, content) : 先頭のセルから数えて n番目のセルの前に、contentをデータ部にもつセルを挿入(insert)する • delete(n) : 先頭のセルから数えてn番目のセルを削除する(delete)
クラスCellにgetメソッドを追加 • get(n) : 先頭のセルから数えてn番目のセルを返す(get) 例: x.get(1) => data next 要するに x.nextと同じ “b” data next data next data next x “a” “b” “c” x.get(0) は x を返す。それでは x.get(2) は?
クラスCellにgetメソッドを追加 class Cell definitialize(x,y) @data = x @next = y end # def attr_accessor :data, :next def get(n) return self if (n <= 0) if (@next != nil) return @next.get(n-1) else return nil end # if end #def end # class 既に定義済み self は「自分自身」を指すポインタ
クラスCellのgetメソッドの検証 c = Cell.new(“c”, nil); b = Cell.new("b", c) ; x = Cell.new("a", b) # getの検証 p x.get(0) p x.get(1) p x.get(2) p x.get(3) class Cell def get(n) return self if (n <= 0) if (@next != nil) return @next.get(n-1) else return nil end # if end #def end n = 0 x (= self) next n = 1 b (= b.get(0)) = b.get(1) = c.get(0) =c data next data next data next “a” “b” “c” x c b
クラスCellにinsertメソッドを追加 • insert(n, content) : 先頭のセルから数えて n番目のセルの前に、contentをデータ部にもつセルを挿入(insert)する data next data next data next “a” “b” “c” x この時、x.insert(2,”d”)を実行すると data next “d” となるようにできればよい
クラスCellにinsertメソッドを追加(続) class Cell definitialize(x,y) @data = x @next = y end # def attr_accessor :data, :next def get(n) …. end def insert(n, cont) if (n == 1) @next = Cell.new(cont, @next) else # n > 1 と仮定 @next.insert(n - 1, cont) end # if end #def end # class 既に定義済み ここにはちょっと問題がある 値のチェックをすることが必要。
クラスCellのinsertメソッドの検証 c = Cell.new(“c”, nil); b = Cell.new("b", c) ; x = Cell.new("a", b) x.insert(2, “d”) def insert(n, cont) if (n == 1) @next = Cell.new(cont, @next) else # n > 1 と仮定 @next.insert(n - 1, cont) end # if end #def n =2 “d” next next n =1 b.insert(1,”d”) “d” next data next data next data next data next “a” “b” “c” x b
insertメソッドの動き(続) 先の結果を書き直すと x.insert(2,”d”)の結果は: x “a” “d” “c” “b” x から見て2番目のセルの後ろに、新しいセルが挿入され、元のリストの残りの要素がそれに繋がっている
insertメソッドの問題 • このままだと先頭のセルの「前に」 contentをデータ部にもつセルを挿入(insert)することができない --- 何とかならないのだろうか? def insert(n, cont) if (n == 0) # n = 0 の場合、セルの先頭に挿入 y = Cell.new(@data,@next) @data , @next = cont, y elsif (n == 1) @next = Cell.new(cont, @next) else # n > 1 と仮定 @next.insert(n - 1, cont) end # if end #def if (n == 1)
リストの先頭に insert data next data next data next a “a” “a” “h” “b” “c” このとき、insert(1,”h”) をすると、どうなる? data next 該当するプログラム: y = Cell.new(@data,@next) @data, @next = cont, y y cont
ちょっとbreak… • Cell、get メソッドとinsertメソッドについて、質問はありますか?
クラスCellにdeleteメソッドを追加 • delete(n) : 先頭のセルから数えてn番目のセルを削除する(delete) data data data data next next next next 変数aの値が以下のような連結リストへのポインタとする: x “a” “b” “d” “c” x.delete(2) を実行すると となるようにできればよい
deleteメソッドの動き 先の結果を書き直すと x.delete(2)の結果は: x “a” “b” “c” “d” 元のリストの3番目が2番目のセルになる。 元の2番目の要素は消えたわけではない。 が、リストの先頭からはアクセス(接近)不能=削除!
クラスCellにdeleteメソッドを追加 class Cell definitialize(x,y) @data = x @next = y end # def attr_accessor :data, :next def get(n) …. end def insert(n,cont) … end def delete(n) if (n == 1) @next = @next.next else # n > 1 と仮定 @next.delete(n - 1) end # if end #def end # class 既に定義済み ここにはちょっと問題がある 値のチェックをすることが必要。
クラスCellのdeleteメソッドの検証 c = Cell.new(“c”, nil); d = Cell.new(“d”, c) b = Cell.new("b", c) ; x = Cell.new("a", b) data data next next x.delete(2) def delete(n) if (n == 1) @next = @next.next else # n > 1 と仮定 @next.delete(n - 1) end # if end #def n =2 n =1 b.delete(1) data next data next x “a” “b” “d” “c” b d c
deleteメソッドの問題 • このままだと先頭のセル削除(delete)することができない --- 何とかならないのだろうか? def delete(n) if (n == 0) # n = 0 の場合、セルの先頭に挿入 @data = @next.data @next = @next.next elsif (n == 1) @next = @next.next else # n > 1 と仮定 @next.delete(n - 1) end # if end #def if (n == 1)
先頭の要素をdelete data data next next @data @next x “a” “b” “b” “c” 該当するプログラム: @data = @next.data @next = @next.next x.delete(0) を実行すると リストの2番目の要素を1番目のセルにコピー 元の2番目の要素はリストの先頭からはaccess不能 つまり、『1番目のセルではなく2番目のセルを削除』
プログラミング演習 クラスCellに次のようなインスタンスメソッドを付け加える: • find(data) : 先頭のセルから順にたどり、dataをデータ部に持つセルを返す • remove(data) : 先頭のセルから順にたどっていき、最初にdataをデータ部に持つセルを削除する(remove)
リストの実現のための別な方法 • 今見てきた方法だと、1番目の要素の削除や、1番目に要素を挿入するのは、かなり技巧的 • 以下の方法は、その点、分かりやすい方法 ⇒ 先頭にダミー(データ記憶には使わない)セルを置く 今までのリスト: x “a” “b” 新方法のリスト: x “a” “b”
リストの実現のための別な方法(続) • ダミーセルの next の値を書き換えることで、 次を実現するプログラムが容易に書ける (1) 1番目のセルとして新たな要素を挿入 dummy.next = Cell.new(新たな要素,dummy.next) (2) 1番目のセルを削除 dummy.next = dummy.next.next
リストの実現のための別な方法(続) • この方式による新たなリスト構造のためのデータ構造(クラス)をNCellとする class NCell def initialize(data) @contents = Cell.new(nil, data) end attr_accessor :contents end 使用例: c = Cell.new(“c”, nil);b = Cell.new("b", c) a = Cell.new("a", b) ; x = NCell.new(a)
発展問題 このように定義したNCellクラスに、3つのインスタンスメソッドを付け加える: • get(n) : 先頭のセルから数えてn番目のセルを返す。ただし、n =0の時は先頭のセルを返す。 • insert(n, content) : 先頭のセルから数えて n番目のセルの前に、contentをデータ部にもつセルを挿入する。n=0の時は先頭のセルの「前に」挿入する。 • delete(n) : 先頭のセルから数えてn番目のセルを削除する。n=0の時は、先頭のセルを削除する。