Какой самый быстрый способ сортировки массива из 7 целых чисел? - PullRequest
10 голосов
/ 12 сентября 2009

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

Я использую этот тип (среди прочего, конечно):

  type
    T7Cards = array[0..6] of integer;

Есть две вещи в этом массиве, которые могут быть важны при решении, как его отсортировать:

  1. Каждый элемент имеет значение от 0 до 51. Другие значения невозможны.
  2. Дубликатов нет. Никогда.

Имея эту информацию, какой самый быстрый способ сортировать этот массив? Я использую Delphi, поэтому код на Паскале будет лучшим, но я могу читать C и псевдо, хотя и немного медленнее: -)

В данный момент я использую быструю сортировку, но забавно то, что это почти не быстрее, чем пузырьковая сортировка! Возможно из-за небольшого количества предметов. Сортировка составляет почти 50% от общего времени выполнения метода.

EDIT:

Мейсон Уилер спросил, почему необходимо оптимизировать. Одна из причин заключается в том, что метод будет вызываться 2118760 раз.

Основная информация о покере: всем игрокам сдаются по две карты (в кармане), а затем на стол сдаются пять карт (первые три называются флопом, следующие - тёрн, а последние - ривер. пять лучших карт, из которых состоят их руки)

Если у меня в кармане две карты, P1 и P2, я буду использовать следующие циклы для генерации всех возможных комбинаций:

for C1 := 0 to 51-4 do
  if (C1<>P1) and (C1<>P2) then
     for C2 := C1+1 to 51-3 do
       if (C2<>P1) and (C2<>P2) then
         for C3 := C2+1 to 51-2 do
           if (C3<>P1) and (C3<>P2) then
             for C4 := C3+1 to 51-1 do
               if (C4<>P1) and (C4<>P2) then
                 for C5 := C4+1 to 51 do
                   if (C5<>P1) and (C5<>P2) then
                   begin
                     //This code will be executed 2 118 760 times
                     inc(ComboCounter[GetComboFromCards([P1,P2,C1,C2,C3,C4,C5])]);
                   end;

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

Итак, новый вопрос: каков самый быстрый способ сортировки массива из 7 целых чисел, когда последние 5 элементов уже отсортированы. Я полагаю, что это можно решить с помощью пары (?) If и перестановок: -)

Ответы [ 22 ]

1 голос
/ 12 сентября 2009

В псевдокоде:

int64 temp = 0;
int index, bit_position;

for index := 0 to 6 do
   temp |= 1 << cards[index];

for index := 0 to 6 do
begin
   bit_position = find_first_set(temp);
   temp &= ~(1 << bit_position);
   cards[index] = bit_position;
end;

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

Примечание: Вторая часть также может быть реализована путем итерации по битам за линейное время, но на практике это может быть не быстрее:

index = 0;
for bit_position := 0 to 51 do
begin
   if (temp & (1 << bit_position)) > 0 then
   begin
      cards[index] = bit_position;
      index++;
   end;
end;
1 голос
/ 13 сентября 2009

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

Отображение исходных карт на биты в 64-битном целом (или любом целом числе с> = 52 битами).

Если во время первоначального отображения массив отсортирован, не меняйте его.

Разделите целое число на кусочки - каждому будет соответствовать значение от 0x0 до 0xf.

Используйте полубайты в качестве индексов для соответствующих отсортированных подмассивов. Вам понадобится 13 наборов из 16 под-массивов (или просто 16 под-массивов, использующих второе перенаправление, или выполняйте битовые операции вместо поиска ответа; что быстрее будет зависеть от платформы).

Объединить непустые вложенные массивы в окончательный массив.

Вы можете использовать больше, чем грызть, если хотите; байты дают 7 наборов по 256 массивов и повышают вероятность того, что непустые массивы требуют конкатенации.

Предполагается, что ветки дорогие, а доступ к кэшированному массиву - дешевый.

#include <stdio.h>
#include <stdbool.h>
#include <stdint.h>

// for general case of 7 from 52, rather than assuming last 5 sorted
uint32_t card_masks[16][5] = {
    { 0, 0, 0, 0, 0 },
    { 1, 0, 0, 0, 0 },
    { 2, 0, 0, 0, 0 },
    { 1, 2, 0, 0, 0 },
    { 3, 0, 0, 0, 0 },
    { 1, 3, 0, 0, 0 },
    { 2, 3, 0, 0, 0 },
    { 1, 2, 3, 0, 0 },
    { 4, 0, 0, 0, 0 },
    { 1, 4, 0, 0, 0 },
    { 2, 4, 0, 0, 0 },
    { 1, 2, 4, 0, 0 },
    { 3, 4, 0, 0, 0 },
    { 1, 3, 4, 0, 0 },
    { 2, 3, 4, 0, 0 },
    { 1, 2, 3, 4, 0 },
};

