Почему удаление элементов хеш-таблицы с использованием двусвязного списка является O (1)? - PullRequest
18 голосов
/ 12 ноября 2011

В учебнике CLRS "Введение в алгоритм" есть такой параграф на стр. 258.

Мы можем удалить элемент за O (1) раз, если списки связаны дважды. (Обратите внимание, что CHAINED-HASH-DELETE принимает в качестве входных данных элемент x, а не его ключ k, так что нам не нужно сначала искать x. Если хэш-таблица поддерживает удаление, то ее связанный список должен быть дважды связан, так что мы можем быстро удалить элемент. Если бы списки были связаны только по одному, то для удаления элемента x нам сначала нужно было бы найти x в списке, чтобы мы могли обновить атрибут next предшественника x. односвязные списки, и удаление, и поиск будут иметь одинаковое время асимптотики).

Что меня озадачивает, так это большие круглые скобки, я не смог понять его логику С двусвязным списком еще нужно найти x, чтобы удалить его, чем он отличается от односвязного списка? Пожалуйста, помогите мне понять это!

Ответы [ 8 ]

28 голосов
/ 12 ноября 2011

Проблема, представленная здесь: учтите, что вы просматриваете определенный элемент хеш-таблицы. Как дорого это удалить?

Предположим, у вас есть простой связанный список:

v ----> w ----> x ----> y ----> z
                |
            you're here

Теперь, если вы удалите x, вам нужно подключить w к y, чтобы сохранить ваш список связанным. Вам нужно получить доступ к w и сказать ему, чтобы он указывал на y (вы хотите иметь w ----> y). Но вы не можете получить доступ к w с x, потому что он просто связан! Таким образом, вы должны просмотреть весь свой список, чтобы найти w в операциях O (n), а затем указать ему ссылку на y. Это плохо.

Тогда предположим, что вы двусвязны:

v <---> w <---> x <---> y <---> z
                |
            you're here

Круто, вы можете получить доступ к w и y отсюда, так что вы можете соединить два (w <---> y) в операции O (1)!

2 голосов
/ 12 ноября 2011

Мне кажется, что это часть хеш-таблицы в основном красная сельдь.Реальный вопрос таков: «Можем ли мы удалить текущий элемент из связанного списка за постоянное время, и если да, то как?»

Ответ таков: это немного сложно, но в действительности да, мы можем -по крайней мере, обычно.Мы не (обычно) должны пройти весь связанный список, чтобы найти предыдущий элемент.Вместо этого мы можем поменять местами данные между текущим элементом и следующим элементом, а затем удалить следующий элемент.

Единственное исключение из этого - когда / если нам нужно / мы хотим удалить last пункт в списке.В этом случае у нет следующего элемента для обмена.Если вам действительно нужно это сделать, то нет реального способа избежать поиска предыдущего элемента.Тем не менее, есть способы, которые обычно работают, чтобы избежать этого - один - завершить список с помощью сторожа вместо нулевого указателя.В этом случае, поскольку мы никогда не удаляем узел со значением Sentinel, нам никогда не приходится иметь дело с удалением последнего элемента в списке.Это оставляет нас с относительно простым кодом, примерно таким:

template <class key, class data>
struct node {
    key k;
    data d;
    node *next;
};

void delete_node(node *item) {
    node *temp = item->next;
    swap(item->key, temp->key);
    swap(item->data, temp->data);
    item ->next = temp->next;
    delete temp;
}
2 голосов
/ 12 ноября 2011

В целом вы правы - алгоритм, который вы опубликовали, принимает в качестве входных данных элемент , а не только его ключ:

Обратите внимание, что CHAINED-HASH-DELETE принимает в качестве входных данных элемент x, а не его ключ k, так что нам не нужно сначала искать x .

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

1 голос
/ 19 июля 2012

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

0 голосов
/ 06 января 2019

Просматривая учебник, я также запутался в той же теме (, является ли "x" указателем на элемент или сам элемент ) и затем в конце концов наткнулся на этот вопрос. Но после прохождения вышеупомянутого обсуждения и повторного обращения к учебнику, я думаю, что в книге «x» неявно предполагается как «узел», а его возможные атрибуты - «ключ», «следующий».

Некоторые строки образуют учебник ..

1) прикован-HASH-ВСТАВИТЬ (Т, х) вставьте x в начало списка T [h ( x.key )]

2) Если списки были связаны только по одному, то удалив элемент x, мы сначала должны найти x в списке T [h ( x.key )], чтобы мы может обновить атрибут next предшественника x.

Следовательно, мы можем предположить, что указатель на элемент задан , и я думаю, что Фезвез дал хорошее объяснение заданному вопросу.

0 голосов
/ 03 июня 2015

Точка зрения кодирования: для реализации этого можно использовать unordered_map в c ++.

