C++で実装する双方向循環リンクリスト:アルゴリズムとサンプルコード徹底解説
循環リンクリストとは
循環リンクリスト(Circular Linked List)は、リンクリストの変形版であり、最初の要素が最後の要素を指し、最後の要素が最初の要素を指す構造を持つデータ構造です。片方向リンクリスト(Singly Linked List)でも双方向リンクリスト(Doubly Linked List)でも、循環リンクリストとして実装することができます。
双方向リンクリストの場合、末尾ノードのnextポインタが先頭ノードを指し、先頭ノードのprevポインタが末尾ノードを指すことで、両方向に循環する構造になります。

上図のように、押さえておくべき重要なポイントは以下の2点です。
- 双方向リンクリストでは、末尾リンクのnextがリストの先頭リンクを指します。
- 先頭リンクのprevが、双方向リンクリストの末尾を指します。
アルゴリズム
双方向循環リンクリストに対する主な操作(表示・挿入・削除)の擬似コードは以下の通りです。
displayForward()(先頭から末尾へ表示):
Begin
ptr := head
while ptr != null, do
print ptr->key, ptr->data
ptr := ptr->next
done
End
displayBackward()(末尾から先頭へ表示):
Begin
ptr := last
while ptr != null, do
print ptr->key, ptr->data
ptr := ptr->prev
done
End
insertFirst(key, data)(先頭に挿入):
Begin
keyとdataを持つ新しいノードを作成
if リストが空ならば
last := node
else
head->prev := node
end if
node->next := head
head := node
End
insertLast(key, data)(末尾に挿入):
Begin
keyとdataを持つ新しいノードを作成
if リストが空ならば
last := node
else
last->next := node
node->prev := last
end if
last := node
End
insertAfter(key, newKey, data)(指定キーの直後に挿入):
Begin
current := head
if headがnullならば falseを返す
while currentのkeyがkeyと一致しない間、do
if current->nextがnullならば
falseを返す
else
current := currentのnext
end if
done
newKeyとdataを持つ新しいノードを作成
if current = lastならば
nodeのnext := null
last := node
else
nodeのnext := currentのnext
currentのnextのprev := node
end if
nodeのprev := current
currentのnext := node
trueを返す
End
deleteFirst()(先頭を削除):
Begin
tempNode := head
if headのnextがnullならば
last := null
else
headのnextのprev := null
end if
head := headのnext
tempNodeを返す
End
deleteLast()(末尾を削除):
Begin
tempNode := last
if headのnextがnullならば
head := null
else
lastのprevのnext := null
end if
last := lastのprev
tempNodeを返す
End
deleteNode(key)(指定キーのノードを削除):
Begin
curr := head、prev := null
if headがnullならば
nullを返す
end if
while currのkeyがkeyと異なる間、do
if currのnextがnullならば
nullを返す
else
prev := curr
curr := currのnext
end if
done
if currがheadならば head := headのnext、そうでなければ currのprevのnext = currのnext
if currがlastならば last := currのprev、そうでなければ currのnextのprev = currのprev
currを返す
End
C++での実装例
それでは、上記のアルゴリズムを実際のC++コードで確認してみましょう。以下のサンプルでは、ノードの定義から各操作の実装、そしてmain関数での動作検証までを一通り示しています。
#include <iostream>
#include <cstdio>
using namespace std;
class node {
public:
int data;
int key;
node *next;
node *prev;
};
//このリンクは常に先頭のリンクを指す
node *head = NULL;
//このリンクは常に末尾のリンクを指す
node *last = NULL;
node *current = NULL;
//リストが空かどうかを判定
bool isEmpty() {
return head == NULL;
}
int length() {
int length = 0;
node *current;
for(current = head; current != NULL; current = current->next){
length++;
}
return length;
}
//リストを先頭から末尾へ表示
void displayForward() {
//先頭から開始
node *ptr = head;
//リストの終わりまで移動
printf("\n[ ");
while(ptr != NULL) {
printf("(%d,%d) ",ptr->key,ptr->data);
ptr = ptr->next;
}
printf(" ]");
}
//リストを末尾から先頭へ表示
void displayBackward() {
//末尾から開始
node *ptr = last;
//リストの先頭まで移動
printf("\n[ ");
while(ptr != NULL) {
//データを表示
printf("(%d,%d) ",ptr->key,ptr->data);
//前の項目へ移動
ptr = ptr->prev;
}
}
//先頭位置にリンクを挿入
void insertFirst(int key, int data) {
//リンクを作成
node *link = new node();
link->key = key;
link->data = data;
if(isEmpty()) {
//これを末尾のリンクにする
last = link;
}
else {
//先頭のprevリンクを更新
head->prev = link;
}
//古い先頭リンクを指す
link->next = head;
//新しいリンクを先頭に設定
head = link;
}
//末尾位置にリンクを挿入
void insertLast(int key, int data) {
//リンクを作成
node *link = new node();
link->key = key;
link->data = data;
if(isEmpty()) {
//これを末尾のリンクにする
last = link;
}
else {
//新しいリンクを末尾につなぐ
last->next = link;
//古い末尾ノードを新しいリンクのprevに設定
link->prev = last;
}
//新しいノードを末尾に設定
last = link;
}
//先頭の項目を削除
node* deleteFirst() {
//先頭リンクへの参照を保存
node *tempLink = head;
//リンクが1つだけの場合
if(head->next == NULL){
last = NULL;
}
else {
head->next->prev = NULL;
}
head = head->next;
//削除したリンクを返す
return tempLink;
}
//末尾位置のリンクを削除
node* deleteLast() {
//末尾リンクへの参照を保存
node *tempLink = last;
//リンクが1つだけの場合
if(head->next == NULL) {
head = NULL;
}
else {
last->prev->next = NULL;
}
last = last->prev;
//削除したリンクを返す
return tempLink;
}
//指定キーのリンクを削除
node* del(int key) {
//先頭リンクから開始
node* current = head;
node* previous = NULL;
//リストが空の場合
if(head == NULL) {
return NULL;
}
//リストを走査
while(current->key != key) {
//末尾ノードの場合
if(current->next == NULL) {
return NULL;
}
else {
//現在のリンクへの参照を保存
previous = current;
//次のリンクへ移動
current = current->next;
}
}
//一致が見つかったのでリンクを更新
if(current == head) {
//先頭を次のリンクに変更
head = head->next;
}
else {
//現在のリンクをバイパス
current->prev->next = current->next;
}
if(current == last) {
//末尾を前のリンクに変更
last = current->prev;
}
else {
current->next->prev = current->prev;
}
return current;
}
bool insertAfter(int key, int newKey, int data) {
//先頭リンクから開始
node *current = head;
//リストが空の場合
if(head == NULL) {
return false;
}
//リストを走査
while(current->key != key) {
//末尾ノードの場合
if(current->next == NULL) {
return false;
}
else {
//次のリンクへ移動
current = current->next;
}
}
//リンクを作成
node *newLink = new node();
newLink->key = newKey;
newLink->data = data;
if(current == last) {
newLink->next = NULL;
last = newLink;
}
else {
newLink->next = current->next;
current->next->prev = newLink;
}
newLink->prev = current;
current->next = newLink;
return true;
}
int main() {
insertFirst(1,10);
insertFirst(2,20);
insertFirst(3,30);
insertFirst(4,1);
insertFirst(5,40);
insertFirst(6,56);
printf("\nList (First to Last): ");
displayForward();
printf("\n");
printf("\nList (Last to first): ");
displayBackward();
printf("\nList , after deleting first record: ");
deleteFirst();
displayForward();
printf("\nList , after deleting last record: ");
deleteLast();
displayForward();
printf("\nList , insert after key(4) : ");
insertAfter(4,7, 13);
displayForward();
printf("\nList , after delete key(4) : ");
del(4);
displayForward();
}
実行結果
上記のコードをコンパイルして実行すると、以下のような出力が得られます。先頭からの表示・末尾からの表示、そして各操作後のリストの状態が確認できます。
List (First to Last): [ (6,56) (5,40) (4,1) (3,30) (2,20) (1,10) ] List (Last to first): [ (1,10) (2,20) (3,30) (4,1) (5,40) (6,56) List , after deleting first record: [ (5,40) (4,1) (3,30) (2,20) (1,10) ] List , after deleting last record: [ (5,40) (4,1) (3,30) (2,20) ] List , insert after key(4) : [ (5,40) (4,1) (7,13) (3,30) (2,20) ] List , after delete key(4) : [ (5,40) (7,13) (3,30) (2,20) ]
-
C++で2つの連結リストの交点を見つける方法
連結リストとは連結リスト(Linked List)は線形データ構造の一種です。各ノードは2つの部分で構成されており、一方にはノードの値(データ)が、もう一方には次のノードへのアドレス(ポインタ)が格納されています。ここでは、各ノードがリスト内の他のノードを指すポインタを持つ連結リストを想定します。この問題のタスクは、2つの連結リストが交差するノードを見つけることです。交差していない場合は、NULL(空)を出力として返します。入力例1出力:2解説: 与えられた連結リストは値「2」のノードで交差しているため、「2」を出力として返します。入力例2出力:NULL解説: 共通するノードが存在しないため、
-
C++で循環リンクリストのノード数をカウントする方法
ノードから構成される循環リンクリスト(Circular Linked List)が与えられ、そのリスト内に存在するノードの総数を求めるのが課題です。 循環リンクリストとは、連結リストの一種であり、最初の要素が最後の要素を指し、最後の要素が最初の要素を指すという特徴を持つデータ構造です。片方向リンクリスト(Singly Linked List)でも双方向リンクリスト(Doubly Linked List)でも、この循環リンクリストとして実装することが可能です。 以下のプログラムでは、片方向リンクリストを循環リンクリストとして実装し、その中に含まれるノード数をカウントする方法を紹介します。 具体