Pythonの双方向連結リストの実装方法を解説する

スポンサーリンク

2重リンクリストは、リストを格納するためのデータ構造です。

これはリンクリストと非常によく似ていますが、いくつかの追加機能があります。

この記事では、2重連結リストとは何かを説明し、pythonで実装して、その出力を見ていきます。

スポンサーリンク

Pre-Requisite (事前要件) リンクリスト

二重リンクリストに移る前に、リンクリストとは何かについて説明する必要があります。

リンクリストとは、その名前が示すように、リスト項目が他のリスト項目と特定の方法でリンクされているリストのことです。

正確なリンクのされ方は、リンクリストの種類によって異なります。

最も一般的なリンクリストは「単一リンクリスト」または単に「リンクリスト」で、この場合、各項目はリスト内の次の項目とリンクしています。

つまり、10番目の項目にアクセスするためには、まず9番目の項目にアクセスする必要があるのです。

そして、10番目の項目にアクセスすると、10番目の項目が持っているリンクを通じて11番目の項目にアクセスできるようになるのです。

リンクリストの各項目はノードと呼ばれる。

単一リンクリストでは、各ノードは2つの部分を持っています。

最初の部分にはノードのデータが格納され、2番目の部分には次のノードへのリンクが格納される。

次に、2重リンクリストについて見てみましょう。

ダブルリンクリストとは?

二重連結リストもノードがリンクで結ばれているリストですが、この場合、各ノードは前の項目だけでなく次の項目にもリンクしています。

つまり、10番目のノードにアクセスしたら、9番目のノード、11番目のノードにアクセスでき、特定のノードにアクセスするためには、その前のノードか後のノードのどちらかにアクセスする必要があるのです。

その方法は、各ノードが3つの部分を持っているということです。

最初の部分は格納される実際のデータ、2番目の部分はリスト内の前のノードへのリンク、3番目の部分はリスト内の次のノードへのリンクです。

リンクが2つあることのメリットは、追加や削除などの操作が、単一リンクリストよりもはるかに簡単で高速に行えることだ。

二重リンクリストをイメージすると、次のような感じです。

class Node:
    def __init__(self, data = None):
        self.data = data
        self.next = None
        self.previous = None

上の例では、リンクリストの中に4つのアイテム/ノードがあることがわかります。

各ノードは何らかのデータまたは内容を持ち、各ノードはリストの次のノードと前のノードを指し示し/リンクしています。

最初のノードの前のリンクと最後のノードの次のリンクは何も指していないので、(pythonの場合)Noneを格納します。

始めに、リストの先頭はリストの最初のノードを指し、リストの末尾はリストの最後のノードを指します。

つまり、最初と最後のノードは、それらを通して直接アクセスできるのです。

他のノードに行くには、headかtailを経由して、それぞれ次のノード、前のノードにアクセスし、目的のノードに到達することになる。

Pythonで二重リンクリストを実装する

2重リンクリストの作成は非常に簡単です。

ノード用のクラスと、最初のクラスで作成されたノードを使用してリンクリストを作成するクラスの、2つのクラスを作成する必要があります。

1. クラス ノード

ノード・クラスの場合、クラスには3つのメンバーしかありません。

データを格納するためのもの、次のノードを格納するためのもの、そして前のノードを格納するためのものです。

クラス定義は次のようになります。

class DLL:
    def __init__(self):
        self.head = None
        self.tail = None
        self.count = 0
         
    def __repr__(self):
        string = ""
         
        if(self.head == None):
            string += "Doubly Linked List Empty"
            return string
         
        string += f"Doubly Linked List:
{self.head.data}"
       
        start = self.head.next
        while(start != None):
            string += f" -> {start.data}"
            start = start.next
        return string
         
    def append(self, data):
        if self.head == None:
            self.head = Node(data)
            self.tail = self.head
            self.count += 1
            return
         
        self.tail.next = Node(data)
        self.tail.next.previous = self.tail
        self.tail = self.tail.next
        self.count += 1
         
    def insert(self, data, index):
        if (index > self.count) | (index < 0):
            raise ValueError(f"Index out of range: {index}, size: {self.count}")
             
        if(index == self.count):
            self.append(data)
            return
             
        if(index == 0):
            self.head.previous = Node(data)
            self.head.previous.next = self.head
            self.head = self.head.previous
            self.count += 1
            return
         
        start = self.head
        for _ in range(index):
            start = start.next
        start.previous.next = Node(data)
        start.previous.next.previous = start.previous
        start.previous.next.next = start
        start.previous = start.previous.next
        self.count += 1
        return
     
    def remove(self, index):
        if (index >= self.count) | (index < 0):
            raise ValueError(f"Index out of range: {index}, size: {self.count}")
         
        if index == 0:
            self.head = self.head.next
            self.head.previous = None
            self.count -= 1
            return
             
        if index == (self.count - 1):
            self.tail = self.tail.previous
            self.tail.next = None
            self.count -= 1
            return
         
        start = self.head
        for i in range(index):
            start = start.next
        start.previous.next, start.next.previous = start.next, start.previous
        self.count -= 1
        return
     
    def index(self, data):
        start = self.head
        for i in range(self.count):
            if(start.data == data):
                return i
            start = start.next
        return None
     
    def size(self):
        return self.count
     
    def display(self):
        print(self)

