Работа алгоритма вставки Delete Get Random O (1) с множествами в Python - PullRequest
0 голосов
/ 25 августа 2018

Во время работы над проблемой я заметил кое-что странное в наборах. Может кто-нибудь объяснить логику этого. Всякий раз, когда я добавлял элемент для установки, он вставлял его в правильном порядке. Учитывая, что этот набор является неупорядоченной структурой данных, как это возможно?

Ниже приведен пример того, что я наблюдал:

>>> a = set([1,3,5])
>>> a
{1, 3, 5}
>>> a.pop()
1
>>> a
{3, 5}
>>> a.add(4)
>>> a
{3, 4, 5}
>>> a.add(6)
>>> a
{3, 4, 5, 6}
>>> a.add(2)
>>> a
{2, 3, 4, 5, 6}
>>>

Как я наткнулся на это наблюдение :

Я пытался решить проблему, в которой мне нужно спроектировать структуру данных, которая выполняет вставку, удаление и getRandom за O (1) времени. Подробности можно найти на https://leetcode.com/problems/insert-delete-getrandom-o1-duplicates-allowed/description/

Моя основная идея - использовать HashMap of number -> List, который хранит список индексов всех ключей, вставленных в список. Наряду с этим я поддерживаю список (V) значений. List и HashMap допускают постоянную вставку. HashMap позволит постоянно удалять значения. Мы можем добиться постоянного удаления значений из списка, если поменяем местами удаляемый элемент с последним элементом списка, а затем удалим последний элемент.

Базовый вариант использования:

  1. Для вставки значения 1. 1 добавляется в Список (V). Этот индекс хранится в HashMap с ключом 1.
  2. Для getRandom элемент выбирается случайным образом из List (V)
  3. Чтобы удалить значение 1, последний индекс 1 извлекается из HashMap, затем этот индекс заменяется новым элементом. Затем последний индекс замененного элемента обновляется в HashMap, а последний элемент в List (V) удаляется.

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

Но что интересно, когда я использую набор в HashMap вместо списка, мне не нужно заботиться об этом. Набор каким-то образом вставляет элемент в правильное положение. Я знаю, что набор должен быть неупорядоченным набором данных, тогда почему это работает. Может кто-нибудь объяснить это поведение наборов?

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

    import random
    import bisect
    class RandomizedCollection:

        def __init__(self):
            """
            Initialize your data structure here.
            """
            self.myMap = {}
            self.stack = []

        def insert(self, val):
            """
            Inserts a value to the collection. Returns true if the collection did not already contain the specified element.
            :type val: int
            :rtype: bool
            """
            #print("Inserting",val)
            #print(self.myMap,self.stack)
            tmp = self.myMap.get(val,[])
            if len(tmp) == 0:
                self.stack.append(val)
                tmp.append(len(self.stack)-1)
                self.myMap[val] = tmp
                return True
            else:
                self.stack.append(val)
                tmp.append(len(self.stack)-1)
                self.myMap[val] = tmp
                return False

        def remove(self, val):
            """
            Removes a value from the collection. Returns true if the collection contained the specified element.
            :type val: int
            :rtype: bool
            """
            #print("Removing",val)
            #print(self.myMap,self.stack)
            tmp = self.myMap.get(val,[])
            if len(tmp) > 0:
                if self.stack[-1] != val:
                    idx_to_remove = tmp.pop()
                    last_val = self.stack[-1]
                    #print(idx_to_remove, last_val)

                    self.myMap[last_val].pop() ## removes the last index
                    insert_pos = bisect.bisect_left(self.myMap[last_val],idx_to_remove)
                    self.myMap[last_val].insert(insert_pos,idx_to_remove)

                    self.stack[idx_to_remove],self.stack[-1] = self.stack[-1],self.stack[idx_to_remove]
                    self.stack.pop()
                else:
                    self.stack.pop()
                    tmp.pop()
                return True
            else:
                return False


        def getRandom(self):
            """
            Get a random element from the collection.
            :rtype: int
            """
            return random.choice(self.stack)

Ниже приведен аналогичный код с использованием наборов. Я не уверен, почему это работает.

from collections import defaultdict
import random

