4.6 演習¶
4.6.1 確認問題¶
1. 配列と連結リストが要素を見つける方法¶
配列と単方向連結リストに、どちらも [A, B, C, D, E] が順に保存されています。ここで 4 番目の要素 D を読み取ります。
- 配列では、どのインデックスを直接使えますか?
- 単方向連結リストでは、先頭ノード
Aから始めて、nextをたどりながらどのノードを通りますか? - 読み取る位置が後ろになるほど、2 つの構造で必要な手順はどのように変わりますか?位置を指定した読み取りを繰り返すには、どちらが適していますか?その理由も説明してください。
解答
-
インデックスが 0 から始まる場合、4 番目の要素のインデックスは 3 なので、配列では
arr[3]を直接読み取れます。 -
単方向連結リストでは、必ず先頭から始めます。アクセスする経路は
A → B → C → Dで、nextを 3 回たどります。 -
配列は先頭アドレスとインデックスから要素の位置を直接求められるため、位置を指定したアクセスの時間計算量は \(O(1)\) です。 単方向連結リストで \(k\) 番目のノードにアクセスするには、先頭ノードから
nextを \(k-1\) 回たどる必要があり、 最悪の場合は \(O(n)\) の時間がかかります。ここでは位置を指定した読み取りだけを比較しており、連結リストがすべての操作で遅いという意味ではありません。
2. 配列と連結リストに要素を挿入する方法¶
配列と単方向連結リストに、どちらも A、B、C、D が保存されています。ここで B の後ろに X を挿入します。
- 配列の容量は 5 で、現在の状態は
[A, B, C, D, _]です。 - 連結リストは
A → B → C → Dで、ノードBを指す参照はすでに得られています。
- 配列では、どの要素を移動する必要がありますか?挿入後の配列も書いてください。
- 連結リストでは、
X.nextとB.nextをどの順序で変更すべきですか?挿入後の連結リストも書いてください。 - 挿入の効率を比較するときに、「ノード B を指す参照はすでに得られている」と明記する必要があるのはなぜですか?
解答
-
配列では、まず
Dを 1 つ右へ移動し、次にCを 1 つ右へ移動します。最後にXをインデックス 2 に置くと、[A, B, X, C, D]になります。 -
B.nextはもともとCを指しています。まずX.next = B.nextとしてXがCを指すようにし、 次にB.next = Xとします。その結果、A → B → X → C → Dとなります。 元の接続を保存せずに先にB.nextを上書きすると、Cを見つけられなくなる可能性があります。 -
Bの位置が分かっていれば、連結リストへの挿入は 2 つの接続を変更するだけなので、\(O(1)\) の時間で行えます。 先頭からBを探す必要もある場合、その探索自体に \(O(n)\) の時間がかかる可能性があります。
3. リストの容量が増える仕組み¶
配列を基に実装されたリストの現在の内容が [A, B, C]、長さが size = 3、容量が capacity = 4 であるとします。
容量が足りないときは、新しい配列の容量を元の 2 倍にするものとします。
Dを追加した後、リストの長さと容量はそれぞれいくつですか?容量を増やす必要はありますか?- 続けて
Eを追加すると、容量はいくつになりますか?既存の要素をいくつコピーする必要がありますか? - 内部で使う配列の長さは変えられないのに、リストの容量が増えたように見えるのはなぜですか?
解答
-
Dは最後の空き位置に置けます。このとき内容は[A, B, C, D]、size = 4、capacity = 4となり、容量を増やす必要はありません。 -
さらに
Eを追加するときは空き位置がないため、容量 8 の新しい配列を作り、 既存の要素A、B、C、Dの 4 つをコピーしてからEを追加します。 このときsize = 5、capacity = 8です。 -
元の配列そのものが長くなったわけではありません。リストはより大きな新しい配列を作って既存の要素をコピーし、 内部の保存先を新しい配列に切り替えます。そのため、利用者から見ると容量が増えたように見えます。
4.6.2 プログラミング演習¶
1. 配列で表した大きな整数に 1 を足す¶
配列 digits には、0 以上の整数の各桁が左から右へ保存されています。たとえば、[3, 0, 8] は 308 を表します。
数値 0 は [0] で表し、それ以外の入力の先頭の桁は 0 ではありません。
十進数の筆算と同じように、この整数に 1 を足し、結果を同じ配列形式で返してください。
digits は直接変更してかまいません。先頭に新しい繰り上がりが生じた場合は、より長い配列を返せます。
解法のヒント
- 筆算の加算と同じように、配列の末尾の桁から始めます
- 現在の桁が 9 より小さければ、1 を足してすぐに返せます
- 現在の桁が 9 なら 0 に変えます。すべての桁が 9 だった場合は、先頭に新しく 1 を置く必要があります
2. 単方向連結リストの反転¶
単方向連結リストの先頭ノード head が与えられます。各ノードには、値と次のノードを指す next が含まれています。
反復を使ってすべてのノード間の接続を反転し、反転後の先頭ノードを返してください。 新しい連結リストノードを作ってはいけません。
解法のヒント
- まず紙に 3 つの連続したノードと、2 つのポインタ prev、cur を描きます
- cur.next を書き換える前に、元の次のノードを nxt に保存する必要があります
- cur.next を反転した後、prev = cur、続いて cur = nxt とし、元の連結リストの次のノードを処理します