Эффективная структура данных для вставки

Я ищу структуру данных (подобную массиву), которая позволяет быстро (быстрее, чем O (N)) произвольно вставлять значения в структуру. Структура данных должна иметь возможность распечатывать свои элементы в том виде, в котором они были вставлены. Это похоже на что-то вроде List.Insert () (который слишком медленный, поскольку он должен сдвигать каждый элемент), за исключением того, что мне не нужен произвольный доступ или удаление. Вставка всегда будет в пределах размера «массива». Все ценности уникальны. Никаких других операций не требуется.

Например, если Insert (x, i) вставляет значение x в индекс i (0-индексация). Потом:

  • Вставка (1, 0) дает {1}
  • Вставка (3, 1) дает {1,3}
  • Вставка (2, 1) дает {1,2,3}
  • Вставка (5, 0) дает {5,1,2,3}

И в конце нужно будет распечатать {5,1,2,3}.

Я использую C ++.


person Peter    schedule 06.04.2012    source источник
comment
что вы подразумеваете под массивом вроде?   -  person juanchopanza    schedule 06.04.2012
comment
У вас есть требования относительно сложности обхода структуры данных?   -  person Luc Touraille    schedule 06.04.2012
comment
@juanchopanza Я имею в виду, что на поверхности он должен действовать как линейный массив. Он должен сохранить элементы в том виде, в котором я их вставил.   -  person Peter    schedule 06.04.2012
comment
@LucTouraille Итак, вставка должна быть сублинейной (O (lgN) и т. Д.), Но вывод содержимого массива не должен быть очень быстрым (O (N) или O (NlgN) в порядке).   -  person Peter    schedule 06.04.2012
comment
В таком случае что не так с std :: list?   -  person juanchopanza    schedule 06.04.2012
comment
@juanchopanza Если я не ошибаюсь, std :: list дает O (1) для вставки, ЕСЛИ u имеет указатель (итератор) на индекс, в который вы вставляете. Однако для получения этого указателя требуется линейный поиск.   -  person Peter    schedule 06.04.2012
comment
@Peter, ты совершенно прав, моя беда.   -  person juanchopanza    schedule 06.04.2012


Ответы (6)


Используйте список пропуска. Другой вариант - многоуровневый вектор. Список пропусков выполняет вставку с константой O (log (n)) и сохраняет порядок чисел. Многоуровневый вектор поддерживает вставку в O (sqrt (n)) и снова может печатать элементы по порядку.

РЕДАКТИРОВАТЬ: в комментарии amit я объясню, как найти k-й элемент в списке пропуска:

Для каждого элемента у вас есть башня со ссылками на следующие элементы, и для каждой ссылки вы знаете, через сколько элементов она перепрыгивает. Итак, ища k-й элемент, вы начинаете с заголовка списка и спускаетесь по башне, пока не найдете ссылку, которая перескакивает не более чем через k элементов. Вы переходите к узлу, на который указывает этот узел, и уменьшаете k с количеством элементов, которые вы перепрыгнули. Продолжайте делать это, пока не получите k = 0.

person Ivaylo Strandjev    schedule 06.04.2012
comment
Я также подумал о строках skip-list, не могли бы вы уточнить, как вы изменяете списки, связанные с доступом [те, которые гарантируют O(logn) поиск] после вставки элемента в произвольное место? Не вызовет ли это необходимости менять многие из них? Я считаю, что его [список пропуска] можно изменить, чтобы он подходил здесь, но этот момент следует проработать. ИМО - person amit; 06.04.2012
comment
Нет, на самом деле, как я реализовал список пропуска некоторое время назад, вы никогда не меняете высоту узла. Это основано на том факте, что если вы вставляете каждый новый узел с равномерно распределенной высотой, высота элементов будет достаточно близкой к идеальной. В Интернете был проведен анализ амортизированной сложности этого подхода, который показал, что он не намного хуже, чем самый лучший из возможных. - person Ivaylo Strandjev; 06.04.2012
comment
Я не понимаю, как изменить не высоту, но и индексы, как узнать, что элемент является k-м? Если ваши ключи являются индексами, разве каждая произвольная вставка не требует изменения всего хвоста связанного списка? [меня беспокоит не высота, использование недетерминированного связного списка аккуратно решает эту проблему] - person amit; 06.04.2012
comment
Для каждого элемента у вас есть башня со ссылками на следующие элементы, верно? Для каждой ссылки вы знаете, сколько элементов она перепрыгивает. Итак, ища k-й элемент, вы начинаете с заголовка списка и спускаетесь по башне, пока не найдете ссылку, которая перескакивает не более чем через k элементов. Вы переходите к новому узлу и уменьшаете k в зависимости от количества элементов, которые вы перепрыгнули. Продолжайте делать это, пока не получите k = 0. - person Ivaylo Strandjev; 06.04.2012
comment
Отлично, это прекрасно объясняет. +1. Предлагаю добавить это пояснение к самому ответу. [на самом деле, я знаю, что глупо спрашивать, это очень похоже на идею поддержки индекса в BST путем добавления поля numberOfSons к каждому узлу] - person amit; 06.04.2012

