LINUX.ORG.RU

Ищу структуру данных: массив/список: перестановка из любого места в начало за O(1), доступ к элементу по индексу - O(1).

 


0

6

Имеется самый тупой массив типа std::vector, где по индексу можно за O(1) получить элемент. То есть, указатель на начало + индекс = указатель на нужный элемент.

Хочется выдернуть из середины элемент с индексом Y и вставить его в начало.

У элементов с индексами >= Y+1 индексы уменьшаются на 1, т.е. дырки не образуется, а происходит сдвиг всех элементов после выдернутого ближе к началу на 1.

Вставка в начало: вставляемый элемент получает индекс 0, а у всех последующих элементов индекс увеличивается на 1.

Операция «выдернуть из Y и вставить на 0» на самом деле в итоге сохранит индексы у всех элементов начиная с индекса Y+1, а у всех элементов [1...Y-1] индексы увеличатся на 1, а у элемента Y индекс поменяется на 0.

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

Ясно, что противоречащие условия. Я не хочу никакого memmove() на 999 элементов, если переношу тысячный элемент в начало. При этом хочется доступа по индексу за O(1) или близко к этому.

А ещё хочется, чтобы элементы в памяти лежали максимально запаковано, с точки зрения хеша, т.е. в идеале это недостижимый std::vector, а на практике хотя-бы какая-то цепочка из блоков, где лежит последовательно по 64 элемента. Что-то memmov-ать в пределах нескольких линий кеша — норм.



Последнее исправление: hlamotron (всего исправлений: 1)
Ответ на: комментарий от hlamotron

Всё новое всегда втыкается в индекс 0, сдвиная всё «назад».

Хм, а что если std::vector<std::optional<T>> storage? Конец вектора трактовать как начало (ну или заставить вектор расти влево). Тогда:

void move_front(size_type at) {
  // образуем дыры при перемещении в начало
  storage.emplace_back(std::move(storage[storage.size() - at - 1]));
}

// at - индекс с конца
T& operator [](size_type at) {
  auto iter = storage.rbegin() + at;
  while (iter > storage.rend() && !iter->has_value()) {
    ++iter;
  }

  assert(iter > storage.rend());
  return *iter;
}

// можно дергать временами (после N вызовов move_front), чтоб уменьшить время operator[]
void shrink_to_fit() {
   storage.erase(std::remove_if(storage.begin(), storage.end(), [](const auto &el) {
     return !el.has_value();
   }));
}

KennyMinigun ★★★★★
()
Последнее исправление: KennyMinigun (всего исправлений: 7)
Ответ на: комментарий от unC0Rr

А временная сложность O(n) (если что я не про ту, которая конкретно в этой структуре) от деления на 2 никак не изменится...

peregrine ★★★★★
()
Последнее исправление: peregrine (всего исправлений: 1)

А массив перевернуть, не? Ну то есть вставлять в конец и делать вид, что конец - это начало.

anonymous
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.