unordered_map<value,node*>mp;

Где node* - указатель на структуру, хранящую ключевые, левые и правые указатели!

Как использовать:

Если у вас есть значение v и вы хотите удалить этот узел, просто сделайте:

  1. Получите доступ к этим узлам, например, mp[v].

  2. Теперь просто наведите указатель влево на узел справа.

И вуаля,все готово.

(Напомню, что в C ++ unordered_map требуется среднее значение O (1) для доступа к конкретному сохраненному значению.)

0 голосов
/ 13 марта 2013

Find(x) - это, как правило, O (1) для цепочки хеш-таблиц - неважно, используете ли вы односвязные списки или двусвязные списки. Они идентичны по производительности.

Если после запуска Find(x) вы решите, что хотите удалить возвращенный объект, вы обнаружите, что внутри хэш-таблицы, возможно, придется снова искать ваш объект. Обычно это будет O (1) и не имеет большого значения, но вы обнаружите, что вы удаляете очень много, вы можете сделать немного лучше. Вместо того, чтобы возвращать элемент пользователя напрямую, возвращайте указатель на базовый хеш-узел. Затем вы можете воспользоваться некоторыми внутренними структурами. Таким образом, если в этом случае вы выбрали двусвязный список в качестве способа выражения цепочки, то во время процесса удаления нет необходимости повторно вычислять хеш и снова искать коллекцию - вы можете пропустить этот шаг. У вас достаточно информации, чтобы выполнить удаление прямо с того места, где вы сидите. Дополнительные меры предосторожности должны быть предприняты, если отправляемый вами узел является головным узлом, поэтому целое число может использоваться для обозначения местоположения вашего узла в исходном массиве, если он является заголовком связанного списка.

Компромисс - это гарантированное пространство, занимаемое дополнительным указателем, по сравнению с возможным более быстрым удалением (и немного более сложным кодом). В современных настольных компьютерах пространство обычно очень дешевое, поэтому это может быть разумным компромиссом.

0 голосов
/ 12 ноября 2011

Учебник не прав. Первый член списка не имеет полезного «предыдущего» указателя, поэтому требуется дополнительный код, чтобы найти и отсоединить элемент, если он окажется первым в цепочке (обычно 30% элементов являются главой их цепочки, если N = M, (при отображении N элементов в M слотов; каждый слот имеет отдельную цепочку.))

EDIT:

Лучший способ, чем использовать обратную ссылку, - это использовать указатель на ссылку, которая указывает на нас (обычно это -> следующая ссылка предыдущего узла в списке)

struct node {
   struct node **pppar;
   struct node *nxt;
   ...
   }

удаление становится:

*(p->pppar) = p->nxt;

И приятной особенностью этого метода является то, что он одинаково хорошо работает для первого узла в цепочке (чей указатель pppar указывает на некоторый указатель, который не является частью узла.

ОБНОВЛЕНИЕ 2011-11-11

Поскольку люди не видят мою точку зрения, я попытаюсь проиллюстрировать. В качестве примера приведена хеш-таблица table (в основном массив указателей) и множество узлов one, two, three, один из которых должен быть удален.

    struct node *table[123];
    struct node *one, *two,*three;
    /* Initial situation: the chain {one,two,three}
    ** is located at slot#31 of the array */
    table[31] = one, one->next = two , two-next = three, three->next = NULL;
                one->prev = NULL, two->prev = one, three->prev = two;


    /* How to delete element one :*/
    if (one->prev == NULL) {
            table[31] = one->next;
            }
    else    {
            one->prev->next = one->next
            }
    if (one->next) {
            one->next->prev = one->prev;
            }

Теперь очевидно, что приведенный код - это O (1), но есть что-то противное: ему по-прежнему нужны array и индекс 31, поэтому в большинстве случаев узел «самодостаточный», и указатель на узел достаточен для удаления его из его цепочки, за исключением , когда он оказывается первым узлом в своей цепочке; тогда потребуется дополнительная информация, чтобы найти table и 31.

Далее рассмотрим эквивалентную структуру с указателем на указатель в качестве обратной ссылки.

    struct node {
            struct node *next;
            struct node **ppp;
            char payload[43];
            };

    struct node *table[123];
    struct node *one, *two,*three;
    /* Initial situation: the chain {one,two,three}
    ** is located at slot#31 of the array */
    table[31] = one, one-next = two , two-next = three, three->next = NULL;
                one->ppp = &table[31], two->ppp = &one->next, three->ppp = &two-next;

    /* How to delete element one */
    *(one->ppp) = one->next;
    if (one->next) one->next->ppp = one->ppp;

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

Часто в сценарии {prev, next} можно избежать особых случаев, добавив фиктивный узел в начале двойного связанного списка; Но это тоже должно быть выделено и инициализировано.

...