std::unordered_map — как отслеживать максимальный/минимальный ключ в любое время

У меня std::unordered_map<int, int>. Я не хочу использовать другие структуры, такие как дерево или что-либо еще, вызывающее требования к задержке. Но в любое время мне нужно знать текущий максимальный ключ и минимальный ключ. Как я могу это сделать? Распределение НЕ является равномерным, вместо этого часто удаляются и вставляются значения max и min. Поэтому мне нужно что-то более умное, чем «просто сканировать всю карту для нового максимума/минимума, когда текущий максимум/минимум удален».

Я не хочу использовать никакие другие структуры. Я хочу использовать std::unordered_map!

upd по ответу создал такую ​​структуру:

struct OrderBookItem {
    int64_t price;
    int32_t lots;
};

typedef multi_index_container
    <OrderBookItem, indexed_by<

    hashed_unique<
    BOOST_MULTI_INDEX_MEMBER(OrderBookItem,int64_t,price)
    >,

    ordered_unique<
    BOOST_MULTI_INDEX_MEMBER(OrderBookItem,int64_t,price),
    std::greater<int64_t>
    >

    >> OrderBookContainer;

person Oleg Vazhnev    schedule 22.01.2014    source источник
comment
Я не думаю, что вы можете отслеживать минимум и максимум после удаления для неупорядоченного контейнера. Для этого вам по своей сути требуется сохранять порядок элементов, чтобы вы знали, какой из них предшествует минимальному/максимальному значению, которое вы только что удалили.   -  person user541686    schedule 22.01.2014
comment
Как говорит Мердад, вы просите невозможного. Есть некоторые компромиссы, с которыми вы могли бы пофлиртовать, если использование памяти является серьезной проблемой, например, сохранение фиксированного размера верхних N и нижних N буферов при вставке и стирании и воссоздание их только тогда, когда вы потеряли след минимального или максимального значения. - в зависимости от шаблонов доступа, которые могут быть после 10N или более операций. Однако для средних накладных расходов хорошим способом является параллельное сбалансированное двоичное дерево (то есть std::map). Обратите внимание, что ваш поиск unordered_map по-прежнему будет таким же быстрым... просто ваша вставка и стирание замедлятся до уровня производительности двоичного дерева.   -  person Tony Delroy    schedule 22.01.2014
comment
зачем тебе std::unordered_map?   -  person Bryan Chen    schedule 22.01.2014
comment
потому что std::unordered_map быстрый   -  person Oleg Vazhnev    schedule 22.01.2014
comment
@javapowered Но он быстрый именно потому, что не сохраняет порядок элементов...   -  person Tristan Brindle    schedule 22.01.2014
comment
Вы не можете съесть свой пирог и получить его одновременно.   -  person R. Martinho Fernandes    schedule 22.01.2014
comment
Нужен порядок, нужен порядок! Единственная причина для использования unordered_map в этом случае — если вы либо только добавляете данные, либо готовы страдать от поиска на карте новых самых высоких/самых низких значений, если вы удалили один из них.   -  person thecoshman    schedule 22.01.2014
comment
-1, но мама, я хочу использовать X, это не правильный Q   -  person NoSenseEtAl    schedule 22.01.2014
comment
О скольких элементах идет речь? Вы сравнивали скорость, используя std::map или boost::container::flat_map (boost.org/doc/libs/1_55_0/doc/html/container/)? Насколько часты вставки по сравнению с удалениями? Если вставки происходят намного чаще, чем вставки, то увеличение минимального/максимального значения при вставке, но отмена его действия при удалении, возможно, может быть вариантом.   -  person dalle    schedule 22.01.2014
comment
возможный дубликат unordered_map с максимальным и минимальным отслеживанием ключей?   -  person Sneftel    schedule 23.01.2014
comment
@Sneftel да, я создал новый вопрос, потому что этот вопрос был о С++, а предыдущий вопрос был об алгоритме в целом. Вероятно, мне лучше как-то изменить предыдущий вопрос.   -  person Oleg Vazhnev    schedule 23.01.2014


Ответы (2)


Ваши точные требования не могут быть выполнены: std::unordered_map работает быстро (т. е. O(1) вставка/удаление/поиск), потому что не упорядочивает свои элементы. Это означает, что поиск минимума/максимума занимает O(N).

Цена заказа, уплаченная std::map за то, что вставка/удаление/поиск (включая нахождение минимума/максимума) становится O(log N).

Если вы не возражаете против использования Boost, вы можете взглянуть на Boost.MultiIndex. Это позволяет вам получать доступ к вашим данным через несколько интерфейсов одновременно. Это чрезвычайно универсальная и высокопроизводительная библиотека, которую также можно использовать для структур данных кэша MRU (которые также сочетают в себе быстрый доступ и отслеживание порядка).

Ваш основной интерфейс std::unordered_map предоставляется индексом hashed_unique, где вы используете объект функции identity (предоставляемый Boost.MultiIndex). Ваш вторичный интерфейс будет имитировать std::set. Это позволяет найти минимальное/максимальное значение в O(1) времени как *begin() и *rbegin().