ここで、最初はノードは他のノードを指さないし、どのように作られたかによってデータを持つことも持たないこともある。

2. クラス ダブリーリンクリスト

このクラスはノードクラスよりもはるかに多くのものを含んでいます。

先頭ノード、末尾ノード、リスト内のアイテム数、そして新しいノードの挿入、既存のノードの削除、既存のノードの検索、リストの表示など必要なメソッドが含まれます。

クラスは次のようなものになります。

Doubly Linked List Repr 2
Doubly Linked List Representation

上記のクラスは多くのメンバを持つが,一つ一つ説明していこう.

3. 3. __init__ メソッド

コンストラクタでは、3つの変数を宣言しています。

headtailNoneで初期化されます。

つまり、リストには最初から変数が存在しないので、count0` で初期化されます。

4. 4. __repr__ メソッド

このメソッドはリンクリストを表示するための文字列を返します。

つまり、リストが空の場合はそれを表示し、リストが空でない場合は各ノードのデータを1つずつ表示します。

5. 5. append と insert メソッド

この実装では、指定した位置にノードを追加したり挿入したりすることができます

追加する場合は、リストが空であるかどうかをチェックします。

空であれば、 headtail が新しいノードを指すようにします。

そうでない場合は、最後のノードの next が新しいノードを指すようにし、次に新しいノードの previous が最後のノードを指すようにし、最後に tail が新しいノードを指すようにします。

指定された位置に挿入する場合、その位置が末尾であれば、単にノードを追加し、そうでなければ、その位置が先頭であれば、最初のノードの previous ポイントを新しいノードに、次に新しいノードの next ポイントを最初のノードに、そして最後に head ポイントを新しいノードに作成します。

もし指定された位置が真ん中であれば、まずその位置に到達し、その位置より前のノードの next を新しいノードに向け、次に新しいノードの previous をその位置より前のノードに向け、次に新しいノードの next をその位置のノードに向け、最後にその位置のノードの previous を新しいノードに向けます。

また、与えられたインデックスが有効かどうかをチェックし、有効でない場合は ValueError を発生させます。

また、挿入が成功するたびに count をインクリメントします。

6. remove` メソッド

アイテムを削除するには、そのアイテムをどこから削除するかを指定する必要があります。

指定したインデックスが範囲外の場合は、 ValueError を発生させます。

インデックスが 0 の場合、最初のアイテムを削除します。

そのためには、head が 2 番目のノードを指すようにします。

もし head が NULL の場合は、リストが空になったことを意味します。

そうでない場合は、新しい headprevious ストアを None にする必要があります。

同様に、インデックスがリストのサイズよりも 1 つ小さい場合は、最後のアイテムを削除する必要があることを意味します。

したがって、 tail を 2 番目のノードを指すようにし、新しい tailnext ストアを None にします。

インデックスが中間のどこかにある場合は、まずその位置に到達し、その位置より前のノードの next をその位置より後のノードを指すようにし、最後にその位置より後のノードの previous をその位置より前のノードを指すようにします。

removalでは、リストからノードをアクセス不能にするだけで、実際のメモリからの削除処理はPythonのガベージコレクションモジュールに任されています。

7. index,size,display` メソッドです。

indexメソッドは、リスト内の項目を検索するために使用します。

リストサイズに基づいてリスト全体を調べ、ターゲットが見つかればインデックスを返します。

見つからなかった場合は、None`を返します。

sizeメソッドは、リストに含まれる項目の数を格納するクラスのcount` メンバの値を返します。

そして、display メソッドはオブジェクトを表示し、__repr__ メソッドを呼び出して、返された文字列を画面に結果を出力すると、以下の様になります。

出力

このクラスに対して複数のステートメントを実行した結果、以下のような出力が得られました。

Dll Init Append And Insert Example
Example illustrating initialization, append method, and insert method.
Dll Remove Index Size And Display Example
Example illustrating remove method, index method, size method, and display method.

まとめ

この記事では、二重リンクリストについて学習し、Pythonで実装しました。

まず、単一リンクリストの動作を理解し、次に二重リンクリストがどのように異なるかを議論しました。

Pythonでデータ構造のコードを書き、それぞれのメソッドがどのように動作しているかを議論し、最後にコードの出力を確認しました。

次回のチュートリアルでお会いしましょう。

タイトルとURLをコピーしました