class RandomizedCollection:

    def __init__(self):
        """
        Initialize your data structure here.
        """
        self.nums = []
        self.num_map = defaultdict(set)


    def insert(self, val):
        """
        Inserts a value to the collection. Returns true if the collection did not already contain the specified element.
        :type val: int
        :rtype: bool
        """
        self.nums.append(val)
        self.num_map[val].add(len(self.nums) - 1)
        return True


    def remove(self, val):
        """
        Removes a value from the collection. Returns true if the collection contained the specified element.
        :type val: int
        :rtype: bool
        """
        if len(self.num_map[val]) == 0:
            return False
        index = self.num_map[val].pop()
        last_index = len(self.nums) - 1
        if not (index == last_index):
            last_val = self.nums[last_index]
            self.nums[index] = last_val
            self.num_map[last_val].remove(last_index)
            self.num_map[last_val].add(index)
        self.nums.pop()
        return True


    def getRandom(self):
        """
        Get a random element from the collection.
        :rtype: int
        """
        return self.nums[random.randint(0, len(self.nums) - 1)]

1 Ответ

0 голосов
/ 28 августа 2018

Наборы в python не имеют никаких гарантий относительно заказа. На самом деле это обычно, когда порядок не поддерживается. Например, в моих реализациях Python-2.7 и 3.5.2:

>>> a = set([3,10,19])
>>> a
set([19, 10, 3])
>>> a.add(1)
>>> a
set([19, 1, 10, 3])
>>> a.pop()
19
>>> a.pop()
1
>>> a
set([10, 3])

Порядок иногда поддерживается с set, потому что именно так работают хеш-таблицы. Обычно set начинается как хеш-таблица с size=a*len(hash_table) сегментами. Элемент value вставляется в номер ковша value % size. Для маленьких и плотных целых чисел value < size. Это означает, что в таких случаях value вставляется в номер корзины value, который является сортировкой порядка значений.

Это показывает, что в некоторых случаях неудивительно, когда set поддерживает отсортированный порядок, но это не так. Дело в том, почему класс RandomizedCollection работает.


На самом деле обе реализации RandomizedCollection имеют одинаковую концептуальную структуру данных. self.stack варианта на основе списка читается и обновляется точно так же, как self.nums варианта на основе set. Они оба поддерживают плоский список всех элементов в объекте RandomizedCollection.

Реализация на основе списка использует тот факт, что self.myMap[val] является отсортированным списком только в remove() at:

last_val = self.stack[-1]
self.myMap[last_val].pop() ## removes the last index

Поскольку self.myMap[last_val] является отсортированным списком индексов, его последний элемент должен быть последним last_val элементом в self.stack. Поскольку last_val было взято с конца self.stack, то последний элемент self.myMap[last_val] гарантированно указывает на len(self.stack)-1). Это означает, что выталкивание последних элементов как self.stack, так и self.myMap[last_val] сохранит структуру данных согласованной. За исключением двух вышеупомянутых строк, создание self.myMap[val] отсортированного списка приводит только к затратам. Поиск по индексу в списке O(log K) и вставка O(K).

Решение на основе set работает в другом порядке. Вместо удаления индекса, который указывает на конец стека в O (1):

self.myMap[last_val].pop() # (Sorted list variant)

Стирает тот же элемент, используя возможности O (1) set:

self.num_map[last_val].remove(last_index)

Эффект тот же. В обоих случаях карта больше не указывает на последний элемент в стеке. Заказывать set не нужно, только чтобы можно было удалить элементы в O (1). В конце концов, последний элемент в стеке будет удален простым self.stack.pop() или self.nums.pop().

Иногда это был не последний элемент, который нужно было удалить, но код все равно удалил его. Идея состоит в том, чтобы затем удалить правильный элемент и поместить «неправильно» удаленный элемент вместо него. Решение с отсортированным списком усердно работает в O (K), чтобы заново вставить удаленный элемент:

insert_pos = bisect.bisect_left(self.myMap[last_val],idx_to_remove)
self.myMap[last_val].insert(insert_pos,idx_to_remove)

В случае set это довольно просто, так как заказ не требуется:

self.num_map[last_val].add(index)

И, наконец, массив, который содержит фактические значения (self.nums и self.stack), обновляется таким образом, чтобы последний элемент был окончательно удален и снова вставлен вместо элемента, который должен был быть удален в первую очередь.


Подводя итог, обе структуры данных эквивалентны. В одном набор индексов поддерживается как отсортированный список, а в другом - как set. Код в варианте отсортированного списка иногда использует тот факт, что список отсортирован, чтобы удалить самый большой элемент в O (1). Однако для случая set такая оптимизация не требуется, поскольку для удаления любого элемента используется O (1), и вместо pop(). можно использовать remove().
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...