Вы можете сделать это, добавив индекс ordered_unique с определяемым пользователем функциональным объектом MyOrder для вычисления любого критерия, который вы хотите использовать для их упорядочения. Небольшой пример кода (исключая пространство имен boost:: везде)

using MyContainer = multi_index_container<
    Item,
    indexed_by<
        hashed_unique<identity<Item>>, // O(1) erasure/lookup, O(log N) insertion
        ordered_unique<MyOrder<Item>>  // track minimum/maximum in O(log N)
    >
>;

За кулисами Boost.MultiIndex реализует это примерно как std::set<Value> с std::unordered_map<Key, set<Value>::iterator>. Это означает, что и поиск, и стирание O(1). Стирание может быть O(1), потому что unordered_map::erase(value) возвращает итератор в set, а std::set<Value>::erase(iterator) равно O(1).

Однако количество вставок остается O(log N), потому что вы не можете избежать поиска ранга вновь вставленного значения в упорядоченной последовательности за меньшее время.

Это улучшение по сравнению с std::map для поиска/стирания стоит всего несколько указателей на элементное пространство.

person TemplateRex    schedule 22.01.2014
comment
я думаю, что A неправ, так как он все еще будет иметь логарифмическую сложность из-за min max ... так что это то же самое, если не хуже, чем использование только карты, потому что он говорит, что вставки часты (если бы это были в основном поиски, было бы лучше ) - person NoSenseEtAl; 22.01.2014
comment
так вы говорите, что поддержание упорядоченного индекса при вставке - это O (1)? - person NoSenseEtAl; 22.01.2014
comment
Я не вижу этого в документах, в основном я не понимаю, как новый итератор из неупорядоченной карты помогает вам вставлять в другую карту. IDK, если это даже законное поведение, чтобы дать итератор одной карты в качестве подсказки для другой карты... - person NoSenseEtAl; 22.01.2014
comment
@NoSenseEtAl спасибо, что указали на мою ошибку! Я исправил свой ответ, чтобы отразить вставку O(log N). Обратите внимание, что стирание по-прежнему O(1) из-за косвенности итератора. - person TemplateRex; 22.01.2014
comment
почему это лучше, чем просто std::map? - person Oleg Vazhnev; 22.01.2014
comment
@javapowered, потому что std::map имеет O(log N) поиск и стирание для всех элементов. Boost.MultiIndex имеет O(1) поиск и стирание для всех элементов. - person TemplateRex; 22.01.2014
comment
извините за простой вопрос. предполагая, что мне нужно unordered_map<int, int> что такое Item? - person Oleg Vazhnev; 22.01.2014
comment
@javapowered Item будет value_type из unordered_map, то есть std::pair<int, int> - person TemplateRex; 22.01.2014
comment
как сказать, что hashed_unique должен использовать первую int пары для хеширования? - person Oleg Vazhnev; 22.01.2014
comment
@javapowered с помощью извлечения ключа. Использование Boost.MultiIndex требует некоторого обучения, но документы превосходны. - person TemplateRex; 22.01.2014
comment
спасибо, отметив ваш ответ как правильный, но, вероятно, я потрачу пару дней на изучение мультииндекса. - person Oleg Vazhnev; 22.01.2014
comment
@javapowered или просто протестируйте с картой и посмотрите, насколько она медленнее, чем unordered_map до этого - person NoSenseEtAl; 22.01.2014
comment
что медленнее чего? - person Oleg Vazhnev; 23.01.2014
comment
отправил мою структуру на вопрос. Я думаю, что мне не следует использовать функцию идентификации, вместо этого я бы предпочел хешировать по цене - person Oleg Vazhnev; 23.01.2014
comment
возможно, вы тоже можете помочь мне просмотреть мою реализацию! заранее спасибо :) codereview.stackexchange.com/ вопросы/39870/ - person Oleg Vazhnev; 23.01.2014

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

#pragma once

#include <unordered_map>

using std::unordered_map;

class my_unordered_map
{
public:
    void insert(int key, int value)
    {
        _map[key] = value;
        if (_max < value)
            _max = value;
        if (_min> value)
            _min = value;
    }
    int get(int key)
    {
        auto it = _map.find(key);
        if (it != _map.end())
            return it->second;
        return 0; // or some error handling here.
    }
    int getMin()
    {
        return _min;
    }
    int getMax()
    {
        return _max;
    }
    // more functionality here.
private:
    unordered_map<int, int> _map;
    int _min = 0;
    int _max = 0;
};
person poljpocket    schedule 22.01.2014
comment
@poljpocket Обертка тоже была моей первоначальной мыслью, но что происходит, когда вы удаляете максимальное значение с карты? Как найти второе по величине значение? - person Tristan Brindle; 22.01.2014
comment
В этом примере кода удобно избегать одной проблемной операции: удаления. - person R. Martinho Fernandes; 22.01.2014
comment
Я знаю, я постараюсь найти решение сегодня вечером. - person poljpocket; 22.01.2014
comment
@poljpocket там еще день?.. :П - person Adorn; 09.04.2015