大きな画面での利用をおすすめします — アニメーションと操作がより快適になります。
入力

循環連結リスト

循環単方向リストは最後の next を null ではなく最初のノードへ向けます。このレッスンは head と tail を保存し、空でない時に tail.next == head を維持します。

next をたどると始点へ戻り、空参照には達しません。したがって一周して curr == head になったら止めます。この条件を忘れると、存在しない値の検索が無限に続きます。

循環リストの仕組み

円形に並ぶ走者が、次の一人へバトンを渡す形です。コースに終端はなく、出発した人へ戻ったことを自分で判定します。

両端を保存するため、insert_head と insert_tail は O(1)。先頭挿入は新ノードを旧先頭へ向け、head と tail.next を更新します。末尾挿入は新ノードを先頭へ、旧末尾を新ノードへ向け、tail を進めます。毎回 tail.next = head を回復し、画面の折り返し矢印が変化を示します。末尾へ走査しないのは tail の効果で、通常の単方向リストにも追加できます。

search は先頭から比較し、一致すれば途中で添字を返します。不一致なら末尾から先頭へ戻り、進んだ後の curr == head で未発見を返します。成功と失敗では停止理由が違います。

delete は同じ一周の境界で対象と前のノードを探し、前から対象を飛ばします。発見 O(n)、参照変更 O(1)。先頭削除は折り返し先を変え、末尾削除は tail を前へ戻し、唯一のノードなら空にします。

容量 4–10、既定 7 のスライダーは表示制限で、コードの max が対応します。リスト自体の固定容量ではありません。

操作をステップで追う

空の環から始めます。

  1. 先頭に 5:5.next = 5 の自己ループ。head と tail はともに 5。5 ↺。
  2. 先頭に 8:8.next = 5、旧末尾の 5.next = 8、head を 8 へ。8 → 5 → 8 に戻る。
  3. 末尾に 3:tail はすでに 5。3.next = 8、5.next = 3、tail を 3 へ。8 → 5 → 3 → 8 に戻る。
  4. 末尾に 7:7.next = 8、3.next = 7、tail を 7 へ。8 → 5 → 3 → 7 → 8 に戻る。
  5. 3 を検索:添字 0 で 8 ≠ 3、1 で 5 ≠ 3、2 で 3 = 3。2 を返し、途中で停止。
  6. 9 を検索:8 ≠ 9、5 ≠ 9、3 ≠ 9、7 ≠ 9。末尾を越えたカーソルが折り返し矢印で先頭へ戻る。curr == head で未発見として停止。null は一度も使わない。

画面の通常の next はノード列に、尾から頭への線は下にあります。失敗検索のカーソルはその弧を移動し、一周を目で追えます。

時間・空間計算量

操作費用理由
添字アクセスO(n)先頭から next をたどる
先頭挿入O(1)旧先頭へつなぎ、折り返しを更新
末尾挿入O(1)tail を保持
値による削除O(n)最大一周の探索と定数回の変更
検索O(n)一致か一周まで
空間O(n)各ノードに値と参照一つ

ノードごとの保存量は単方向リストと同じで、リスト全体に尾への参照を加えます。輪番スケジューリングやリンク型の循環バッファーに適し、環と停止条件の維持が必要です。循環バッファーは配列でも実装でき、リストに限りません。

境界ケース

  • 一ノードの環:node.next = node、head == tail。画面は自己ループを示す。
  • 先頭削除:複数ノードなら head = node.next、次に tail.next = head。
  • 末尾削除:複数ノードなら tail = prev、次に tail.next = head。
  • 唯一のノードの削除:head と tail をともに空にする。旧ノードへの環を残さない。
  • 値がない:ちょうど一周し、先頭へ戻って -1 / False。null を待たない。
  • 空の検索:if not self.head で走査前に返す。

よくある間違い

  • 空参照を待つ。 非空の環で while curr: や while curr.next: は終わりません。処理して進み、head に戻ったかを確認します。既定の検索 9 で試せます。
  • 折り返しの更新を忘れる。 挿入や端の削除後に tail.next = head を回復します。旧末尾の接続を忘れると切断や新ノードを飛ばす環になります。
  • 末尾削除後に tail を残す。 次の挿入が、環から外したノードにつながります。
  • 一ノードを特別扱いしない。 head == tail の削除では両方を空にし、空への最初の挿入では自己ループを作ります。常に二ノード以上あるという仮定は使えません。