二重リンクリスト: C++, Python (コード例)

二重リンクリストとは何ですか?

二重リンクリストでは、各ノードには前と次の両方のノードへのリンクがあります。 各ノードは XNUMX つの要素で構成されます。XNUMX つはデータを保持し、他の XNUMX つは次と前のノードのポインターです。 これら XNUMX つのポインターは、特定のノードから前進または後退するのに役立ちます。

二重リンクリストの基本構造は次のとおりです。

二重リンクリストの構造
二重リンクリストの構造

すべてのリンクされたリストには先頭ノードと末尾ノードがあります。 Head ノードには「prev」(前のポインタ) ノードがありません。また、tail ノードには「next」ノードがありません。

二重リンクリストに関する重要な用語をいくつか示します。

  • 前のページ: 各ノードは前のノードにリンクされています。 ポインタまたはリンクとして使用されます。
  • 次の投稿: 各ノードは次のノードにリンクされます。 ポインタまたはリンクとして使用されます。
  • 日付: これはノードにデータを保存するために使用されます。 「データ」には他のものを保持できます データ構造 その中。 たとえば、文字列、辞書、セット、ハッシュマップなどを「データ」に保存できます。

二重リンク リストの単一ノードの基本構造は次のとおりです。

二重リンクリストのノードの構造

二重リンクリストのノードの構造

Opera二重リンクリストの構成

二重リンク リストの操作には、ノードの追加と削除、ノードの挿入と削除、リンク リストの上から下へのトラバースが含まれます。

二重リンクリストを使用して実装できる操作のリストは次のとおりです。

  • 前で挿入
  • 末尾または最後のノードへの挿入
  • ノードの後に​​挿入
  • ノードの前に挿入
  • 前から削除
  • 末尾からの削除
  • ノードの検索と削除
  • 頭から尾までトラバースする
  • 尾部から頭までトラバースする

上記のすべての操作の実装と疑似コードを確認します。

二重リンクリストの前への挿入

前に挿入するということは、リンク リストにノードを作成し、それをリンク リストの先頭に配置する必要があることを意味します。

たとえば、特定のノード「15」があります。 これをヘッド ノードに追加する必要があります。

この操作を実行する際の重要な条件が 2 つあります。

  1. 二重リンク リストにノードがない場合、新しいノードがヘッド ノードになります。
  2. すでにヘッド ノードがある場合は、前のヘッドが新しいノードに置き換えられます。

この操作の疑似コードは次のとおりです。

function insertAtFront (ListHead, value):
  newNode = Node()
  newNode.value = value
  ListHead.prev = NewNode
  NewNode.next = ListHead
  newNode.prev = NULL
  return ListHead
フロントノードへの挿入
フロントノードへの挿入

二重リンクリストの最後に挿入

「最後に挿入」とは、リンクされたリストにノードを作成し、それを最後に配置することを意味します。

これを実行するには、次の XNUMX つの方法を使用できます。

  • 方法1: 二重リンクリストの先頭から「次」が null になるまでトラバースを開始します。 次に、新しいノードを「次」にリンクします。
  • 方法2: 二重リンクリストの最後のノードを取得します。 次に、最後のノードの「次」のノードが新しいノードにリンクします。 これで、新しいノードが末尾ノードになります。

末尾ノードに挿入するための疑似コードは次のとおりです。

function insertAtTail(ListHead, value):
  newNode = Node()
  newNode.value = value
  newNode.next = NULL
  while ListHead.next is not NULL:
	then 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
	then List = ListHead.next
  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
	then List = ListHead.next
  NewNode.next = List.next
  NewNode.prev = List
  List.next = NewNode
ノードの前にノードを挿入する

ノードの前にノードを挿入する

二重リンクリストの先頭を削除

前のノードを持たない二重リンク リストのヘッド ノード。 したがって、ヘッド ノードを削除する場合、次のポインターは新しいヘッド ノードになります。 ノードを削除する際には、ノードが占有しているメモリを解放する必要もあります。

ヘッド ノードを削除する手順は次のとおりです。

ステップ1) 現在のヘッド ノードに変数を割り当てます。

ステップ2) 現在のヘッド ノードの「次」ノードに移動し、「前」(前のポインタ) ノードを「NULL」にします。 これは、XNUMX 番目のノードを最初のノードから切断していることを意味します。

ステップ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.next
  ListHead.next = NULL
  free memory( Tail )
  return head

二重リンクの末尾を削除

二重リンクリストからノードを検索して削除します

この操作により、特定のノード データを検索し、そのノードを削除できます。リンク リストは線形データ構造であるため、線形検索を実行する必要があります。削除後、メモリを解放する必要もあります。

二重リンクリスト内のノードを検索および削除する手順は次のとおりです。

ステップ1) ノードの値が検索項目と等しくなるまで、リンクされたリストを先頭からたどります。

ステップ2) 一致したノードに変数「deleteNode」を割り当てます。

ステップ3) 「deleteNode」の前のノードを次のノードに割り当てます。

ステップ4) 「deleteNode」のメモリを解放します。

リンクリストからノードを検索して削除するための疑似コードは次のとおりです。

function SearchAndDelete(ListHead, searchItem):
  head = ListHead
  while ListHead.next.value not equals searchItem:
	head = head.next
  deleteNode = head.next
  head.next = head.next.next
  head.next.prev = head
  deleteNode.next, deleteNode.next = NULL
  free memory(deleteNode)
  return ListHead
検索と削除 Opera生産

検索と削除操作