Вы рассматривали возможность использования std::map или std::vector?

Вы можете использовать std::map с рангом вставки в качестве ключа. А vector имеет reserve функцию-член.

person Basile Starynkevitch    schedule 06.04.2012
comment
OP хочет быстрее, чем линейная произвольная вставка, разве вектор и карта не будут O (n)? - person amit; 06.04.2012
comment
Да, вставка std::vector в позицию i будет O (n), потому что элементы с i по n нужно сдвинуть. С std::map происходит нечто подобное, потому что ключи должны быть обновлены. - person Fred Foo; 06.04.2012
comment
@Yavar: Но вам придется изменять индексы всех следующих элементов после каждой вставки. предположим, что у вас есть карта = [(1, a), (2, b), (3, c)], и вы хотите добавить z в местоположение 0, вам нужно будет изменить карту на [(1, z), (2, а), (3, б), (4, в)]. Если есть обходной путь - его надо проработать .. - person amit; 06.04.2012
comment
@juanchopanza: да, но он применяет уникальные ключи. Вам нужно проделать дополнительную работу, чтобы разрешить множественные вставки в один и тот же индекс без удаления предыдущих элементов. - person Fred Foo; 06.04.2012

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

(i, t) < (i*, t*)

если только

i < i* or t > t*

В коде:

struct lt {
    bool operator()(std::pair<size_t, size_t> const &x,
                    std::pair<size_t, size_t> const &y)
    {
        return x.first < y.first || x.second > y.second;
    }
};

typedef std::map<std::pair<size_t, size_t>, int, lt> array_like;

void insert(array_like &a, int value, size_t i)
{
    a[std::make_pair(i, a.size())] = value;
}
person Fred Foo    schedule 06.04.2012
comment
Предположим, мы вставляем 300 в 0, затем 100 в 0, затем 200 в 1. Что должно произойти: [], затем [300], затем [100 300], затем [100 200 300]. Но что происходит на самом деле: [], затем [((0, 1), 300)], затем [((0, 2), 100), ((0, 1), 300)], пока все хорошо, но потом [((0, 2), 100), ((0, 1), 300), ((1, 3), 200)]. Вывод: без статистики заказов это обычно сложно. - person Evgeni Sergeev; 03.05.2016

Относительно вашего комментария:

List.Insert () (который работает слишком медленно, так как должен сдвигать каждый элемент),

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

person nndhawan    schedule 05.06.2012

Решение, которое по умолчанию включено в GCC, - это структура данных веревка. Вот документация. Обычно веревки приходят на ум при работе с длинными строками символов. Здесь вместо символов ints, но работает то же самое. Просто используйте int в качестве параметра шаблона. (Также может быть pairs и т. Д.)

Вот описание веревки в Википедии.

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

person Evgeni Sergeev    schedule 05.05.2016

В С ++ вы можете просто использовать карту векторов, например:

int main() {
  map<int, vector<int> > data;
  data[0].push_back(1);
  data[1].push_back(3);
  data[1].push_back(2);
  data[0].push_back(5);
  map<int, vector<int> >::iterator it;
  for (it = data.begin(); it != data.end(); it++) {
    vector<int> v = it->second;
    for (int i = v.size() - 1; i >= 0; i--) {
      cout << v[i] << ' ';
    }
  }
  cout << '\n';
}

Это печатает:

5 1 2 3 

Так же, как вы хотите, а вставки - O (log n).

person Running Wild    schedule 06.04.2012
comment
Он потерпит неудачу, если вы в следующий раз попытаетесь вставить 10 во 2-й индекс. - person amit; 06.04.2012