単一リンクリストで実装されたキューのエンキューおよびデキューの時間の複雑さはどれくらいですか?


7

リンクリストデータ構造で実装されたキューの時間の複雑さを理解しようとしています。私の本では、次の方法でO(1)時間でキューを実装できると述べています。

  • 奥に入れる
  • 先頭でデキュー

そしてそれはまた言います

要素を尾に追加するのは一定の時間ですが、新しい尾を見つける必要があるため、要素を尾から削除することはO(n)であることに注意してください

新しいノードをキューの先頭(エンキュー)に追加するには、ヘッドポインターがあるためO(1)がかかることを知っています。同様に、先頭のノードを削除すると(デキュー)、O(1)も使用されます。しかし、要素を後ろ(エンキュー)に追加するとO(1)がかかり、削除(デキュー)するとO(n)になる理由がわかりません。どちらの場合も(後ろでの追加/削除)はテールポインターを見つける必要があるため、トラバースする必要があるため、後ろでのキューへの登録がO(1)ではなくO(n)である場合、私にはより理にかなっています。リスト全体。


7
回答を受け取ったら、質問から重要な情報を削除しないでください。質問と回答のペアが将来他の人にも役立つようにしたいので、物事を完全に保つようにしてください。
離散トカゲ

回答:


14

エンキュー

新しい尾を見つけるためにリスト全体を走査する必要はありません。現在の尾が指す新しいノードを追加し、そのノードを新しい尾として設定するだけです。

疑似コード(head != nullおよびそれを想定tail != null):

function enqueue(value) {
    node = new Node(value)     // O(1)
    tail.next = node           // O(1)
    tail = node                // O(1)
    size++                     // O(1)
}

ここから、時間の複雑さはと結論付けることができます。O(1)


デキュー

デキューするには、現在のヘッドの次のノードを新しいヘッドとして設定し、古いヘッドの値を返すだけです。

:新しいヘッドがに設定されているnull場合、テールnullも同様に設定する必要があることを忘れないでください。

疑似コード(head != nullおよびそれを想定tail != null):

function dequeue() {
    value = head.value         // O(1)
    head = head.next           // O(1)
    size--                     // O(1)

    if (head == null) {        // O(1)
        tail = null            // O(1)
    }

    return value               // O(1)
}

これらすべての操作には時間の複雑さがあり、デキュー関数の時間複雑もになります。O(1)O(1)


探す

値の検索は、先頭から開始してすべてのアイテムをトラバースすることによって行われます。最悪のシナリオでは、キュー全体をトラバースする必要があるため、最悪の場合の時間はます。O(n)

たとえば、テールを削除する場合、時間の複雑さはます。これは、キューの新しいテールを見つける必要があり、テールが単一リンクリストの前の要素にアクセスできないため、キュー全体で新しいテールを検索する必要があるためです。O(n)

疑似コード(head != nullおよびそれを想定tail != null):

function removeLast() {

    // Edge case when there is only 1 element in the queue.
    if (head == tail) {                   // O(1)
        value = head.value                // O(1)
        head = null                       // O(1)
        tail = null                       // O(1)

        return value                      // O(1)
    }

    // Searching for the new tail.
    newTail = head                        // O(1)
    while (newTail.next != tail) {        // O(n)
        newTail = newTail.next            // O(1)
    }

    value = tail.value                    // O(1)
    newTail.next = null                   // O(1)
    tail = newTail                        // O(1)

    return tail                           // O(1)    
}

これから、時間の複雑さは確かにがわかります。O(n)


エンキューは空のリストを処理しません。一方、デキューはリストが空になったときに処理します。
ラチェットフリーク

1

本のコメントは明らかに、リンクリストの実装が2つのポインタを保持していることを前提としています。ポインタheadはリストの最初のノードを指し、それlastは最後のノードを指します。

この設計では、リストへの追加は単純です:

list.last.next = newNode;
list.last = newNode;

ただし、最後のノードを削除するには、2番目から最後のノードを見つける必要があるためnext、そのリンクをクリアして、lastポインターがそのノードを指すように変更できます。これは、ノードを見つけるためにリストをスキャンする必要がありnode.next == list.lastます。

おそらくlist.secondToLastポインターも考えられますが、最後のノードを削除するときに、前の3番目から最後のノードを指すように変更する必要があり、この問題がリスト全体に再帰するため、問題は解決しません。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.