二重リンクリストを前方から走査する

ヘッド ノードからトラバースし、「NULL」が見つかるまで次のノードを反復します。 各ノードを走査しながら、ノードの値を出力できます。 正面(前方向)から横切る手順は次のとおりです。

ステップ1) 現在のヘッド ノードにポインターまたは変数を割り当てます。

ステップ2) 「NULL」になるまでヘッドの次のノードまで繰り返します。

ステップ3) 各反復でノードのデータを出力します。

ステップ4) ヘッド ノードを返します。

二重リンクリストを前から走査するための疑似コードは次のとおりです。

function traverseFromFront(ListHead):
  head = ListHead
  while head not equals to NULL:
	print head.data
	head = head.next
  return ListHead

ここで、戻りは必須ではありません。ただし、操作後にヘッドノードを返すことをお勧めします。

二重リンクリストを後方からたどる

この操作は、前方からのトラバースの逆です。アプローチは少し異なりますが、同じです。エンド ノードまでトラバースしてから、エンド ノードからフロント ノードまでトラバースする必要があります。

二重リンクリストを後ろからたどる手順は次のとおりです。

ステップ1) 尾部ノードに到達するまでトラバースします。

ステップ2) 末尾ノードから、前のノードが「NULL」になるまでトラバースします。 ヘッド ノードの「prev」(前のポインター) は null になります。

ステップ3) 各反復でノード データを出力します。

以下は、後方から横断するための疑似コードです。

function traverseFromBack(ListHead):
  head = ListHead
  while head not equals NULL:
	head = head.next
  tail = head
  while tail not equal to NULL:
	print tail.value
	tail = tail.prev
  return ListHead

単一リンクリストと二重リンクリストの違い

単一リンクリストと二重リンクリストの主な違いは、リンクの数です。

単一リンクリストと二重リンクリストの違い

単一リンク リストのノードと二重リンク リストのノード構造の違いは次のとおりです。

フィールド 単方向リスト 二重リンクリスト
Structure 単方向リスト XNUMX つのデータ フィールドと次のノードへの XNUMX つのリンクがあります。 二重リンク リストには XNUMX つのデータ フィールドと XNUMX つのリンクがあります。 XNUMX つは前のノード用で、もう XNUMX つは次のノード用です。
トラバーサル 頭から尾までしか移動できません。 前方にも後方にも横断できます。
メモリ メモリの占有量が少なくなります。 単一リンクされたリストよりも多くのメモリを占有します。
ユーザー補助 単一リンクリストは、次のノードへのリンクを XNUMX つだけ使用するため、効率が低くなります。 前のノードへのリンクはありません。 二重リンク リストは、単一リンク リストよりも効率的です。

二重リンクリスト 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);
  }
  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 = new 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 = new node();
  head = listHead;	
  cout<<"Traversal from head:\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 = new node();
  head = (*listHead);
  while(head->next!=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;
  return;
  }
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
Aded 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 head:    10      9       11      5       20      6       7
Deleted Node    7
Traversal from head:    6        20      5       11      9       10
Traversal from head:    10      9       11      5       20      6

二重リンクリスト 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.insertFront(val)
    
    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:
      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 is not 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 is not 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.next is not None and temp.data is not searchItem:
      temp = temp.next
    
    if self.head is None or temp is None:
      return

    if self.head.data is 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))
    return

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 つのタイプに分けられます。

彼らは以下のとおりです。

  1. 最良の場合
  2. 平均的なケース
  3. 最悪の場合

二重リンクリストの最良のケースにおける時間の計算量:

  1. ヘッドまたはテールへの挿入には O(1) のコストがかかります。リンク リスト内をトラバースする必要がないためです。ヘッド ポインターとテール ポインターを使用すると、O(1) の時間計算量でヘッド ノードとテール ノードにアクセスできます。
  2. 先頭または末尾の削除には O(1) のコストがかかります。
  3. ノードの検索には O(1) の時間計算量がかかります。これは、ターゲットノードがヘッドノードになる可能性があるためです。

二重リンクリストの平均的なケースにおける時間の計算量:

  1. 先頭または末尾への挿入には、コスト O(1) の時間計算量がかかります。
  2. 先頭または末尾での削除には、コスト O(1) の時間計算量がかかります。
  3. ノードの検索には、O(n) の時間計算量が必要です。これは、検索項目がリンク リスト内のどこにでも存在する可能性があるためです。ここで、「n」はリンク リスト内に存在するノードの合計です。

二重リンクリストの最悪のケースの時間計算量は、平均的なケースと同じになります。

二重リンクリストのメモリ複雑度

メモリの複雑度は O(n) です。ここで、n はノードの総数です。リンク リストを実装する際には、使用したメモリを解放する必要があります。そうしないと、リンク リストが大きくなるとメモリ リークが発生します。

まとめ

  • リンク リストは、線形データ構造の一種であり、線形で表現されたデータの集合です。
  • 二重リンク リストは、ノードが前と次の両方のノードとリンクを持つリンク リストの一種です。
  • 二重リンク リストには、ノードの追加、ノードの削除、別のノードの後または前へのノードの挿入、リンク リストの先頭から末尾へのトラバースなどのすべての操作が含まれます。
  • 二重リンク リストには XNUMX つのデータ フィールドと XNUMX つのリンクがあります。 XNUMX つは前のノード用で、もう XNUMX つは次のノード用です。
  • 二重リンクリストの複雑さ 最良の場合、平均的な場合
  • そして最悪の場合。