void sort7 ( uint32_t* cards) {
    uint64_t bitset = ( ( 1LL << cards[ 0 ] ) | ( 1LL << cards[ 1LL ] ) | ( 1LL << cards[ 2 ] ) | ( 1LL << cards[ 3 ] ) | ( 1LL << cards[ 4 ] ) | ( 1LL << cards[ 5 ] ) | ( 1LL << cards[ 6 ] ) ) >> 1;

    uint32_t*   p    = cards;
    uint32_t    base = 0;

    do {
        uint32_t* card_mask = card_masks[ bitset & 0xf ];

        // you might remove this test somehow, as well as unrolling the outer loop
        // having separate arrays for each nibble would save 7 additions and the increment of base
        while ( *card_mask )
            *(p++) = base + *(card_mask++);

        bitset >>= 4;
        base += 4;
    } while ( bitset );
}

void print_cards ( uint32_t* cards ) {
    printf ( "[ %d %d %d %d %d %d %d ]\n", cards[0], cards[1], cards[2], cards[3], cards[4], cards[5], cards[6] );
}

int main ( void ) {
    uint32_t cards[7] = { 3, 9, 23, 17, 2, 42, 52 };

    print_cards ( cards );
    sort7 ( cards );
    print_cards ( cards );

    return 0;
}
0 голосов
/ 13 сентября 2009

Вот ваша основная O (n) сортировка.Я не уверен, как это сравнивается с другими.Используются развернутые циклы.

char card[7]; // the original table of 7 numbers in range 0..51
char table[52]; // workspace

// clear the workspace
memset(table, 0, sizeof(table));

// set the 7 bits corresponding to the 7 cards
table[card[0]] = 1;
table[card[1]] = 1;
...
table[card[6]] = 1;

// read the cards back out
int j = 0;
if (table[0]) card[j++] = 0;
if (table[1]) card[j++] = 1;
...
if (table[51]) card[j++] = 51;
0 голосов
/ 12 сентября 2009

Взгляните на это:

http://en.wikipedia.org/wiki/Sorting_algorithm

Вам нужно будет выбрать тот, который будет иметь стабильную стоимость в худшем случае ...

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

0 голосов
/ 12 сентября 2009

То, на что ссылается JRL - это сортировка сегмента. Поскольку у вас есть конечный дискретный набор возможных значений, вы можете объявить 52 сегмента и просто отбросить каждый элемент в блок за O (1) раз. Следовательно, сортировка ведра O (n). Без гарантии конечного числа различных элементов самая быстрая теоретическая сортировка - это O (n log n), а такие вещи как сортировка слиянием - быстрая сортировка. Тогда это просто баланс лучших и худших сценариев.

Но длинный ответ короткий, используйте сортировку ведра.

0 голосов
/ 12 сентября 2009

Если вам нравится вышеупомянутое предложение сохранить массив из 52 элементов, который всегда сохраняет ваш массив отсортированным, то, возможно, вы можете сохранить еще один список из 7 элементов, который будет ссылаться на 7 допустимых элементов в массиве из 52 элементов. Таким образом, мы даже можем избежать анализа массива из 52 элементов.

Я полагаю, что для того, чтобы это было действительно эффективно, нам нужно иметь структуру типа связанного списка, которая будет поддерживать операции: InsertAtPosition () и DeleteAtPosition () и быть эффективной при этом.

0 голосов
/ 13 сентября 2009

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

Приветствия

0 голосов
/ 11 октября 2013

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

С небольшими числами двоичная отбивная является избыточной, а троичная отбивная в любом случае уместна: Одна новая карта будет в основном разделена на две и три, а именно. 2 + 3 или 3 + 2, две карты в одиночные и парные, например, 2 + 1 + 2

Таким образом, наиболее эффективный с точки зрения времени и времени способ размещения новой карты меньшего размера заключается в сравнении с [1] (то есть пропустить [0]), а затем выполнить поиск влево или вправо, чтобы найти карту, которую следует сместить, и поменять местами и двигайтесь вправо (смещая, а не пузырясь), сравнивая с новой картой большего размера, пока не найдете, куда она идет. После этого вы двинетесь вперёд (две карты были вставлены). Переменные, содержащие новые карты (и свопы), должны быть регистрами.

Подход был бы быстрее, но потреблял бы больше памяти.

0 голосов
/ 29 декабря 2010

Если вы ищете очень низкие издержки, оптимальная сортировка, вы должны создать сеть сортировки. Вы можете сгенерировать код для сети из 7 целых чисел, используя алгоритм Бозе-Нельсона.

Это обеспечит фиксированное количество сравнений и равное количество обменов в худшем случае.

Сгенерированный код некрасив, но оптимален.

0 голосов
/ 12 сентября 2009

В ответах много петель. Учитывая его требования к скорости и крошечный размер набора данных, я бы не делал ЛЮБЫХ циклов.

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

Интересно, это правильный подход? Как вы собираетесь анализировать 7-карточную руку? Я думаю, что вы все равно преобразуете его в другое представление для анализа. Разве массив 4x13 не будет более полезным представлением? (В любом случае, это может поставить вопрос о сортировке на спор.)

...