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)

Кое как может подойти deque. Только удаление из середины за O(n)

ЗЫЖ кто такую ублюдочную капчу прикрутил?

anonymous
()

Перестановка в начало как должна выглядеть? А то и массив может подойти, самый простой который, если перестановка — всего лишь обмен местами.

peregrine ★★★★★
()
Ответ на: комментарий от peregrine

Видимо тут не перестановка, а на самом деле перемещение, с «дыркой» в том месте, где элемент был раньше.

Crocodoom ★★★★★
()

Сомневаюсь, что получишь О(1) по обоим хотелкам, они друг другу мешают.

Bfgeshka ★★★★★
()

А много элементов в начало перекидывать? Можно просто массив + список индексов выкинутых в начало.

morse ★★★★★
()
Ответ на: комментарий от anonymous

массив указателей + memmove. наколенке эдик сделает за 5 минут.

Да здравствует cachemiss при итерации.

andreyu ★★★★★
()
Ответ на: комментарий от anonymous

сам такой. я не эдік ліл :-) тьі меня с кемто путаешь, дружок

anonymous
()
Ответ на: комментарий от anonymous

Не совсем то, но ... hashtree, hasharray или что-то где-то рядом.

Самый ценный коммент пока. Или нет. Я просто таких слов не слышал, щас погуглю, вдруг тема!

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

У тебя вопрос неправильный. Ты хочешь, чтобы было быстро? Или чтобы гарантии времени выполнения были? Это ведь разные хотелки.

i-rinat ★★★★★
()
Ответ на: комментарий от anonymous

Алгоритмическая сложность больше, чем ту что хочет автор

anonymous
()
Ответ на: комментарий от hlamotron

У хеш таблицы условно О(1) для доступа-вставки-удаления, надо к нему прикрутить упорядоченный доступ, если ты это имел в виду под доступом по индексу. Я сам эти структуры не знаю, гугли сам

anonymous
()

skip-list, N-tree они все дадут log(N) с очень небольшим множителем для обеих операций (поиск и перемещение в начало)

даже просто список из блоков по K элементов позволит «тягать из середины в начало/конец» без крупных memmove и больших потерь на доступ.

PS/ когда вижу «хочется, чтобы элементы в памяти лежали максимально запаковано, с точки зрения хеша», бываю убеждён что автор начитался форумов и занялся преждевременной оптимизацией.

MKuznetsov ★★★★★
()
Ответ на: комментарий от MKuznetsov

За выражение «преждевременная оптимизация» можно уже смело бить кочергой по морде и ни одно разумное существо не пострадает.

hlamotron
() автор топика
Ответ на: комментарий от hlamotron

Не. Это за слоптимизацию без профайлинга и прочих пруфов оверхеда надо гвозди в голову забивать слоптимизаторам. Через обе потные ладошки. Чтоб время не тратили ни свое, ни тем более чужое.

anonymous
()
Ответ на: комментарий от hlamotron

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

vtVitus ★★★★★
()

Именно для твоего странного случая есть точное решение: хранить элементы парами, индексы которых идут так: a(1), a(N/2+1), a(2), a(N/2+2) ... [случай нечетного N разберешь сам]

Тогда поиск элемента по индексу O(1) и вытаскивание элемента из «середины» массива на первое место — тоже O(1) [перестановка первого и второго по факту]

unanimous ★★★★★
()
Ответ на: комментарий от vtVitus

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

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

hlamotron
() автор топика
Ответ на: комментарий от anonymous

Не. Это за слоптимизацию без профайлинга и прочих пруфов оверхеда надо гвозди в голову забивать слоптимизаторам. Через обе потные ладошки. Чтоб время не тратили ни свое, ни тем более чужое.

Совершенно точно нужно бить кочергой по морде людей, у которых возникает тема «преждевременной оптимизации» и любой другой оптимизации и особенно её уместности, когда ограничений по ресурсам (например на разработку) не ставилось вообще. С их мнением о том, что любая разработка оценивается в деньгах и должна прекращаться, если она экономически не оправдана, они смело шагают строем фтопушку.

hlamotron
() автор топика

По лексемам «преждевременная + оптимизация» в разговоре, где ограничений на стоимость разработки не стоит, можно неплохо детектить кретинов. Всегда срабатывает. Употребляющий это обычно каким-то чертОм оказывается.

hlamotron
() автор топика

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

anonymous
()
Ответ на: комментарий от anonymous

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

Ну типа похоже так и есть с точки зрения теории...

hlamotron
() автор топика

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

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

ox55ff ★★★★★
()

А просто менять элемент Y и первый местами не? Ну или завести отдельный список для переставленных в начало.

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

Бодливой корове б-г рогов не дает. А по тому как у тебя пригорает на раз детектится слоптимизатор-неадекват... которому били по рукам кочергой за преждевременную оптимизацию, из-за которой он проебывал сроки :)

anonymous
()
Ответ на: комментарий от hlamotron

а ты гордо маршируешь нахуй :)

anonymous
()
Ответ на: комментарий от anonymous

Бодливой корове б-г рогов не дает. А по тому как у тебя пригорает на раз детектится слоптимизатор-неадекват... которому били по рукам кочергой за преждевременную оптимизацию, из-за которой он проебывал сроки :)

Не взлетело.

