Блог о математике, программировании, алгоритмах. И немного о работе операционной системы Linux.

пятница, 17 декабря 2010 г.

Генерация случайного разбиения

У Кнута в 4-ой части Искусства программирования есть описание алгоритма генерации всех разбиений n-элементного множества на m-частей (раздел 7.2.1.4). Положим, что нам нужно найти какое-нибудь случайное разбиение (например, мне это понадобилось для генерации случайного дерева заданной несбалансированности). Конечно, мы будем использовать случайные числа.
Смысл следующий: берем массив из m элементов и заполняем его случайными числами в диапазоне от 0 до n. Последнему элементу присваиваем значение n. Мы получили массив, в котором содержатся "метки" — значения указывающие на то, в каком именно месте будет разбито наше множество.
Затем сортируем массив, последовательно начиная с m-1-ого элемента находим разность между i-м и i-1-м элементами и записываем её в i-ую ячейку массива. Нулевой элемент массива оставляем без изменений.
Проблема в том, что этот алгоритм не гарантирует, что в результирующем массиве не будут отсутствовать нулевые элементы. С этим можно бороться последующей балансировкой. Заводим специальную переменную (например, diff) для сохранения количества "балансировок" и пока эта переменная не станет равна нулю ходим по массиву и совершаем следующие действия:
  1. Если наткнулись на ячейку содержащую ноль, прибавляем к её значению единицу, а из переменной единицу вычитаем.
  2. Если diff меньше нуля, а текущий элемент массива больше нуля, то прибавляем diff единицу, а из текущего элемента её вычитаем.
Вот как это выглядит у меня:
void
getPartition(unsigned n, unsigned m)
{
    if (n < m) {
        throw out_of_range("N must be greater then M");
    }
    unsigned sum = 0;
    vector< unsigned > partition;
    partition.resize(m);
    // заполнение массива случайными значениями
    for (unsigned i = 0; i < m; i++) {
        partition[i] = rand() % n;
    }
    partition[partition.size() - 1] = n;
    // сортировка
    sort(partition.begin(), partition.end());
    // вычитаение
    for (unsigned i = m - 1; i > 0; i--) {
        partition[i] = partition[i] - partition[i - 1];
    }
    int diff = 0;
    // балансировка
    do {
        for (unsigned i = 0; i < m; i++) {
            if (partition[i] < 1) {
                partition[i]++;
                diff--;
            }
            if (diff < 0) {
                if (partition[i] > 1) {
                    partition[i]--;
                    diff++;
                }
            }
        }
    } while (diff != 0);
    // вывод на экран вместе с суммой всех элементов для проверки
    for (unsigned i = m - 1; i > 0; i--) {
        cout << partition[i] << " ";
        sum += partition[i];
    }
    sum += partition[0];
    cout << partition[0] << " " << sum;
    cout << endl;
}

Комментариев нет:

Отправить комментарий