ウィキペディアの記事 が言うように、単一のリンクリストの最後で削除するとO(1)時間になる理由を私は静かに理解していません。
単一のリンクリストはノードで構成されます。ノードには、ある種のデータと次のノードへの参照が含まれています。リンクリストの最後のノードの参照がnullです。
-------------- -------------- --------------
| data | ref | -> | data | ref | -> ... -> | data | ref |
-------------- -------------- --------------
私は確かにO(1)の最後のノードを削除することができます。ただし、その場合、削除された最後のノードへの参照がまだ含まれているため、新しく最後のノードである前のノードの参照をnullに設定しません。だから私は彼らが実行時間分析でそれを無視しているのだろうかと思っていましたか?それとも、参照は何も指していないので、それを変更する必要はないと考えられますか?それはnullと見なされますか?
それが無視されないのであれば、削除はO(n)であると私は主張するからです。新しく最後のノードに到達し、その参照もnullに設定するには、リスト全体を反復処理する必要があるためです。二重リンクリストでのみ、実際にはO(1)になります。
-編集-多分この視点はもう少し明確になります。しかし、「ノードの削除」は、ノード自体を正常に削除し、以前の参照をnullに設定したものと見なされます。
ウィキペディアの記事で、単一リンクリストの最後のエントリをO(1)時間内に削除できる」と書かれているのかわかりませんが、その情報はほとんどの場合正しくありません。リンクリスト内の個々のノードが与えられた場合、その新しいノードの周りにリストを再配線することにより、O(1)時間内に、ノードの後のノードを削除することが常に可能です。リンクリストの最後から2番目のノードへのポインタが与えられた場合、O(1)時間内にリストの最後の要素を削除できます。
ただし、ヘッドポインター以外の追加のポインターがリストにない場合は、リストの最後までスキャンせずにリストの最後の要素を削除することはできません。これには、Θ(n)時間が必要です。あなたが気づいた。最初にポインタを変更せずに最後のノードを削除するだけでは非常に悪い考えです。これを行うと、既存のリストに割り当て解除されたオブジェクトへのポインタが含まれるためです。
より一般的には、単一リンクリストで挿入または削除を行うためのコストは、挿入または削除するノードの直前にノードへのポインタがあると仮定すると、O(1)です。ただし、そのノードを見つけるには、追加の作業(最大Θ(n))が必要になる場合があります。
お役に立てれば!
[〜#〜] any [〜#〜]ノードの[〜#〜] any [〜#〜]の場所の追加/削除はO(1)です。コードは、ノードを追加/削除するために、固定費(ポインター計算とmalloc/freesがほとんどない)で遊ぶだけです。この算術コストは、特定の場合に固定されています。
ただし、目的のノードに到達(インデックス作成)するためのコストはO(n)です。
この記事では、追加/削除を複数のサブカテゴリ(中間、開始、終了で追加)にリストしているだけで、中間で追加するコストが開始/終了で追加するコストとは異なることを示しています(ただし、それぞれのコストは固定されています)。
単一のリンクリストで削除するノードへのポインタを指定した場合は、次のノードを削除するノードにコピーするだけで、このノードを一定時間で削除できます。
M pointer to node to delete
M.data=M.next.data
M.next=M.next.next
そうでなければ、ノードを検索する必要がある場合は、O(n)よりもうまくいくことはできません。
ぶら下がっているノードを修正するコストを含める場合でも、O(1)で、最後にセンチネルノードを使用して修正できます(このページでも説明されています)。
あなたの「空の」リストは単一の歩哨から始まります
Head -> [Sentinel]
いくつかのものを追加します
Head -> 1 -> 2 -> 3 -> [Sentinel]
次に、3であったノードを無効としてマークし、古いセンチネルへのリンクを削除して、そのためのメモリを解放することにより、テール(3)を削除します。
Head -> 1 -> 2 -> 3 -> [Sentinel]
Head -> 1 -> 2 -> [Sentinel] -> [Sentinel]
Head -> 1 -> 2 -> [Sentinel]
将来の参考のために、いくつかの調査の結果、この質問に答えて提供された議論のいずれも関連性がないことがわかったと言わなければなりません。答えは、スタックの一番上を(テールではなく)リンクリストの先頭にすることを単純に決定することです。これにより、プッシュルーチンにわずかな変更が導入されますが、ポップとプッシュは両方ともo(1)のままになります。
たとえば、最後の前(「最後から2番目」)および削除時に要素へのポインタを設定できます。1。この「最後から2番目」の要素の* nextを削除します。 2.この「最後から2番目」を* NULLの隣に設定します
O(1)は単に「一定のコスト」を意味します。 1回の操作という意味ではありません。これは、他のパラメーターの変更(リストサイズなど)に関係なく、Cが固定された「最大でC」の操作を意味します。実際、時々混乱するビッグオーの世界では:O(1) == O(22)。
対照的に、リスト全体を削除すると、リストのサイズ(n)によってコストが変化するため、O(n)コストがかかります。
はい、「前の要素」へのポインタを維持していなくても、O(1)時間でこれを行うことができます。
このリストがあるとします。ここで、「head」と「tail」は静的ポインターであり、「End」は終了としてマークされたノードです。通常どおり「next == NULL」を使用できますが、この場合、値を無視する必要があります。
head -> 1 -> 2 -> 3 -> 4 -> 5 -> End <- tail
これで、ノード3へのポインターが与えられましたが、前のノードは与えられていません。ヘッドポインタとテールポインタもあります。 SingleLinkedListクラスを想定した、Python風のコードを次に示します。
class SingleLinkedList:
# ... other methods
def del_node(self, node): # We "trust" that we're only ever given nodes that are in this list.
if node is self.head:
# Simple case of deleting the start
self.head = node.next
# Free up 'node', which is automatic in python
Elif node.next is self.tail
# Simple case of deleting the end. This node becomes the sentinel
node.value = None
node.next = None
# Free up 'self.tail'
self.tail = node
else:
# Other cases, we move the head's value here, and then act
# as if we're deleting the head.
node.value = self.head.value
self.head = self.head.next
new_head = self.head.next
# Free up 'self.head'
self.head = new_head
残念ながら、これによりリストが並べ替えられます。and値が移動しますが、アプリケーションにとって問題がない場合もあります。