Какая структура данных поддерживает эффективное удаление и произвольный доступ?

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

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

Насколько мне известно, связанный список идеально подходит для удаления, но доступ к его элементам может занять O(n) времени. С другой стороны, простой массив (например, vector в C++) имеет свойство произвольного доступа, но удаление элемента из такой структуры имеет сложность O(n).

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

Заранее спасибо!


person kMaster    schedule 07.03.2013    source источник
comment
Набор также может быть альтернативой, если реализация позволяет получить случайный элемент (я думаю, то же требование относится и к хеш-таблице, которая тоже не всегда делает это)   -  person Joachim Isaksson    schedule 07.03.2013
comment
К сожалению, реализация set в C++ не допускает произвольного доступа :(   -  person kMaster    schedule 07.03.2013
comment
@ManiBastaniParizi Тогда вы можете пометить свой вопрос как C++ :)   -  person Joachim Isaksson    schedule 07.03.2013


Ответы (2)


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

Вы предложили:

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

Если вы действительно можете установить разумное максимальное количество записей, то я бы предложил вам предварительно выделить массив (например, используя std::array, если максимум известен во время компиляции, или std::vector в противном случае) с этим количеством записей, установить количество элементов (чтобы подсчитать количество элементов, хранящихся в данный момент), и выполните следующие действия:

  1. Изначально вы устанавливаете счетчик на 0
  2. Когда вы вставляете элемент, вы добавляете его в конец и увеличиваете количество
  3. Когда вы удаляете элемент, вы заменяете его последним элементом и уменьшаете счетчик
  4. Для произвольного доступа (в том смысле, в каком вы его описали, т. е. буквально случайного выбора элемента) вы определяете случайное число между 0 и подсчетом и выбираете этот элемент

Единственная деталь, которую я изменил, — это удаление элемента, которое я предлагаю вам реализовать как переключить позиции с последним элементом.

Возможная реализация:

#include <vector>
#include <utility>
#include <iostream>

template <typename Elem>
class randomaccesstable
{
public:
  randomaccesstable(std::size_t initial_size)
   : data_(initial_size) , count_(0)
  { }

  randomaccesstable &push_back(const Elem &elem)
  {
    if (count_ < data_.size())
      data_[count_++] = elem;
    else {
      data_.push_back(elem);
      ++count_;
    }
    return *this;
  }

  randomaccesstable &remove(const std::size_t index)
  {
    if (index < count_)
    {
      std::swap(data_[index],data_[count_-1]);
      --count_;
    }
    return *this;
  }

  const Elem &operator[](const std::size_t index) const
  { return data_[index]; }

  Elem &operator[](const std::size_t index)
  { return data_[index]; }

  std::size_t size() const
  { return count_; }

private:
  std::vector<Elem>  data_;
  std::size_t        count_;
};

int main()
{
  randomaccesstable<int> table(10);
  table.push_back(3);
  table.push_back(12);
  table.push_back(2);

  for (std::size_t i = 0 ; i < table.size() ; ++i)
    std::cout << table[i] << ' ';
  std::cout << '\n';

  table.remove(1);   // this removes the entry for 12, swapping it for 2

  for (std::size_t i = 0 ; i < table.size() ; ++i)
    std::cout << table[i] << ' ';
  std::cout << '\n';

  return 0;
}
person jogojapan    schedule 07.03.2013

Я бы предложил использовать хеш-таблицу. Там вы можете как удалять, так и искать элемент с постоянной сложностью. В C++ вы можете использовать std::unordered_map(C++11) или boost::unordered_map(pre-C++11), а в java - HashMap.

person Ivaylo Strandjev    schedule 07.03.2013
comment
Спасибо, не могли бы вы дать мне больше пояснений или ссылок на них? Я о них понятия не имею, к сожалению. - person kMaster; 07.03.2013
comment
@TomerArazy в среднем и при условии, что у вас достаточно хорошая хеш-функция, правда. - person Ivaylo Strandjev; 07.03.2013
comment
Большое спасибо. Я немного запутался, так как я предполагаю, что unordered_map (или для простоты map, если мы пренебрегаем коэффициентом разницы log(n) в операциях) хранит два значения (ключ и значение), в то время как мне нужно только хранить кучу единичных значений. - person kMaster; 07.03.2013
comment
Предположим, я сначала поместил 10 элементов 0,1,...,9 в map<int,int> s; (например, в порядке s[i] = i). Затем, если я хочу выбрать один случайным образом, я могу легко сгенерировать случайное число в {0,1,...,9} r, а затем получить доступ к s[r]. Но что, если я удалю некоторые из них, например. 4 и 8 элемент. Теперь, как я могу случайным образом выбрать один из оставшихся 8-ми элементов? Теперь мне нужно сгенерировать случайное число в {0,1,...,9}\{3,7}, что непросто. - person kMaster; 07.03.2013
comment
@ManiBastaniParizi, может быть, я неправильно понял вопрос. Для вашего случая использования unordered_set - person Ivaylo Strandjev; 07.03.2013