単方向リストの問題について
科目B勉強中さん
(No.1)
https://www.fe-siken.com/kakomon/sample/b10.html
長文かつ散文で分かりにくい文章になってしまいましたが
自分でどれだけ考えても分からないため皆様のお力添えをいただきたいです。
多分リスト構造でデータがどのように格納されているか等が理解できていないのだと思います。
疑問点
ウのlistHead.next.next
カのprev.next.next
がそれぞれ何を指しているのか分かりません。
※同じ要素を指定しているように見えてしまう
例えば以下のようなデータがあったとします。
※東京から広島までの新幹線の停車駅のデータです。東京スタートです。
|アドレス|要素の値|次の参照アドレス
|100 |京都 |130
|110 |広島 |未定義
|120 |東京 |150
|130 |新大阪 |110
|150 |名古屋 |100
※アドレス=listHead、要素の値=prev.val、次の参照アドレス=prev.next
と紐づく理解です。
listHeadにはリストの先頭要素の参照があらかじめ格納されている
とありますが上記の例の場合
listHeadの中身は「120」のみが格納されているのか
すべての要素が格納されているのかが分かりません。
ただ、delNode(1)が呼ばれた時に
listHead ← listHead.next
という処理を実行しているので
すべての要素が格納されていると理解しました。
実際に上記の例でトレースしていくと
初期の段階で
listHead = {120,150,100,130,110}という並び
↑データの並び通りの {100,110,120,130,150}の並びにならないのは何故なのでしょうか?
リスト構造だからそうなる?
で先頭要素が格納されており
delNode(1)の場合は
pos = 1なのでif文の処理を実行するため
listHead(120)にlistHead.next(150)を代入している。
そうすると初期の参照が150(名古屋)になるので
120(東京)はデータとしては存在しているけど参照されなくなる=削除された
となり処理が終了する。
delNode(2)の場合は
pos = 2のためif文内の処理には入らずコメント部分で記載されている通り
繰り返し処理のfor文にも入らず問題文の
prev.next ← □□□□
を考えることになる。
for文に入る前の真ん中の処理で
prev ← listHead
という処理を実行しているので
ここでlistHead = prev(同一の中身)
になる認識。
2個目の要素を削除するため
|アドレス |要素の値 |次の参照アドレス ⇒ |アドレス |要素の値 |次の参照アドレス
|(listHead)|(prev.val)|(prev.next) ⇒ |(listHead)|(prev.val)|(prev.next)
|100 |京都 |130 ⇒ |100 |京都 |130
|110 |広島 |未定義 ⇒ |110 |広島 |未定義
|120 |東京 |150 ⇒ |120 |東京 |【100】
|130 |新大阪 |110 ⇒ |130 |新大阪 |110
|150 |名古屋 |100 ⇒ |150 |名古屋 |100※参照されない
というデータにしたい。
最初のprev.nextには東京の150が格納されているので
これがどうやったら100に置き換えられるかを考えると
東京の次の次の要素(prev.next.next=名古屋の100)を
prev.nextに代入すればprev.nextが東京の100になるので
150(名古屋)が参照されなくなる=削除された
となり処理が終了する。
ここでlistHead.next.nextは何かと考えてみると
listHead=120※先頭要素
listHead.next=150
listHead.next.next=100
となり
prev.next ← listHead.next.next
でも東京のprev.next=100となるので
150(名古屋)が参照されなくなる=削除された
となるような気がするのですが・・
どこの理解が誤っているのかが分かりません。
「次の要素」に「次の次の要素」を代入することで
参照できないようにして削除行為を行っていることは分かるのですが
一部トレースが出来ず困っています。
長文かつ散文で分かりにくい文章になってしまいましたが
自分でどれだけ考えても分からないため皆様のお力添えをいただきたいです。
多分リスト構造でデータがどのように格納されているか等が理解できていないのだと思います。
疑問点
ウのlistHead.next.next
カのprev.next.next
がそれぞれ何を指しているのか分かりません。
※同じ要素を指定しているように見えてしまう
例えば以下のようなデータがあったとします。
※東京から広島までの新幹線の停車駅のデータです。東京スタートです。
|アドレス|要素の値|次の参照アドレス
|100 |京都 |130
|110 |広島 |未定義
|120 |東京 |150
|130 |新大阪 |110
|150 |名古屋 |100
※アドレス=listHead、要素の値=prev.val、次の参照アドレス=prev.next
と紐づく理解です。
listHeadにはリストの先頭要素の参照があらかじめ格納されている
とありますが上記の例の場合
listHeadの中身は「120」のみが格納されているのか
すべての要素が格納されているのかが分かりません。
ただ、delNode(1)が呼ばれた時に
listHead ← listHead.next
という処理を実行しているので
すべての要素が格納されていると理解しました。
実際に上記の例でトレースしていくと
初期の段階で
listHead = {120,150,100,130,110}という並び
↑データの並び通りの {100,110,120,130,150}の並びにならないのは何故なのでしょうか?
リスト構造だからそうなる?
で先頭要素が格納されており
delNode(1)の場合は
pos = 1なのでif文の処理を実行するため
listHead(120)にlistHead.next(150)を代入している。
そうすると初期の参照が150(名古屋)になるので
120(東京)はデータとしては存在しているけど参照されなくなる=削除された
となり処理が終了する。
delNode(2)の場合は
pos = 2のためif文内の処理には入らずコメント部分で記載されている通り
繰り返し処理のfor文にも入らず問題文の
prev.next ← □□□□
を考えることになる。
for文に入る前の真ん中の処理で
prev ← listHead
という処理を実行しているので
ここでlistHead = prev(同一の中身)
になる認識。
2個目の要素を削除するため
|アドレス |要素の値 |次の参照アドレス ⇒ |アドレス |要素の値 |次の参照アドレス
|(listHead)|(prev.val)|(prev.next) ⇒ |(listHead)|(prev.val)|(prev.next)
|100 |京都 |130 ⇒ |100 |京都 |130
|110 |広島 |未定義 ⇒ |110 |広島 |未定義
|120 |東京 |150 ⇒ |120 |東京 |【100】
|130 |新大阪 |110 ⇒ |130 |新大阪 |110
|150 |名古屋 |100 ⇒ |150 |名古屋 |100※参照されない
というデータにしたい。
最初のprev.nextには東京の150が格納されているので
これがどうやったら100に置き換えられるかを考えると
東京の次の次の要素(prev.next.next=名古屋の100)を
prev.nextに代入すればprev.nextが東京の100になるので
150(名古屋)が参照されなくなる=削除された
となり処理が終了する。
ここでlistHead.next.nextは何かと考えてみると
listHead=120※先頭要素
listHead.next=150
listHead.next.next=100
となり
prev.next ← listHead.next.next
でも東京のprev.next=100となるので
150(名古屋)が参照されなくなる=削除された
となるような気がするのですが・・
どこの理解が誤っているのかが分かりません。
「次の要素」に「次の次の要素」を代入することで
参照できないようにして削除行為を行っていることは分かるのですが
一部トレースが出来ず困っています。
2024.10.09 20:52
科目B勉強中さん
(No.2)
ちなみにfor文の中ではposで指定された要素の直前まで
ループ処理で移動してdelNode(2)でやったような
処理をやっていることは理解できます。
ループ処理で移動してdelNode(2)でやったような
処理をやっていることは理解できます。
2024.10.09 20:53
まーぼさん
★FE シルバーマイスター
(No.3)
> 疑問点
>ウのlistHead.next.next
>カのprev.next.next
>がそれぞれ何を指しているのか分かりません。
この例を借りて説明すると、
> 例えば以下のようなデータがあったとします。
>※東京から広島までの新幹線の停車駅のデータです。東京スタートです。
listHead→東京→名古屋→京都→新大阪→広島
となる感じです。
> ただ、delNode(1)が呼ばれた時に
>listHead ← listHead.next
>という処理を実行しているので
>すべての要素が格納されていると理解しました。
すべての要素は格納されていません。
> listHeadにはリストの先頭要素の参照
とある通り、listHeadにはリストの先頭要素の参照が格納されているだけです。(すべての要素が格納されているわけではないということ。)
スレ主さんのイメージでは、
listHeadがリストの要素をすべて管理しているというイメージでしょうか?実際は、listHeadが管理しているのは先頭の要素の参照だけです。
listHead→1番目の要素→2番目の要素→3番目の要素→…
のようにバケツリレーのように繋がっています。listHeadと2番目の要素は直接繋がってるわけではなく、1番目の要素を介して繋がっています。上記のようなリスト構造から1番目の要素を削除するには、listHeadが2番目の要素を参照するように書き換えるだけです。
長くなるのでとりあえずここまでにしますがいかがでしょうか?
2024.10.09 21:11
まーぼさん
★FE シルバーマイスター
(No.4)
delNode(2)の場合は
両者で同じになります。しかしposは、リストの要素数以下の整数が入るため、pos=1,2だけの場合では不十分です。pos=3以降でトレースすればlistHead.next.nextがおかしいことが分かると思います。
> ウのlistHead.next.next
>カのprev.next.next
>がそれぞれ何を指しているのか分かりません。
>※同じ要素を指定しているように見えてしまう
両者で同じになります。しかしposは、リストの要素数以下の整数が入るため、pos=1,2だけの場合では不十分です。pos=3以降でトレースすればlistHead.next.nextがおかしいことが分かると思います。
2024.10.09 21:30
科目B勉強中さん
(No.5)
まーぼ 様
ご説明いただきありがとうございます。
⇒つまりlistHeadには最初の参照に使用する「120」というデータしか格納されていない
ということですね。まずここの理解が誤っていることは認識できました。
⇒まさにそのイメージでした。
listHead.nextが指定できるということは配列のように次に指定できる要素が
あらかじめ格納されていると理解していました。
⇒以下でpos=3でトレースしてみたいと思います。
delNode(3)の場合
|アドレス |要素の値 |次の参照アドレス ⇒ |アドレス |要素の値 |次の参照アドレス
|(listHead)|(prev.val)|(prev.next) ⇒ |(listHead)|(prev.val)|(prev.next)
|100 |京都 |130 ⇒ | 100 | 京都 |130※参照されない
|110 |広島 |未定義 ⇒ | 110 | 広島 |未定義
|120 |東京 |150 ⇒ | 120 | 東京 |150
|130 |新大阪 |110 ⇒ | 130 | 新大阪 |110
|150 |名古屋 |100 ⇒ | 150 | 名古屋 |【130】
delNode(1)とdelNode(2)の場合は「120(東京)150」を基準に考えていたが
delNode(3)の場合は基準が「150(名古屋)100」になるため
prev.nextの値が変動する。
トレースすると以下の通り
prev.next=100
prev.next.next=130
ここでlistHeadは最初の参照要素が格納されているままなので
listHead=120※先頭要素
listHead.next=150
listHead.next.next=100
listHead.next.next.next=130
であるのでdelNode(3)の場合は
prev.next ← listHead.next.next.next
を指定してあげないといけないため
ウの選択肢は不適
という理解で問題ないでしょうか?
そうなるとまた新しい疑問が湧いてきました。
delNode(3)の場合は、見る基準(prev)が「150(名古屋)100」に
なっているため一度はfor文の中に入り処理を実行していると思います。
for文内での処理で、見る基準(prev)を
posの直前まで移動させているということは分かるのですが
delNode(3)の場合って
for文の条件が「iを2からpos-1(つまり3-1で2)まで1ずつ増やす」
となり「2から2まで1ずつ増やす」と条件を満たさなくなるのではないでしょうか?
そうなるとループ処理には入らずprevが更新されず処理に不整合が発生してしまうと思うのですが・・
引き続きの質問になり申し訳ないのですが
お付き合いいただけると助かります。
ご説明いただきありがとうございます。
> listHeadにはリストの先頭要素の参照が格納されているだけです。
> (すべての要素が格納されているわけではないということ。)
⇒つまりlistHeadには最初の参照に使用する「120」というデータしか格納されていない
ということですね。まずここの理解が誤っていることは認識できました。
> listHeadがリストの要素をすべて管理しているというイメージでしょうか?
⇒まさにそのイメージでした。
listHead.nextが指定できるということは配列のように次に指定できる要素が
あらかじめ格納されていると理解していました。
> pos=3以降でトレースすればlistHead.next.nextがおかしいことが分かると思います。
⇒以下でpos=3でトレースしてみたいと思います。
delNode(3)の場合
|アドレス |要素の値 |次の参照アドレス ⇒ |アドレス |要素の値 |次の参照アドレス
|(listHead)|(prev.val)|(prev.next) ⇒ |(listHead)|(prev.val)|(prev.next)
|100 |京都 |130 ⇒ | 100 | 京都 |130※参照されない
|110 |広島 |未定義 ⇒ | 110 | 広島 |未定義
|120 |東京 |150 ⇒ | 120 | 東京 |150
|130 |新大阪 |110 ⇒ | 130 | 新大阪 |110
|150 |名古屋 |100 ⇒ | 150 | 名古屋 |【130】
delNode(1)とdelNode(2)の場合は「120(東京)150」を基準に考えていたが
delNode(3)の場合は基準が「150(名古屋)100」になるため
prev.nextの値が変動する。
トレースすると以下の通り
prev.next=100
prev.next.next=130
ここでlistHeadは最初の参照要素が格納されているままなので
listHead=120※先頭要素
listHead.next=150
listHead.next.next=100
listHead.next.next.next=130
であるのでdelNode(3)の場合は
prev.next ← listHead.next.next.next
を指定してあげないといけないため
ウの選択肢は不適
という理解で問題ないでしょうか?
そうなるとまた新しい疑問が湧いてきました。
delNode(3)の場合は、見る基準(prev)が「150(名古屋)100」に
なっているため一度はfor文の中に入り処理を実行していると思います。
for文内での処理で、見る基準(prev)を
posの直前まで移動させているということは分かるのですが
delNode(3)の場合って
for文の条件が「iを2からpos-1(つまり3-1で2)まで1ずつ増やす」
となり「2から2まで1ずつ増やす」と条件を満たさなくなるのではないでしょうか?
そうなるとループ処理には入らずprevが更新されず処理に不整合が発生してしまうと思うのですが・・
引き続きの質問になり申し訳ないのですが
お付き合いいただけると助かります。
2024.10.09 23:55
jjon-comさん
★FE ゴールドマイスター
(No.6)
変数名prevは,英単語previous「順序が一つ前の,以前の」に由来しています。
prev=120「一方向リストの1番目の要素"東京"を指す」状態になったとすると,
1回目のforループの実行で
prev=150「一方向リストの2番目の要素"名古屋"を指す」状態になり,
2回目のforループの実行で
prev=100「一方向リストの3番目の要素"京都"を指す」状態になり,
3回目のforループの実行で
prev=130「一方向リストの4番目の要素"新大阪"を指す」状態になります。
(以降,省略)
いいえ,2から2,ですからforループは1回実行されます。
iが1つ増えて3になったらループ終了です。
いいえ,先頭要素を指すlistHeadを絶対に使わなければならない
のであればその表記になりますが,
変化する変数prevを使うのなら,
次の一連の流れによって同じことを実現できます。
prev←listHead (prev=120 で"東京"を指し)
prev←prev.next(prev=150 で"名古屋"を指し)
prev.next←prev.next.next
("名古屋"の次(="京都")の次を指す参照アドレス130を,"名古屋"の参照アドレスに代入)
else
prev←listHead
for (略)
prev←prev.next
endfor
の prev←listHead によって,まず最初にprev←listHead
for (略)
prev←prev.next
endfor
prev=120「一方向リストの1番目の要素"東京"を指す」状態になったとすると,
1回目のforループの実行で
prev=150「一方向リストの2番目の要素"名古屋"を指す」状態になり,
2回目のforループの実行で
prev=100「一方向リストの3番目の要素"京都"を指す」状態になり,
3回目のforループの実行で
prev=130「一方向リストの4番目の要素"新大阪"を指す」状態になります。
(以降,省略)
> 「2から2まで1ずつ増やす」
> そうなるとループ処理には入らず
いいえ,2から2,ですからforループは1回実行されます。
iが1つ増えて3になったらループ終了です。
> delNode(3)の場合は
> prev.next ← listHead.next.next.next
> を指定してあげないといけない
いいえ,先頭要素を指すlistHeadを絶対に使わなければならない
のであればその表記になりますが,
変化する変数prevを使うのなら,
次の一連の流れによって同じことを実現できます。
prev←listHead (prev=120 で"東京"を指し)
prev←prev.next(prev=150 で"名古屋"を指し)
prev.next←prev.next.next
("名古屋"の次(="京都")の次を指す参照アドレス130を,"名古屋"の参照アドレスに代入)
2024.10.10 00:52
スレ主ですさん
(No.7)
昨日質問した端末とは別の端末から書き込みしています。
jjon-com 様
ご回答ありがとうございます。
確認遅くなりすみません。
要は正しくは
delNode(3)の場合の
for文内の条件(「2から2まで1ずつ増やす」)で言うと
i ≦ pos -1
であれば2 ≦ 2
となり条件が真になるのでループ内の処理を実行
実行後にiが3になるので2回目のループ時には
であれば3 ≦ 2
となり条件が偽になるのでループ内の処理を実行しない
ということになるということですね。
「2から2まで1ずつ増やす」だと
2 < 2となり
条件が偽となり処理を実行しない
と思ってしまいますが
疑似言語の記載方法だと思って慣れるしかないですね。
※2から2までだとどうしてもループ処理に入るイメージが持ちにくいです。
最初から不等号で書いてくれればイメージしやすいのですが・・
あと、自分で整理していて再度疑問が湧いてきたので質問させてください
(これが最後の質問になると思います。)
まーぼ様の回答より
初期のlistHeadには最初の要素を示す「120」というデータのみが格納されているとのことでした。
「120」という参照アドレス情報しか持っていないのであれば
listHead.nextの指定が出来ないように思えるのですが
何故指定がうまく行くのでしょうか?
※リスト構造だからという話になるのでしょうか
そこのイメージが分からなくなったので
教えていただけると助かります。
お手数ですがよろしくお願いします。
jjon-com 様
ご回答ありがとうございます。
確認遅くなりすみません。
要は正しくは
delNode(3)の場合の
for文内の条件(「2から2まで1ずつ増やす」)で言うと
i ≦ pos -1
であれば2 ≦ 2
となり条件が真になるのでループ内の処理を実行
実行後にiが3になるので2回目のループ時には
であれば3 ≦ 2
となり条件が偽になるのでループ内の処理を実行しない
ということになるということですね。
「2から2まで1ずつ増やす」だと
2 < 2となり
条件が偽となり処理を実行しない
と思ってしまいますが
疑似言語の記載方法だと思って慣れるしかないですね。
※2から2までだとどうしてもループ処理に入るイメージが持ちにくいです。
最初から不等号で書いてくれればイメージしやすいのですが・・
あと、自分で整理していて再度疑問が湧いてきたので質問させてください
(これが最後の質問になると思います。)
まーぼ様の回答より
初期のlistHeadには最初の要素を示す「120」というデータのみが格納されているとのことでした。
「120」という参照アドレス情報しか持っていないのであれば
listHead.nextの指定が出来ないように思えるのですが
何故指定がうまく行くのでしょうか?
※リスト構造だからという話になるのでしょうか
そこのイメージが分からなくなったので
教えていただけると助かります。
お手数ですがよろしくお願いします。
2024.10.10 17:39
スレ主ですさん
(No.8)
jjon-com 様
⇒ここに関してはlistHeadを使って指定するとどうなるかが知りたかったので
「listHeadを絶対に使わなければならないのであればその表記になります」
という回答で納得できました。
ありがとうございました。
>> delNode(3)の場合は
>> prev.next ← listHead.next.next.next
>> を指定してあげないといけない
>いいえ,先頭要素を指すlistHeadを絶対に使わなければならない
>のであればその表記になりますが,
⇒ここに関してはlistHeadを使って指定するとどうなるかが知りたかったので
「listHeadを絶対に使わなければならないのであればその表記になります」
という回答で納得できました。
ありがとうございました。
2024.10.10 17:43
まーぼさん
★FE シルバーマイスター
(No.9)
>スレ主さん
>初期のlistHeadには最初の要素を示す「120」というデータのみが格納されているとのことでした。
>「120」という参照アドレス情報しか持っていないのであれば
>listHead.nextの指定が出来ないように思えるのですが
>何故指定がうまく行くのでしょうか?
私からこの件について回答させていただきます。
クラス ListElement型
| val:東京 |
| next:150 |
——————————
(120番地)
120番地に上記のようなデータがあり、listHeadには120というアドレスだけが入っています。listHeadには、直接valが東京のListElement型のデータが入っているのではなく、アドレスが入っています。こうすることで、軽量になるなどのメリットがあります。つまり直接ListElement型のデータはlistHeadには入っていませんが、ListElement型のアドレス(参照)が入っているので、まるで直接ListElement型をListHeadに入れているかのように扱うことができます。
そのため、実際は軽量にするため、アドレスしか入ってないのですが、イメージでは、listHeadに上記のデータが入っているようなイメージで構いません。そうすれば、listHeadが上記のデータなので、listHead.nextが150となることが分かるのではないでしょうか。
2024.10.10 19:32
科目B勉強中さん
(No.10)
まーぼ 様
再度のご説明ありがとうございます。
どうやら大前提が理解できておらずそれで問題に取り組んでいるのが
悪いような気がしてきました・・
型とかそいうのを意識していませんでした・・
一度整理すると
なんとなーくのイメージしか掴めていませんが
まずlistHeadとprevはListElementクラスをもとに作成されている。
ListElementクラスはメンバ変数としてvalと言う文字列型の変数と
nextというListElement型の変数を持っている。
※クラスは部品を作るための設計書のような理解をしています。
listHeadには初期値として120というアドレス情報だけが格納されていて
prevにはすべての要素が格納されている。
listHeadもprevも同じクラスで作成されているので
nextで次の要素を指定できる。
nextはListElement型のため
listHeadには実際120しか格納されていないが
listHead.nextと指定することで
prev.nextを指定したのと同じように
扱える。
表現が不適切な箇所があるかもしれませんが
ざっくりこんな理解でよいのでしょうか?
基本が理解できておらず
何度も説明いただき申し訳ありません。
再度のご説明ありがとうございます。
どうやら大前提が理解できておらずそれで問題に取り組んでいるのが
悪いような気がしてきました・・
型とかそいうのを意識していませんでした・・
一度整理すると
なんとなーくのイメージしか掴めていませんが
まずlistHeadとprevはListElementクラスをもとに作成されている。
ListElementクラスはメンバ変数としてvalと言う文字列型の変数と
nextというListElement型の変数を持っている。
※クラスは部品を作るための設計書のような理解をしています。
listHeadには初期値として120というアドレス情報だけが格納されていて
prevにはすべての要素が格納されている。
listHeadもprevも同じクラスで作成されているので
nextで次の要素を指定できる。
nextはListElement型のため
listHeadには実際120しか格納されていないが
listHead.nextと指定することで
prev.nextを指定したのと同じように
扱える。
表現が不適切な箇所があるかもしれませんが
ざっくりこんな理解でよいのでしょうか?
基本が理解できておらず
何度も説明いただき申し訳ありません。
2024.10.10 21:21
まーぼさん
★FE シルバーマイスター
(No.11)
>スレ主さん
だいたいその解釈で問題ないです。
ただ以下の記述が気になりました。
> prevにはすべての要素が格納されている
prevもlistHeadと同じクラスのインスタンスを参照しているので、「listHeadはこうだけどprevはこう」にはならないはずです。listHeadがvalとnextというメンバ変数を持っているなら同じようにprevもvalとnextというメンバ変数を持ち、nextには次の要素であるListElement型のアドレス(参照)のみが入ると思います。
2024.10.10 21:36
jjon-comさん
★FE ゴールドマイスター
(No.12)
クラスListElementの理解は正しいです。
私は,クラスは「タイ焼きの型」,クラスを元にいくつでも生み出すことができるインスタンス(実体オブジェクト)は「タイ焼き」,という比喩を用いることが多いです。
次の5つはすべて,クラスListElementを元にして生み出されたインスタンスです。
val next
+ーーー+ーー+
|京都 | 130|
+ーーー+ーー+
val next
+ーーー+ーー+
|広島 |NULL|
+ーーー+ーー+
val next
+ーーー+ーー+
|東京 | 150|
+ーーー+ーー+
val next
+ーーー+ーー+
|新大阪| 110|
+ーーー+ーー+
val next
+ーーー+ーー+
|名古屋| 100|
+ーーー+ーー+
これとは別に,問題文には「ListElement型の変数」が登場します。
「ListElement型の変数」にはアドレス値を格納します。
上図から分かるとおり,listHead自身,prev自身,next自身は,
メンバ変数valやnextを内蔵していません。
加えて,たった今,
と述べましたが,アドレス値ならばどんな値でも格納できるわけではありません。
「クラスListElementのインスタンスの参照」つまり比喩で言うならば,
タイ焼きを指し示すアドレス値しか格納できないという制限がかけられています。
(ただし例外として,未定義値 NULL は格納できます)
ですから,
listHeadが指し示す先には必ずvalとnextが存在するし
(それを listHead.val ,listHead.next と表記します)
prevが指し示す先には必ずvalとnextが存在するし
(それを prev.val ,prev.next と表記します)
nextが指し示す先には必ずvalとnextが存在します。
(それを .next.val ,.next.next と表現します)
私も,この表現は間違っている,と考えます。
prevは単一の変数です。
クラスListElementのインスタンスを指すアドレス値であれば,
100でも110でも120でも130でも150でも代入することができますが,
すべての値がまとめて格納されているわけではありません。
prevに格納されたアドレス値を次々と書き換えることによって,
一方向リストのすべての要素をたどっていくことができる,というだけです。
> ※クラスは部品を作るための設計書のような理解をしています。
私は,クラスは「タイ焼きの型」,クラスを元にいくつでも生み出すことができるインスタンス(実体オブジェクト)は「タイ焼き」,という比喩を用いることが多いです。
次の5つはすべて,クラスListElementを元にして生み出されたインスタンスです。
val next
+ーーー+ーー+
|京都 | 130|
+ーーー+ーー+
val next
+ーーー+ーー+
|広島 |NULL|
+ーーー+ーー+
val next
+ーーー+ーー+
|東京 | 150|
+ーーー+ーー+
val next
+ーーー+ーー+
|新大阪| 110|
+ーーー+ーー+
val next
+ーーー+ーー+
|名古屋| 100|
+ーーー+ーー+
これとは別に,問題文には「ListElement型の変数」が登場します。
ListElement型の変数はクラスListElementのインスタンスの参照を格納するものとする。
ちなみにこの問題に登場する「ListElement型の変数」は次の3つです。大域: ListElement: listHead
ListElement: prev
|メンバ変数 next|型 ListElement|説明 次の要素の参照|
listHead
+ーー+
| 120|
+ーー+
prev
+ーー+
| 150|
+ーー+
next
+ーーー+ーー+
| | 100|
+ーーー+ーー+
ListElement: prev
|メンバ変数 next|型 ListElement|説明 次の要素の参照|
listHead
+ーー+
| 120|
+ーー+
prev
+ーー+
| 150|
+ーー+
next
+ーーー+ーー+
| | 100|
+ーーー+ーー+
「ListElement型の変数」にはアドレス値を格納します。
上図から分かるとおり,listHead自身,prev自身,next自身は,
メンバ変数valやnextを内蔵していません。
加えて,たった今,
> 「ListElement型の変数」にはアドレス値を格納します。
と述べましたが,アドレス値ならばどんな値でも格納できるわけではありません。
「クラスListElementのインスタンスの参照」つまり比喩で言うならば,
タイ焼きを指し示すアドレス値しか格納できないという制限がかけられています。
(ただし例外として,未定義値 NULL は格納できます)
ですから,
listHeadが指し示す先には必ずvalとnextが存在するし
(それを listHead.val ,listHead.next と表記します)
prevが指し示す先には必ずvalとnextが存在するし
(それを prev.val ,prev.next と表記します)
nextが指し示す先には必ずvalとnextが存在します。
(それを .next.val ,.next.next と表現します)
> prevにはすべての要素が格納されている
私も,この表現は間違っている,と考えます。
prevは単一の変数です。
クラスListElementのインスタンスを指すアドレス値であれば,
100でも110でも120でも130でも150でも代入することができますが,
すべての値がまとめて格納されているわけではありません。
prevに格納されたアドレス値を次々と書き換えることによって,
一方向リストのすべての要素をたどっていくことができる,というだけです。
2024.10.10 22:23
まーぼさん
★FE シルバーマイスター
(No.13)
>スレ主さん
理解の一助になればと思い、追記させていただきます。
ListElement型の変数には、参照しか入っていないのですが、まるでそこにクラスListElementのインスタンスがあるかのように扱えます。
これを現実で例えるとショートカットのようなものだと思います。アプリケーションAをよく使うのでデスクトップに追加したいけど、アプリケーションAそのものをデスクトップに置くと、容量を余計に食ってしまうため避けたい。そこで、アプリケーションAの参照となるショートカットを作成し、ショートカットをデスクトップに置くことでそのものを直接置くよりも、軽量になります。ショートカットをクリックしてもアプリケーションAが開けるので、デスクトップにはアプリケーションA自体はないが、まるでデスクトップにアプリケーションAがあるかのように扱えます。
2024.10.10 22:59
科目B勉強中さん
(No.14)
まーぼ 様
jjon-com 様
お二方とも再度のご説明ありがとうございます。
ご指摘の通り
上記に対する理解が足りていませんでした。
listHeadには120が格納されていて
prevには最初から
{120,150,100,130,110}
という値がすべて格納されている。
という風に考えてしまっていました。
⇒まさにおっしゃる通りです。自分で同じクラスから作成したと言っておきながら
理解しきれていませんでした。
この問題をまとめると
まずクラスListElementを元にして
以下のデータを作成する。
val next
+ーーー+ーー+
|京都 | 130|
+ーーー+ーー+
val next
+ーーー+ーー+
|広島 |NULL|
+ーーー+ーー+
val next
+ーーー+ーー+
|東京 | 150|
+ーーー+ーー+
val next
+ーーー+ーー+
|新大阪| 110|
+ーーー+ーー+
val next
+ーーー+ーー+
|名古屋| 100|
+ーーー+ーー+
クラスListElementとは別に
「ListElement型の変数」も登場し
対象となる変数は【listHead】【prev】【next】の3つ
ここで注意しないといけないのが
「ListElement型の変数」にはアドレス値のみが格納されること
※整合性が取れているアドレス値以外は格納不可(NULL(未定義)は例外)
格納されているprevまたはlistHeadのアドレス値を参照しながら
リストをたどっていく。
という前提で「次の要素(prev.next)」をどう書き換えれば
削除行為が可能か?
という問題になるわけですね。
そのうえで
delNode(3)をトレースすると
2 ≦ 2の条件を1度は満たすため
ループ処理を実行しアドレス値の書き換えを行う。
この時、1度ループ処理を実行しているため
prevで指定される要素が1つずれる。
ループ内でlistHeadの指定される要素は動いていないため
ウのlistHead.next.nextだと不適で
カのprev.next.nextが正答となる。
おかげさまで一人では全く理解できなかったことが
少しは理解できるようになりました。
リスト構造を使うことで容量の大幅な削減ができるのが
大きなメリットになるのですね。
似たような問題をこなして感覚を磨いていく作業は
必要になりますが土台はできた感じがします。
お時間割いて説明していただきありがとうございました。
助かりました。
jjon-com 様
お二方とも再度のご説明ありがとうございます。
ご指摘の通り
> prevにはすべての要素が格納されている
上記に対する理解が足りていませんでした。
listHeadには120が格納されていて
prevには最初から
{120,150,100,130,110}
という値がすべて格納されている。
という風に考えてしまっていました。
> prevもlistHeadと同じクラスのインスタンスを参照しているので
> 「listHeadはこうだけどprevはこう」にはならないはずです。
⇒まさにおっしゃる通りです。自分で同じクラスから作成したと言っておきながら
理解しきれていませんでした。
この問題をまとめると
まずクラスListElementを元にして
以下のデータを作成する。
val next
+ーーー+ーー+
|京都 | 130|
+ーーー+ーー+
val next
+ーーー+ーー+
|広島 |NULL|
+ーーー+ーー+
val next
+ーーー+ーー+
|東京 | 150|
+ーーー+ーー+
val next
+ーーー+ーー+
|新大阪| 110|
+ーーー+ーー+
val next
+ーーー+ーー+
|名古屋| 100|
+ーーー+ーー+
クラスListElementとは別に
「ListElement型の変数」も登場し
対象となる変数は【listHead】【prev】【next】の3つ
ここで注意しないといけないのが
「ListElement型の変数」にはアドレス値のみが格納されること
※整合性が取れているアドレス値以外は格納不可(NULL(未定義)は例外)
格納されているprevまたはlistHeadのアドレス値を参照しながら
リストをたどっていく。
という前提で「次の要素(prev.next)」をどう書き換えれば
削除行為が可能か?
という問題になるわけですね。
そのうえで
delNode(3)をトレースすると
2 ≦ 2の条件を1度は満たすため
ループ処理を実行しアドレス値の書き換えを行う。
この時、1度ループ処理を実行しているため
prevで指定される要素が1つずれる。
ループ内でlistHeadの指定される要素は動いていないため
ウのlistHead.next.nextだと不適で
カのprev.next.nextが正答となる。
おかげさまで一人では全く理解できなかったことが
少しは理解できるようになりました。
リスト構造を使うことで容量の大幅な削減ができるのが
大きなメリットになるのですね。
似たような問題をこなして感覚を磨いていく作業は
必要になりますが土台はできた感じがします。
お時間割いて説明していただきありがとうございました。
助かりました。
2024.10.10 23:33
科目B勉強中さん
(No.15)
最後の最後にもう1つだけ・・
ちなみになんですがこの問題の難易度は
本番の試験で言うとどれくらいのレベルに分類されるのでしょうか?
実際に出題された問題は秘匿されているため厳密には分からないのは承知なのですが
個人の肌感覚でもいいので参考までに教えていただければと思います。
答えにくい質問かもしれませんが
これが簡単な問題だよと言われると
ちょっと心が折れてしまいそうです・・
よろしくお願いします。
ちなみになんですがこの問題の難易度は
本番の試験で言うとどれくらいのレベルに分類されるのでしょうか?
実際に出題された問題は秘匿されているため厳密には分からないのは承知なのですが
個人の肌感覚でもいいので参考までに教えていただければと思います。
答えにくい質問かもしれませんが
これが簡単な問題だよと言われると
ちょっと心が折れてしまいそうです・・
よろしくお願いします。
2024.10.10 23:39
boyonboyonさん
★FE シルバーマイスター
(No.16)
横から失礼します。
このプログラムのポイントは、下記の①②の処理の違いです。
①prev←prev.next
の処理は、基準になるprevが変わっていくこと、つられてnextも変わることです。
最初は下記の状態だとすると
prev prev.next prev.next.next
120 150 100
prev←prev.next を実行すると
prev prev.next prev.next.next
120 150 100 130
もう一度実行すると
prev prev.next prev.next.next
120 150 100 130 110
のように変化していきます。
②prev.next←prev.next.next
の処理では、prevに変化はありません。
prev prev.next prev.next.next
120 150 100
prev.next←prev.next.nextを実行すると
prev prev.next.next
120 100
prev prev.next prev.next.next
120 150 100 130 110
prev.next←prev.next.nextを実行すると
prev prev.next.next
120 150 100 110
になります。prevに変化はありません。
①と②の働きの違いが分かれば、理解しやすいかと思います。
蛇足ですが、こちらで例としている駅名のリストですが、
120東京150名古屋100京都130新大阪110広島
と書いてしまうと見やすいですよ。
駅の間の数字は、左の駅のnextと右の駅のアドレスを兼ねています。
名古屋を削除したら
120東京100京都130新大阪110広島
という具合に、
このプログラムのポイントは、下記の①②の処理の違いです。
①prev←prev.next
の処理は、基準になるprevが変わっていくこと、つられてnextも変わることです。
最初は下記の状態だとすると
prev prev.next prev.next.next
120 150 100
prev←prev.next を実行すると
prev prev.next prev.next.next
120 150 100 130
もう一度実行すると
prev prev.next prev.next.next
120 150 100 130 110
のように変化していきます。
②prev.next←prev.next.next
の処理では、prevに変化はありません。
prev prev.next prev.next.next
120 150 100
prev.next←prev.next.nextを実行すると
prev prev.next.next
120 100
prev prev.next prev.next.next
120 150 100 130 110
prev.next←prev.next.nextを実行すると
prev prev.next.next
120 150 100 110
になります。prevに変化はありません。
①と②の働きの違いが分かれば、理解しやすいかと思います。
蛇足ですが、こちらで例としている駅名のリストですが、
120東京150名古屋100京都130新大阪110広島
と書いてしまうと見やすいですよ。
駅の間の数字は、左の駅のnextと右の駅のアドレスを兼ねています。
名古屋を削除したら
120東京100京都130新大阪110広島
という具合に、
2024.10.11 00:27
boyonboyonさん
★FE シルバーマイスター
(No.17)
もう一つ蛇足を思いつきました。
このプログラムで削除したいのは、prev.nextです。
削除したいものの前ということで、prevという変数名を付けたのかと思います。
するとprev.nextは、削除したいものの前の次になっちゃいますね。ちょっとおもしろい命名です。
このプログラムで削除したいのは、prev.nextです。
削除したいものの前ということで、prevという変数名を付けたのかと思います。
するとprev.nextは、削除したいものの前の次になっちゃいますね。ちょっとおもしろい命名です。
2024.10.11 00:58
科目B勉強中さん
(No.18)
boyonboyon 様
補足の説明ありがとうございます。
⇒ここがこの問題のキモだと思っています。
⇒こういった書き方もあるのですね。
確かに慣れてきてトレースに時間をかける必要がなくなってきたら
この書き方の方が早いし分かりやすいですね。
参考にさせていただきます。
prevについては確かに少し不思議な命名かもしれませんね
どこを基準に考えるかで変数名も分かりやすくできるかもですが・・
さらっとコメントには目を通させていただきましたが
明日(といってももう今日ですが)じっくり確認させていただきます。
ありがとうございました。
補足の説明ありがとうございます。
> 基準になるprevが変わっていくこと、つられてnextも変わることです
⇒ここがこの問題のキモだと思っています。
> 120東京150名古屋100京都130新大阪110広島
> と書いてしまうと見やすいですよ。
⇒こういった書き方もあるのですね。
確かに慣れてきてトレースに時間をかける必要がなくなってきたら
この書き方の方が早いし分かりやすいですね。
参考にさせていただきます。
prevについては確かに少し不思議な命名かもしれませんね
どこを基準に考えるかで変数名も分かりやすくできるかもですが・・
さらっとコメントには目を通させていただきましたが
明日(といってももう今日ですが)じっくり確認させていただきます。
ありがとうございました。
2024.10.11 01:13
まーぼさん
★FE シルバーマイスター
(No.19)
> ちなみになんですがこの問題の難易度は
>本番の試験で言うとどれくらいのレベルに分類されるのでしょうか?
やや難しめといったところでしょうか。
クラスや参照といったテーマが最初はとっつきにくいと思うので、このように判断しました。ただ、単方向リストはいつも同じようなパターンなので、この問題で慣れておけば本番ではそれほど難しく感じないと思います。
2024.10.11 07:37
まーぼさん
★FE シルバーマイスター
(No.20)
>スレ主さん
ちょっと単方向リストを現実で例えたものを思いついたので書いてみます。
単方向リストは、最大でも一人の人の連絡先しか知らない人の数珠繋ぎのようなものです。
スレ主さんが行きつけの美容室の美容師さんの連絡先を知っているので、
スレ主さん→美容師
のように表現します。
その美容師さんは漫画家の知り合いがいるので、
美容師→漫画家
が成り立ちます。
漫画家はプロスポーツ選手の知り合いがいるので、
漫画家→プロスポーツ選手
が成り立ちます。
スレ主さんは当然美容師さんの連絡先しか知らないので、プロスポーツ選手とは連絡を取るのが難しいように思います。しかし、美容師さんから漫画家をつたえば、漫画家はプロスポーツ選手の連絡先を知っているので、スレ主さんもプロスポーツ選手と連絡を取ることができます。
なんらかの事情でスレ主さんが美容師さんと連絡が取れなくなったとします。すると
スレ主さん→美容師
という関係が成り立たなくなるので、プロスポーツ選手と連絡を取れなくなります。
しかし、美容師さんの連絡先を消す代わりに、漫画家の連絡先をスレ主さんが手に入れられるならば、美容師さんの連絡先を消す前と同様に漫画家を通せばプロスポーツ選手と連絡が取れます。
単方向リストで重要なことは、リストの要素は見かけ上で隣り合っている2つの要素間でしか繋がっていないということです。隣接する2点を順に、地道に、辿っていくことで、配列のように複数の要素を順番に並べた構造を実現できます。
2024.10.11 09:17
boyonboyonさん
★FE シルバーマイスター
(No.21)
この投稿は投稿者により削除されました。(2024.10.11 15:37)
2024.10.11 15:37
boyonboyonさん
★FE シルバーマイスター
(No.22)
TAB使っちゃったらインデントが反映されなかったので再投稿します。
問題の難易度について触れられたので、どんなものかと考えているうちに思いついたことがあります。
問題のプログラムをちょっと変えて
ーーーーーーーーーーーーーーーーーーーー
if(posが1と等しい)
ListHead←ListHead.next
elseif(posが2と等しい)
ListHead.next←ListHead.next.next ***
else
prev←ListHead
for(iを3からposまで1ずつ増やす)
prev←prev.next
endfor
prev.next←【prev.next.next】設問
endif
ーーーーーーーーーーーーーーーーーーーー
この方が、よけいな但し書きなどしないですむので自分的にはわかりやすいです。
しかし、nextをnext.nextで上書きするところがあります。(***のところ)
ここを参考にすると問題としては解答が導きやすくなると思います。
さらに、elseifのところは、
prev←ListHead
prev.next←prev.next.next
ともかけちゃうので、elseif ~ ***をなくすために設問のような書き方をして、ひねりを加えて難易度を上げたんじゃないかなと思いました。
elseif ~ ***を加えれば、選択肢なしでも思いつきそうです。実際の問題では選択肢を見て、「next.nextって何?」と思ってしまうかもしれません。
問題の難易度について触れられたので、どんなものかと考えているうちに思いついたことがあります。
問題のプログラムをちょっと変えて
ーーーーーーーーーーーーーーーーーーーー
if(posが1と等しい)
ListHead←ListHead.next
elseif(posが2と等しい)
ListHead.next←ListHead.next.next ***
else
prev←ListHead
for(iを3からposまで1ずつ増やす)
prev←prev.next
endfor
prev.next←【prev.next.next】設問
endif
ーーーーーーーーーーーーーーーーーーーー
この方が、よけいな但し書きなどしないですむので自分的にはわかりやすいです。
しかし、nextをnext.nextで上書きするところがあります。(***のところ)
ここを参考にすると問題としては解答が導きやすくなると思います。
さらに、elseifのところは、
prev←ListHead
prev.next←prev.next.next
ともかけちゃうので、elseif ~ ***をなくすために設問のような書き方をして、ひねりを加えて難易度を上げたんじゃないかなと思いました。
elseif ~ ***を加えれば、選択肢なしでも思いつきそうです。実際の問題では選択肢を見て、「next.nextって何?」と思ってしまうかもしれません。
2024.10.11 15:41
科目B勉強中さん
(No.23)
まーぼ 様
引き続きご回答ありがとうございます。
少なくとも基本情報において簡単なレベルの問題では
ないということですね。
参考になります。
他のリスト構造の問題も漁って問題に慣れておきたいと思います。
具体例もありがとうございます。
いつかのテレビ番組で
6人たどれば世界中のだれとでも繋がれるみたいな検証がありましたが
あんな感じですね。
最初の人と最後の人は繋がってないけど
途中の要素(人)が繋がっているからそこからたどっていける。
みたいな
現実に具体例を引っ張ってこれるとイメージもわきやすいですね。
長々とご説明いただきありがとうございました。
助かりました。
引き続きご回答ありがとうございます。
少なくとも基本情報において簡単なレベルの問題では
ないということですね。
参考になります。
他のリスト構造の問題も漁って問題に慣れておきたいと思います。
具体例もありがとうございます。
いつかのテレビ番組で
6人たどれば世界中のだれとでも繋がれるみたいな検証がありましたが
あんな感じですね。
最初の人と最後の人は繋がってないけど
途中の要素(人)が繋がっているからそこからたどっていける。
みたいな
現実に具体例を引っ張ってこれるとイメージもわきやすいですね。
長々とご説明いただきありがとうございました。
助かりました。
2024.10.11 18:27
科目B勉強中さん
(No.24)
boyonboyon 様
変えたプログラム拝見させていただきましたが
少なくとも問題で出題されているプログラムよりかは
こちらの方がどんな処理をしているのかイメージしやすいです。
ですが結局
for(iを3からposまで1ずつ増やす)
というところで
delNode(3)の時に
3から3まで1ずつ増やすとなったときに
一度はループ処理に入るということが分かっていないと
正解にたどり着きにくくなりますね・・
補足説明ありがとうございました。
変えたプログラム拝見させていただきましたが
少なくとも問題で出題されているプログラムよりかは
こちらの方がどんな処理をしているのかイメージしやすいです。
ですが結局
for(iを3からposまで1ずつ増やす)
というところで
delNode(3)の時に
3から3まで1ずつ増やすとなったときに
一度はループ処理に入るということが分かっていないと
正解にたどり着きにくくなりますね・・
補足説明ありがとうございました。
2024.10.11 18:34
広告
返信投稿用フォーム
投稿記事削除用フォーム
広告