二重リンクリスト: C++, Python (Code 例)
⚡ スマートサマリー
二重リンクリストは線形データ構造であり、各ノードはデータに加えて、前のノードへのポインタと次のノードへのポインタの2つを格納します。そのため、順方向と逆方向の両方に効率的に移動できます。

二重リンクリストとは何ですか?
二重リンクリストでは、各ノードは前のノードと次のノードの両方へのリンクを持っています。各ノードは3つの要素で構成されます。1つはデータを保持し、残りの2つは次のノードと前のノードへのポインタです。これらの2つのポインタは、特定のノードから前後に移動するのに役立ちます。
二重リンクリストの基本的な構造は以下のとおりです。
二重リンクリストの構造
すべての連結リストには先頭ノードと末尾ノードがあります。先頭ノードには 前のページ (前のポインタ)ノード、そして末尾ノードには 次の ノード。
二重リンクリストに関する重要な用語をいくつか紹介します。
- 前のページ: 各ノードは前のノードにリンクされています。 ポインタまたはリンクとして使用されます。
- 次の投稿: 各ノードは次のノードにリンクされます。 ポインタまたはリンクとして使用されます。
- 日付: これはノードにデータを保存するために使用されます。データは他のものを保持できます データ構造 その内部には、文字列、辞書、セット、ハッシュマップなどの構造体を格納できます。
二重リンクリストにおける単一ノードの基本的な構造は以下のとおりです。
二重リンクリストのノードの構造
Opera二重リンクリストの構成
二重リンクリストの操作には、ノードの追加、削除、挿入、および削除、ならびにリストを上から下または下から上に走査することが含まれます。
二重リンクリストに対して実行可能な操作の一覧は以下のとおりです。
- 前で挿入
- 末端または最後のノードへの挿入
- ノードの後に挿入
- ノードの前に挿入
- 前から削除
- 末尾からの削除
- ノードの検索と削除
- 頭から尾までトラバースする
- 尾部から頭までトラバースする
これらの各操作の実装と擬似コードを以下に示します。
二重リンクリストの先頭への挿入
先頭への挿入とは、連結リストにノードを作成し、それをリストの先頭に配置することを意味します。
例えば、あるノードが与えられたとします。 15ヘッドノードとして追加する必要があります。
この操作を実行する際には、以下の2つの重要な条件が適用されます。
- 二重リンクリストが空の場合、新しいノードが先頭ノードになります。
- 既に先頭ノードが存在する場合、以前の先頭ノードは新しいノードに置き換えられます。
この操作の擬似コードは以下のとおりです。
function insertAtFront(ListHead, value): newNode = Node() newNode.value = value ListHead.prev = newNode newNode.next = ListHead newNode.prev = NULL return ListHead
フロントノードへの挿入
二重リンクリストの末尾への挿入
末尾への挿入とは、連結リストにノードを作成し、それを末尾に配置することを意味します。
この操作を実行するには、次の2つの方法があります。
- 方法1: 二重リンクリストの先頭から順にたどっていき、 次の null になります。次に、新しいノードをリンクします。 次の ポインター。
- 方法2: 二重リンクリストの最後のノードを取ります。次に、 次の 最後のノードのポインタが新しいノードを指す。新しいノードが末尾ノードとなる。
末尾ノードへの挿入を表す擬似コードは以下のとおりです。
function insertAtTail(ListHead, value): newNode = Node() newNode.value = value newNode.next = NULL while ListHead.next is not NULL: ListHead = ListHead.next newNode.prev = ListHead ListHead.next = newNode return ListHead
リンクされたリストの最後に挿入
ノードの後の挿入
以下のような既存の二重リンクリストを考えてみましょう。
目標は、指定されたノードを、そのノードの値の後にリンクすることです。 12.
ステップ1) 先頭から最後のノードまでをたどります。どのノードに値があるかを確認します。 12.
ステップ2) 新しいノードを作成し、それをノードの次のポインターとして割り当てます。 12を選択します。 次の 新しいノードのノード数は15になります。
二重リンクリストにおいて、ノードの後にノードを挿入するための擬似コードを以下に示します。
function insertAfter(ListHead, searchItem, value): List = ListHead newNode = Node() newNode.value = value while List.value is not equal searchItem: List = List.next newNode.next = List.next newNode.prev = List List.next = newNode
ノードの後の挿入
ノードの前への挿入
この操作は、ノードの後に挿入する操作に似ています。特定のノード値が検索され、その後、新しいノードが作成されて、検索されたノードの前に挿入されます。
指定されたノードを挿入するには 15 ノードの前 12、 次の手順を実行します:
ステップ1) リンクされたリストを先頭ノードから末尾ノードまでたどります。
ステップ2) 現在のノードの次のポインターが値を持っているかどうかを確認します 12.
ステップ3) 新しいノードを挿入します 次の 現在のノードのノード。
二重リンクリストにおいて、ノードの前にノードを挿入するための擬似コードを以下に示します。
function insertBefore(ListHead, searchItem, value): List = ListHead newNode = Node() newNode.value = value while List.next.value is not equal searchItem: List = List.next newNode.next = List.next newNode.prev = List List.next = newNode
ノードの前にノードを挿入する
二重リンクリストの先頭要素を削除する
二重リンクリストの先頭ノードには前のノードがありません。 次の 現在のヘッドが削除されると、ポインタが新しいヘッドノードになります。削除されたノードが占有していたメモリを解放することも必要です。
ヘッドノードを削除する手順は以下のとおりです。
ステップ1) 現在のヘッド ノードに変数を割り当てます。
ステップ2) 仕様書や製品情報の確認は、 次の 現在のヘッドノードのノードを作成し、 前のページ ポインタがNULLです。これにより、2番目のノードが1番目のノードから切断されます。
ステップ3) 前の先頭ノードが占有していたメモリを解放します。
二重リンクリストから先頭要素を削除するための擬似コードを以下に示します。
function deleteHead(ListHead): PrevHead = ListHead ListHead = ListHead.next ListHead.prev = NULL PrevHead.next = NULL free memory(PrevHead) return ListHead
ヘッドノードの削除
削除後には、割り当てられたメモリを解放する必要があります。そうしないと、削除されたブロックのメモリはプログラムの実行中ずっと占有されたままになり、他のアプリケーションはそのメモリ領域を使用できなくなります。
二重リンクリストの末尾を削除する
この操作は、先頭の削除に似ています。先頭の代わりに末尾が削除されます。ノードが末尾であるかどうかを識別するには、次のポインタがNULLかどうかを確認します。末尾を削除した後、メモリを解放する必要があります。
この手術は、 背面からの削除.
これを行う手順は次のとおりです。
ステップ1) 二重リンクリストの末尾ノードまで走査します。
ステップ2) 変数またはポインターを末尾ノードに割り当てます。
ステップ3) をセットする 次の NULLへのポインタを生成し、末尾ノードのメモリを解放します。
末尾ノードを削除するための擬似コードは以下のとおりです。
function deleteTail(ListHead): head = ListHead while ListHead.next is not NULL: ListHead = ListHead.next Tail = ListHead ListHead.prev.next = NULL free memory(Tail) return head
二重リンクリストからノードを検索して削除する
この操作では、特定のノード値を検索し、そのノードを削除します。連結リストは線形データ構造であるため、線形検索が必要です。削除後、メモリを解放する必要があります。
二重リンクリスト内のノードを検索および削除する手順は以下のとおりです。
ステップ1) リンクリストの先頭から順にたどり、ノードの値が検索項目と一致するまで進みます。
ステップ2) 変数に値を代入する ノードの削除 一致したノードへ。
ステップ3) 前のノードをリンクします ノードの削除 次のノードへ移動し、次のノードの 前のページ 前のノードへのポインタ。
ステップ4) 記憶を解放する ノードの削除.
以下は、連結リストからノードを検索および削除するための擬似コードです。
function searchAndDelete(ListHead, searchItem): head = ListHead while head.value not equals searchItem: head = head.next deleteNode = head head.prev.next = head.next if head.next is not NULL: head.next.prev = head.prev free memory(deleteNode) return ListHead
検索と削除操作
二重リンクリストを前方から走査する
先頭ノードから順に走査し、NULLが見つかるまで次のノードを順に処理します。各ノードを走査する際に、その値を表示できます。順方向走査の手順は以下のとおりです。
ステップ1) 現在のヘッド ノードにポインターまたは変数を割り当てます。
ステップ2) NULL になるまで、先頭の次のノードを繰り返します。
ステップ3) 各反復処理でノードデータを出力します。
ステップ4) ヘッド ノードを返します。
二重リンクリストを先頭から走査するための擬似コードを以下に示します。
function traverseFromFront(ListHead): head = ListHead while head not equals NULL: print head.data head = head.next return ListHead
返却は必須ではありませんが、操作後にヘッドノードを返却することは良い習慣です。
二重リンクリストを後方から走査する
この操作は、正面からのトラバースの逆です。アプローチは同じですが、1つだけ小さな違いがあります。まず終点ノードに到達し、次にヘッドに向かって後ろ向きに歩きます。 前のページ ポインター。
二重リンクリストを末尾から走査する手順は以下のとおりです。
ステップ1) 末端ノードに到達するまで辿ります。
ステップ2) 末端ノードから、以下を使用してトラバースします。 前のページ 前のノードがNULLになるまで。 前のページ ヘッドノードのポインタがnullです。
ステップ3) 各反復処理において、ノードデータを出力する。
後方から走査するための擬似コードを以下に示します。
function traverseFromBack(ListHead): head = ListHead while head.next is not NULL: head = head.next tail = head while tail is not NULL: print tail.value tail = tail.prev return ListHead
単方向リンクリストと双方向リンクリストの違い
単方向連結リストと双方向連結リストの主な違いは、各ノードが持つリンクの数です。
単方向連結リストと双方向連結リストのノードの違いは以下のとおりです。
| フィールド | 単方向リスト | 二重リンクリスト |
|---|---|---|
| Structure | 単方向リスト XNUMX つのデータ フィールドと次のノードへの XNUMX つのリンクがあります。 | 二重リンク リストには XNUMX つのデータ フィールドと XNUMX つのリンクがあります。 XNUMX つは前のノード用で、もう XNUMX つは次のノード用です。 |
| トラバーサル | 頭から尾までしか移動できません。 | 前方にも後方にも横断できます。 |
| メモリ | メモリの占有量が少なくなります。 | 単方向連結リストよりも多くのメモリを消費します。 |
| ユーザー補助 | 単方向連結リストは、次のノードへのリンクが1つしかないため、効率が劣ります。前のノードへのリンクはありません。 | 双方向アクセスにおいては、双方向リンクリストは単方向リンクリストよりも効率的です。 |
二重リンクリスト C++
以下は完全なものです C++ 挿入、削除、検索、走査操作を備えた二重リンクリストの実装。
#include<iostream> using namespace std; struct node{ int data; struct node *next; struct node *prev; }; void insertFront(node* &listHead, int value){ node* newNode = new node(); newNode->data = value; newNode->prev = NULL; newNode->next = NULL; if(listHead != NULL){ listHead->prev = newNode; newNode->next = listHead; } listHead = newNode; cout<<"Added "<<value<<" at the front"<<endl; } void insertEnd(node* &listHead, int value){ if(listHead == NULL){ insertFront(listHead, value); return; } node* newNode = new node(); newNode->data = value; newNode->prev = NULL; newNode->next = NULL; node *head = listHead; while(head->next != NULL){ head = head->next; } head->next = newNode; newNode->prev = head; cout<<"Added "<<value<<" at the end"<<endl; } void insertAfter(node* &listHead, int searchValue, int value){ node* newNode = new node(); newNode->data = value; newNode->prev = NULL; newNode->next = NULL; node *head = listHead; while(head->next != NULL && head->data != searchValue){ head = head->next; } newNode->next = head->next; head->next = newNode; newNode->prev = head; if(newNode->next != NULL){ newNode->next->prev = newNode; } cout<<"Inserted "<<value<<" after node "<<searchValue<<endl; } void insertBefore(node* &listHead, int searchValue, int value){ node* newNode = new node(); newNode->data = value; newNode->prev = NULL; newNode->next = NULL; node *head = listHead; while(head->next != NULL && head->next->data != searchValue){ head = head->next; } newNode->next = head->next; head->next = newNode; newNode->prev = head; if(newNode->next != NULL){ newNode->next->prev = newNode; } cout<<"Inserted "<<value<<" before node "<<searchValue<<endl; } void traverseFromFront(node *listHead){ node* head = listHead; cout<<"Traversal from head:\t"; while(head != NULL){ cout<<head->data<<"\t"; head = head->next; } cout<<endl; } void traverseFromEnd(node *listHead){ node* head = listHead; cout<<"Traversal from tail:\t"; while(head->next != NULL){ head = head->next; } node *tail = head; while(tail != NULL){ cout<<tail->data<<"\t"; tail = tail->prev; } cout<<endl; } void searchAndDelete(node **listHead, int searchItem){ node* head = (*listHead); while(head != NULL && head->data != searchItem){ head = head->next; } if(*listHead == NULL || head == NULL) return; if((*listHead)->data == head->data){ *listHead = head->next; } if(head->next != NULL){ head->next->prev = head->prev; } if(head->prev != NULL){ head->prev->next = head->next; } free(head); cout<<"Deleted Node\t"<<searchItem<<endl; } int main(){ node *head = NULL; insertFront(head, 5); insertFront(head, 6); insertFront(head, 7); insertEnd(head, 9); insertEnd(head, 10); insertAfter(head, 5, 11); insertBefore(head, 5, 20); traverseFromFront(head); traverseFromEnd(head); searchAndDelete(&head, 7); traverseFromFront(head); traverseFromEnd(head); }
出力
Added 5 at the front Added 6 at the front Added 7 at the front Added 9 at the end Added 10 at the end Inserted 11 after node 5 Inserted 20 before node 5 Traversal from head: 7 6 20 5 11 9 10 Traversal from tail: 10 9 11 5 20 6 7 Deleted Node 7 Traversal from head: 6 20 5 11 9 10 Traversal from tail: 10 9 11 5 20 6
二重リンクリスト Python
以下は完全なものです Python ノードとリスト自体にクラスを使用した二重リンクリストの実装。
class Node: def __init__(self, data=None, prev=None, next=None): self.data = data self.next = next self.prev = prev class DoublyLinkedList: def __init__(self): self.head = None def insertFront(self, val): newNode = Node(data=val) newNode.next = self.head if self.head is not None: self.head.prev = newNode self.head = newNode print("Added {} at the front".format(val)) def insertEnd(self, val): newNode = Node(data=val) if self.head is None: self.head = newNode print("Added {} at the end".format(val)) return temp = self.head while temp.next is not None: temp = temp.next temp.next = newNode newNode.prev = temp print("Added {} at the end".format(val)) def traverseFromFront(self): temp = self.head print("Traversing from head:\t", end="") while temp is not None: print("{}\t".format(temp.data), end="") temp = temp.next print() def traverseFromEnd(self): temp = self.head print("Traversing from tail:\t", end="") while temp.next is not None: temp = temp.next tail = temp while tail is not None: print("{}\t".format(tail.data), end="") tail = tail.prev print() def insertAfter(self, searchItem, value): newNode = Node(data=value) temp = self.head while temp.next is not None and temp.data != searchItem: temp = temp.next newNode.next = temp.next temp.next = newNode newNode.prev = temp if newNode.next is not None: newNode.next.prev = newNode print("Inserted {} after node {}".format(value, searchItem)) def insertBefore(self, searchItem, value): newNode = Node(data=value) temp = self.head while temp.next is not None and temp.next.data != searchItem: temp = temp.next newNode.next = temp.next temp.next = newNode newNode.prev = temp if newNode.next is not None: newNode.next.prev = newNode print("Inserted {} before node {}".format(value, searchItem)) def searchAndDelete(self, searchItem): temp = self.head while temp is not None and temp.data != searchItem: temp = temp.next if self.head is None or temp is None: return if self.head.data == temp.data: self.head = temp.next if temp.next is not None: temp.next.prev = temp.prev if temp.prev is not None: temp.prev.next = temp.next print("Deleted Node\t{}".format(searchItem)) doublyLinkedList = DoublyLinkedList() doublyLinkedList.insertFront(5) doublyLinkedList.insertFront(6) doublyLinkedList.insertFront(7) doublyLinkedList.insertEnd(9) doublyLinkedList.insertEnd(10) doublyLinkedList.insertAfter(5, 11) doublyLinkedList.insertBefore(5, 20) doublyLinkedList.traverseFromFront() doublyLinkedList.traverseFromEnd() doublyLinkedList.searchAndDelete(7) doublyLinkedList.traverseFromFront() doublyLinkedList.traverseFromEnd()
出力
Added 5 at the front Added 6 at the front Added 7 at the front Added 9 at the end Added 10 at the end Inserted 11 after node 5 Inserted 20 before node 5 Traversing from head: 7 6 20 5 11 9 10 Traversing from tail: 10 9 11 5 20 6 7 Deleted Node 7 Traversing from head: 6 20 5 11 9 10 Traversing from tail: 10 9 11 5 20 6
二重リンクリストの複雑さ
時間計算量は一般的に、最良の場合、平均的な場合、最悪の場合の3種類に分類されます。
二重リンクリストの最良のケースにおける時間の計算量:
- 先頭または末尾への挿入は、連結リスト内部の走査が不要なため、O(1)の計算量で済みます。先頭ポインタと末尾ポインタによって、先頭ノードと末尾ノードに直接アクセスできます。
- 先頭または末尾の削除にはO(1)のコストがかかります。
- ノードの検索コストは、対象ノードがヘッドノードの場合、O(1)です。
二重リンクリストの平均的なケースにおける時間の計算量:
- 先頭または末尾への挿入にはO(1)のコストがかかります。
- 先頭または末尾の削除にはO(1)のコストがかかります。
- ノードの検索にはO(n)のコストがかかります。なぜなら、ターゲットはリスト内のどこにでも存在する可能性があるからです。 n ノードの合計数です。
二重リンクリストの最悪ケースの時間計算量は、平均的なケースと同じです。
二重リンクリストのメモリ複雑度
メモリの複雑さはO(n)であり、 n これはノードの総数です。リンクリストを実装する際には、メモリを解放する必要があります。そうしないと、大きなリンクリストではメモリリークが発生します。
二重リンクリストの応用
双方向リンクリストは、双方向走査によって多くの一般的な操作が簡素化されるため、現実世界の様々なデータ構造で利用されています。
- LRUキャッシュ: 最も最近使用されていないキャッシュは、O(1)の先頭への移動と削除のためにハッシュマップを備えた二重リンクリストを使用します。
- ブラウザの履歴: 戻る/進むナビゲーションは、リンクされたリストをどちらの方向にも移動します。
- 元に戻す/やり直しスタック: エディタとIDE trac前後のポインタを持つk個のドキュメントバージョン。
- デク: Double両端キューは、両端からO(1)の時間でプッシュとポップを行う。
- 音楽プレイリスト: 前へと次へ trackボタンは、前後のポインタに依存しています。











