Лучшая структура данных дерева / кучи для фиксированного набора узлов с изменяющимися значениями + нужны лучшие 20 значений - PullRequest
2 голосов
/ 25 мая 2010

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

  • Фиксированный набор пользователей (может быть от 10 000 до 50 000 пользователей)
  • Будет сопоставлять идентификаторы пользователей с их оценкой и другой пользовательской информацией.
  • Идентификаторы пользователя будут значениями auto_increment.
  • Если структура имеет большие накладные расходы памяти, это, вероятно, не проблема.
  • Если программа вылетает во время игры, ее можно просто перезапустить.
  • Сильно предпочитаю что-то уже доступное, например, код с открытым исходным кодом / общественный домен.
  • Быстро получить текущий счет пользователя.
  • Быстрое добавление к текущему баллу пользователя (и возвращение его текущего балла)
  • Быстро получите 20 пользователей с самым высоким счетом.
  • Без удалений.
  • Нет вставок, за исключением случаев, когда структура создается впервые, и то, сколько времени это занимает, не критично.
  • Получение первых 20 пользователей будет происходить только каждые пять или десять секунд, но получение / добавление будет происходить гораздо чаще.

Если бы не последний, я мог бы просто создать блок памяти, равный sizeof(user) * max(user id) и поставить каждому пользователю значение user id * sizeof(user) для быстрого доступа. Должен ли я сделать это плюс некоторую другую структуру для функции Top 20, или есть одна структура, которая будет обрабатывать все это вместе?

Ответы [ 3 ]

1 голос
/ 25 мая 2010

Вас может заинтересовать Куча Фибоначчи . Это имеет O (1) (амортизированный) увеличенияКлюч и найтиMax.

Для получения дополнительной информации о куче в целом см .: Структура данных кучи , особенно таблица, которая сравнивает разные кучи.

Здесь можно найти реализацию Кучи Фибоначчи, которую вы, возможно, можете использовать / вдохновлять: http://resnet.uoregon.edu/~gurney_j/jmpc/fib.html

1 голос
/ 25 мая 2010

Используйте std::map. В невероятно маловероятном случае, когда это когда-либо появится в вашем профилировании, вы можете подумать о переходе на что-то более экзотическое. Затраты памяти для пользователей 50 КБ составят около одного мегабайта или два.

Я сомневаюсь, что итерация по карте с 50 тысячами записей каждые 5-10 секунд, чтобы найти лучшие результаты, приведет к значительным накладным расходам. Однако, если это так, либо используйте многоиндексный контейнер Boost, либо сохраняйте отдельную структуру для высоких оценок (куча или просто массив указателей на текущие топ-20 по порядку). Просто с массивом / вектором из 20 код для увеличения оценки может выглядеть примерно так (при условии, что оценки только увеличиваются, а не уменьшаются):

player.score += points;
if (player.score > hiscores[19]->score) {
    hiscore_dirty = true;
}

И код для получения высоких оценок:

if (hiscore_dirty) {
    recalculate_hiscores();
    hiscore_dirty = false;
}
std::for_each(hiscores.begin(), hiscores.end(), do_something);

Если ваши политики «автоинкремента» и «без удаления» исправлены навсегда (то есть вы никогда не удалите пользователей из БД), и, следовательно, идентификаторы пользователей действительно имеют непрерывный диапазон от 0 до предела, тогда вам просто нужно используйте std::vector вместо std::map.

0 голосов
/ 25 мая 2010

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

Если вы используете старый старый C ++ и не имеете Boost, следуйте совету Стива Джессопса и просто используйте std::map, если у вас есть C ++ 0x или Boost, вам лучше использовать hash_map или unordered_map: он просто лучше соответствует вашим требованиям (в конце концов, вам не нужно заказывать игроков по идентификатору, вы просто хотите быстро их найти) и, вероятно, будет быстрее, учитывая количество игроков.

Для управления top20 у вас есть 2 варианта:

  • Вы можете использовать библиотеку Boost.MultiIndex для создания одного уникального контейнера, который одновременно предлагает быстрый поиск по идентификатору (используя хэш-карту) и упорядоченный индекс по счету ... однако заказывать всех игроков - пустая трата когда вам нужно только 20 из них
  • Вы можете просто управлять отдельной структурой, такой как vector указателей для пользователей, и каждый раз, когда вы изменяете счет проверки пользователя, она должна заменять пользователя в векторе

Последнее решение, хотя и простое, предполагает, что игрок не может потерять очки ... гораздо сложнее, если это может произойти.

class UsersCollection;

class User
{
public:
  void incrementScore(size_t term);

private:
  size_t mId;
  size_t mScore;
  UsersCollection& mCollection;
};


class UsersCollection
{
public:
  static const size_t MNumberHiScores = 20;
  static const size_t MNotAChampion = -1;

  UsersCollection(DBConnection const&);

  // returns either the position of the user in
  // the hi scores vector or MNotAChampion
  size_t insertUserInHiScores(User const& user);

private:
  std::unordered_map<size_t, User> mUsers;
  std::vector<User const*> mHiScores;         // [1]
};

void User::incrementScore(size_t term)
{
  mScore += term;
  mCollection.insertUserInHiScores(*this);
}

struct UserSort: std::binary_function<User const*, User const*, bool>
{
  bool operator()(User const* lhs, User const* rhs) const
  {
    return lhs->score() > rhs->score();
  }
};

size_t UsersCollection::insertUserInHiScores(User const& user)
{
  std::vector<User const*>::const_iterator it =
    std::find(mHiScores.begin(), mHiScores.end(), &user);

  if (it == mHiScores.end()) // not among the hiscores
  {
    mHiScores.push_back(&user);
  }

  std::sort(mHiScores.begin(), mHiScores.end(), UserSort());

  if (mHiScores.size() > MNumberHiScores) // purge if too many users
  {
    User const* last = mHiScores.back();
    mHiScores.pop_back();

    if (&user == last) return MNotAChampion;
  }

  // return position in the vector in the [0, MNumberHiScores) range
  return std::find(mHiScores.begin(), mHiScores.end(), &user)
         - mHiScores.begin();
}

Примечание (1): использование set может показаться хорошей идеей, однако набор предполагает, что элементы не меняются, и это не так. Это могло бы сработать, если бы мы были очень осторожны:

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