освободить память узла в связанном списке

Я работаю над односвязным списком, и я хотел попробовать другой подход для моего алгоритма функции удаления для практики:

template<class T>
inline void LinkedList<T>::remove(T v)
{
    Node<T>** indirect = &head;
    while (*indirect && (*indirect)->value != v) {
        indirect = &((*indirect)->next);
    }
    *indirect = ((*indirect)->next);
}

все мои узлы создаются с помощью new. Последняя строка просто меняет указатель на следующий узел. Но я должен освободить память, на которую указывал *indirect ранее, верно?

Я изменил свой код, чтобы освободить память узла для базового указателя узла. Также я отслеживаю предыдущий узел, чтобы сохранить хвостовой указатель:

template<class T>
inline void LinkedList<T>::remove(T v)
{
    Node<T>** indirect = &head;
    Node<T>* prev = nullptr;
    while (*indirect && (*indirect)->value != v) {
        prev = *indirect;
        indirect = &((*indirect)->next);
    }
    if (*indirect) {
        if (*indirect == tail) {
            tail = prev;
        }
        Node<T>* tmp = *indirect;
        *indirect = (*indirect)->next;
        delete tmp;
    }
}

person kimsay    schedule 26.02.2017    source источник
comment
На каждый new должен быть delete. На каждые new Type[n] должно быть delete[]. И как только вы освоите это, позвольте умным указателям делать всю работу за вас и ежедневно старайтесь никогда не заниматься управлением памятью вручную. Тем не менее, вам не хватает delete, и вам не хватает проверки на нуль перед окончательным косвенным обращением (случай, когда цикл исчерпан, и вы никогда не найдете значение).   -  person WhozCraig    schedule 26.02.2017


Ответы (1)


Да, если узел был выделен с помощью new, его следует удалить с помощью delete.

Тем не менее, вы пропускаете удаление узла и, что более важно, проверку на нуль перед окончательным разыменованием. Ваш фактический цикл перечисления выглядит солидно (приятно; указатели на указатели не являются тривиальными для некоторых людей).

template<class T>
inline void LinkedList<T>::remove(T v)
{
    Node<T>** indirect = &head;
    while (*indirect && (*indirect)->value != v)
        indirect = &((*indirect)->next);

    if (*indirect)
    {
        Node<T> *tmp = *indirect;
        *indirect = tmp->next;
        delete tmp;
    }
}

Предупреждение: убедитесь, что ваш деструктор Node не делает глупостей, например, не пытается удалить связанную цепочку.


Управляемый список с указателями head и tail

Если в вашем списке есть указатели head и tail для облегчения вставки O(1) на обоих концах, алгоритм будет немного сложнее, но сложность останется прежней. Вы должны поддерживать указатель prev во время цикла перечисления, первоначально оцененный в nullptr, и проходить его на один узел назад от основной итерации:

template<class T>
inline void LinkedList<T>::remove(T v)
{
    Node<T>** indirect = &head, *prev = nullptr;
    while (*indirect && (*indirect)->value != v)
    {
        prev = *indirect;
        indirect = &((*indirect)->next);
    }

    if (*indirect)
    {
        Node<T> *tmp = *indirect;

        // if the victim of the delete is also the tail
        //  then set it to the prior node pointer, which
        //  may be null if this was a single node list and
        //  both head and tail refer to the same node.
        if (tail == tmp)
            tail = prev;

        *indirect = tmp->next;
        delete tmp;
    }
}

Осталось только проверить машину выше со следующими условиями. Что происходит, когда...

  • В списке нет подходящих узлов?
  • В списке есть один узел, который соответствует?
  • Есть несколько узлов, и первый узел совпадает?
  • Есть несколько узлов, и последний узел совпадает?
  • Есть несколько узлов, и какой-то средний узел совпадает?

Ответы на каждый из них стоят умственного упражнения и, возможно, бумаги, ручки/карандаша, нескольких коробок и нескольких стрелок. Пройдитесь по приведенному выше коду в каждом условии и посмотрите, что произойдет. Если все кажется правильным, это, вероятно, прочно. Конечно, написание стека модульных тестов, формирующих списки для каждого из приведенных выше условий, и тестирование функции — это всегда хорошая идея.

Во всяком случае, это все.

person WhozCraig    schedule 26.02.2017
comment
Я использую хвостовой указатель. Мне нужно было бы проверить, был ли узел, который я только что удалил, хвостом, чтобы я мог сбросить указатель хвоста в моем классе LinkedList. Есть ли способ избежать этого (может быть, вообще не использовать хвостовой указатель...? Будут ли добавленные узлы давать O (n) вместо O (1)... - person kimsay; 27.02.2017
comment
Если вы также собираетесь управлять хвостовым указателем, сбросить его немного сложнее. Вы должны поддерживать указатель prev в этом коде, который ссылается на предыдущий узел. Существуют специальные сокращения, которые можно использовать, если вы полностью контролируете позицию члена (т. е. если вы знаете, что next является первым членом структуры Node, которая в вашем случае в настоящее время нет). Но самый простой способ — просто поддерживать файл prev. Я обновлю ответ, чтобы указать, как это сделать. Держите хвост-указатель. что время конечной вставки O(1) слишком выгодно, чтобы его терять. - person WhozCraig; 27.02.2017