hlamotron
() автор топика
Ответ на: комментарий от ox55ff

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

В исходных условиях нет ограничений на время разработки, так что ты проебался.

hlamotron
() автор топика
Ответ на: комментарий от hlamotron

В исходных условиях нет ограничений на время разработки

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

ox55ff ★★★★★
()

Чем хэш таблица не устроила? Гарантированной сложности, конечно, нет, но большинство случаев вполне себе под требования подходить будут

Deleted
()
Ответ на: комментарий от ox55ff

Это ещё не значит, что нужно жечь человеко-часы в печи бессмысленной оптимизации.

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

Сначала нужно подтвердить, что оптимизация данного участка оправдана.

Мне не нужно. Я разрешаю отвечать на сабжевый вопрос из треда без этого.

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

Чем хэш таблица не устроила? Гарантированной сложности, конечно, нет, но большинство случаев вполне себе под требования подходить будут

Хочется сохранять порядок. Иметь возможность достать первые N элементов например, как в массивчике. Или ткнуть в M-ный элемент и достать от него следующие N.

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

Так эти возможности будут - дёргай их по индексу(может быть, даже писать не сильно много придётся, если юзать std::unordered_map<std::size_t,T>::operator[]). Костыльно, но что поделать

Deleted
()

Ну так попробуй сделать хеш с элементами двухсвязного списка. Т.е. что-то вроде

struct Item { Data data; Item *prev; Item *next; }

unordered_map<Key, Item> container;

Хеш будет обеспечивать доступ за константу, а связный список изменение порядка. Проблемы будут, если у тебя нет подходящих ключей для твоей структуры. Ну и с локальностью данных проблемы.

Deleted
()

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

unC0Rr ★★★★★
()
Ответ на: комментарий от Deleted

Хеш будет обеспечивать доступ за константу

это каким образом? Даю подсказку: при перемещении элемента в начало индекс изменится не только у него, но и у всех элементов перед ним.

Вы бы хоть немного подумали над проблемой, прежде чем что-то писать.

anonymous
()
Ответ на: комментарий от anonymous

Но на хуй-то ты пошел :)

Бывает, не плач.

hlamotron
() автор топика
Ответ на: комментарий от anonymous

Есть какая-то другая точка зрения?

Есть. Например теория срать хотела на то, что современное железо представляет собой иерархическую пирамиду по памяти. Регистры, кеш-линии, оператива, диски... И вообще теория срала на железо часто. Например что существуют какие-нибудь SSE4.2... Поиск в дереве из 64 узлов может быть медленнее, чем пробежаться «фуллсканом» по std::vector::size() == 64

hlamotron
() автор топика
Ответ на: комментарий от unC0Rr

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

Была эта идея.

hlamotron
() автор топика
Ответ на: комментарий от anonymous

Хеш будет обеспечивать доступ за константу

это каким образом? Даю подсказку: при перемещении элемента в начало индекс изменится не только у него, но и у всех элементов перед ним.

Вы бы хоть немного подумали над проблемой, прежде чем что-то писать.

Хеш для доступа к элементу за константу. Порядок элементов задается связным списком. Для связного списка перемещение элемента в другое место тоже константное.

Я над проблемой подумал, прежде чем писать.

А так задача не совсем ясна. Что будет храниться, для чего.

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

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

Короче, пока не ясна задача, даже теоретизировать бесполезно, не то что давать дельные советы.

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

Deleted
()
Ответ на: комментарий от Deleted

Хеш для доступа к элементу за константу.

Если я выдернул элемент с индексом 500 и добавил под индексом 0, то в твоей схеме я удалил из хеш таблицы ключ 500 и добавил новый ключ 0. А что делать со всеми ключами начиная со старого 0 до 499? Удалять и добавлять с новыми хешами?

В твоей схеме после вышеописанной операции доступ по индексу 300 будет всегда вести к одному и тому же элементу, хотя после операции он должен вести на элемент, который раньше лежал под 299.

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

Пришло мне в голову вот такое (возможно стоит использовать std::vector<std::pair<size_type, T>>, чтоб не скакать по памяти лишний раз).

#pragma once

#include <cstdint>
#include <vector>

template <typename T>
class reindex_vector {
public:
  using size_type = typename std::vector<T>::size_type;
  using value_type = T;
  using reference = T&;
  using const_reference = const T&;

  void push_back(const T& value) {
    reindex.push_back(storage.size());
    storage.push_back(value);
  }

  template <typename... Args>
  void emplace_back(Args&&... args) {
    reindex.push_back(storage.size());
    storage.emplace_back(std::forward<Args>(args)...);
  }

  void move_front(size_type at) {
    reindex[at] = 0;
    reindex[0] = at;
  }

  auto size() const { return storage.size(); }

  reference operator [](size_type at) { return storage[reindex[at]]; }
  const_reference operator [](size_type at) const { return storage[reindex[at]]; }

private:
  std::vector<size_type> reindex;
  std::vector<T> storage;
};

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

А как ты планируешь заполнять контейнер? push_back?

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

hlamotron
() автор топика
Ответ на: комментарий от AlexAT

Точнее - indexable skiplist, там про него есть.

Ещё можно с модифицированным rbtree с хранением длины левой ветки поиграться, но всё равно выше O(logN) для указанной задачи не прыгнуть.

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