Сложность времени удаления двусвязного элемента списка? - PullRequest
11 голосов
/ 28 мая 2011

Многое из того, что я читаю, говорит, что удаление внутреннего элемента в двусвязном списке (DLL) - это O(1); но почему это так?

Я понимаю, почему это O(n) для SLL; Пройдите по списку O(n) и удалите O(1), но вам все равно не нужно просматривать список в DLL, чтобы найти элемент?

Ответы [ 4 ]

15 голосов
/ 28 мая 2011

Для двусвязного списка это постоянное время для удаления элемента , когда вы знаете, где он находится.

Для односвязного списка это постоянное время для удаления элемента, когда вы знаете, где он и его предшественник.

Поскольку эта ссылка, на которую вы указываете, показывает удаление односвязного списка как O(n) и двусвязное как O(1), наверняка вы уже знаете, где находится элемент, который хотите удалить, но не что-нибудь еще.

В этом случае для двусвязного списка вы можете просто использовать указатели prev и next, чтобы удалить его, давая вам O(1). Игнорирование крайних случаев, когда вы находитесь в голове или хвосте, это означает что-то вроде:

corpse->prev->next = corpse->next
corpse->next->prev = corpse->prev
free (corpse)

Однако в односвязном списке, в котором вы знаете только узел, который хотите удалить, вы не можете использовать corpse->prev для получения предыдущего, потому что - это нет prev ссылки.

Вместо этого вы должны найти предыдущий элемент, пройдя по списку из головы, ища тот, у которого есть next элемента, который вы хотите удалить. Это займет O(n), после чего еще раз O(1) для фактического удаления, например (опять же, игнорируя крайние случаи для простоты):

lefty = head
while lefty->next != corpse:
    lefty = lefty-> next
lefty->next = corpse->next
free (corpse)

Вот , почему эти две сложности отличаются в этой статье.


Кроме того, в односвязном списке есть оптимизации, которые могут сделать удаление O(n) (удаление фактически равно O (1), как только вы нашли элемент, который хотите удалить, и предыдущий элемент) , В терминах кода это выглядит примерно так:

# Delete a node, returns true if found, otherwise false.

def deleteItem(key):
    # Special cases (empty list and deleting head).

    if head == null: return false
    if head.data == key:
        curr = head
        head = head.next
        free curr
        return true

    # Search non-head part of list (so prev always exists).

    prev = head
    curr = head.next
    while curr != null:
        if curr.data == key:
            # Found it so delete (using prev).

            prev.next = curr.next
            free curr
            return true

        # Advance to next item.

        prev = curr
        curr = curr.next

    # Not found, so fail.

    return false
4 голосов
/ 28 мая 2011

Как указано, где ваша ссылка указывает на:

Стоимость изменения внутреннего элемента основана на наличии уже имеющего на него указателя. Если вам нужно сначала найти элемент, также берется стоимость извлечения элемента.

Таким образом, для DLL и SLL линейный поиск равен O (n), а удаление по указателю - O (1).

2 голосов
/ 28 мая 2011

Сложность удаления в DLL составляет O(1). Также может быть O(1) в SLL, если указан указатель на предыдущий элемент, а не на сам элемент.

Эта сложность равна , если вы знаете, где находится элемент .

т.е. подпись операции похожа на remove_element(list* l, link* e) Поиск элемента O(n) в обоих случаях.

1 голос
/ 03 декабря 2015

@ Матуку: Вы правы.

Я смиренно не согласен с большинством ответов, пытаясь объяснить, как операция удаления для DLL является O (1).Это не так.

Позвольте мне объяснить.

Почему мы рассматриваем сценарий, согласно которому у нас «будет» указатель на удаляемый узел?Списки LinkedLists (Singly / Doubly) обходятся линейно, это их определение.У них есть указатели только на голову / хвост.Как мы можем внезапно получить указатель на какой-то узел между ними?Это противоречит цели этой структуры данных.И если исходить из этого предположения, если у меня есть список DLL, скажем, 1 миллион узлов, то мне также нужно поддерживать 1 миллион указателей (назовем их указателями доступа), указывающих на каждый из этих узлов, чтобы я мог удалить их в O (1)?Итак, как мне хранить эти 1 миллион указателей доступа?И как мне узнать, какой указатель доступа указывает на правильные данные / узел, который я хочу удалить?

Можем ли мы привести пример из реальной жизни, где у нас есть «указатель» на данные, которые должны быть удалены в 100% случаев?

И если вы знаете точное местоположение / указатель / ссылку / на удаляемый узел, зачем вообще использовать LinkedList?Просто используйте массив!Для этого нужны массивы - прямой доступ к тому, что вы хотите!

Предполагая, что у вас есть прямой доступ к любому узлу, который вы хотите в DLL, идет вразрез с самой идеей LinkedList как концептуальной структуры данных.Так что я согласен с ОП, он прав.Я буду придерживаться этого - в двух словах LinkedLists не может иметь O (1) для удаления любого узла.Вам все еще нужно начинать с головы или хвоста, что приводит к O (n).

" Если ", у нас есть указатель на удаляемый узел, скажем, X, тогда, конечно, это O (1), потому что у нас есть указатели на следующий и предыдущий узлы, которые мы можем удалить XНо это большое, если воображаемо, а не реально.

Мы не можем поиграть с определением священной структуры данных, называемой LinkedLists, для некоторых странных предположений, которые у нас могут быть время от времени.

...