Получить все возможные комбинации для матрицы в C ++ - PullRequest
0 голосов
/ 09 мая 2019

У нас есть классная комната.Наша главная цель - собрать пары студентов для совместной работы.Как мы собираемся это сделать?Через матрицу.Эта матрица (nxn, n - пара) хранит уровень «предпочтения» каждого ученика другому.Например, i является студентом, а j - другим студентом:

matrix[i][j] = 40

matrix[j][i] = 20

Итак, уровень предпочтений (i,j)может отличаться, что пара (j,i).

Давайте предположим, что у нас есть 10 студентов.Первая итерация составит эти пары: (0,1), (2,3), (4,5) ... (9,10).Следующим будет: (0,2), (1,3), (4,5) ... (9,10) - и т. Д.

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

Мы думаем, что правильным способом было бы создать дерево, но мы не знаем, как его сгенерировать.

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

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

Ответы [ 3 ]

1 голос
/ 09 мая 2019

Ваш вопрос напоминает мне о проблеме minimum transportation cost. Это хорошо известный тип проблем линейного программирования, и ваша проблема может быть его частным случаем.

Вот пример таблицы возможных затрат:

╔═══════════╦════════════╦═════════════╦═════════════╦═════════════╗
║           ║ Student A  ║ Student B   ║ Student C   ║  Supply     ║
╠═══════════╬════════════╬═════════════╬═════════════╬═════════════╣
║           ║DissatisfAB ║DissatisfBA  ║DissatisfCA  ║     1       ║
║           ║DissatisfAC ║DissatisfBC  ║DissatisfCB  ║     1       ║
║ Demand    ║    1       ║      1      ║     1       ║             ║
╚═══════════╩════════════╩═════════════╩═════════════╩═════════════╝

Каждый студент требует одну «пару», и каждый студент может предоставить себя другим. В качестве стоимости перевозки мы можем использовать степень неудовлетворенности своей парой. Решение этой проблемы позволит удовлетворить спрос и минимизировать общую неудовлетворенность.

Конечно, вы можете найти множество библиотек, которые решают эту проблему в c ++. Или вы даже можете попробовать некоторые онлайн калькуляторы

1 голос
/ 11 мая 2019

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

Визуализируйте это

Для 4 учеников у вас есть три возможных комбинации

(0 1) (2 3)       (0 2) (1 3)      (0 3) (1 2)

    1 2 3 
0   x o o            o x o             o o x
1     o o              o x               x o
2       x                o                 o

Обратите внимание, что нам нужно нарисовать только половину матрицы, потому что она симметрична (если 1 в паре с 2, то и 2 в паре с 1). Мы также можем игнорировать диагональ. Уже с 4 учениками это выглядит несколько сложно. Итак, давайте прямо.

Counting

Допустим, у вас есть N учеников, еще не назначенных для пары. Сколько комбинаций? Давайте назовем это C(N) ...

Для 2 студентов существует только одна комбинация, следовательно, C(2)= 1.

Для более чем двух не назначенных учеников мы можем выбрать первого ученика без потери общности. Есть еще N-1 других ученика, с которыми мы могли бы связать его, следовательно, всего C(N) = N-1 * C(N-2).

Давайте сделаем это немного конкретнее, перечислив цифры:

N    N-1    C(N) 
2     1      1
4     3      3
6     5     15
8     7    105
...
n    n-1  (n-1)!!

Теперь мы уже знаем, как их считать. Есть 105 возможностей для 8 студентов. И вообще для n студентов есть (n-1)!! возможности (x!! == x*(x-2)*(x-4)*...).

Построив

Уже при подсчете мы использовали следующую стратегию для построения решения:

  • Выберите первого свободного ученика
  • Выберите одного из других бесплатных учеников
  • Повторите с остальными

Очевидно, нам нужно n/2 шагов, чтобы все студенты были назначены в пару. Давайте рассмотрим пример. С 6 студентами у нас есть

( 5 ) * ( 3 ) * ( 1 )

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

[0...4] x [0...2] x [0]

Теперь, если, например, вы хотите узнать, что такое 5 -ая комбинация, вы можете получить ее следующим образом ...

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

x0 = 5/3;       // -> 1
x1 = (5-x0)/1;  // -> 2

Т.е. это было бы

  • Мы выбираем первого студента для первой пары: 0
  • Доступные студенческие сети на данный момент: available = {1,2,3,4,5}
  • Мы выбираем available[x0], чтобы связать его с 0: (0 2)
  • Доступные студенты на данный момент: available = {1,3,4,5}
  • Мы выбираем первую доступную для следующей пары: 1
  • Доступные студенты на данный момент: available = {3,4,5}
  • Мы выбираем available[x1], чтобы связать его с 1: (1 5)
  • Только две осталось для последней пары (3 4)

-> Сопряжение с индексом 5 равно (0 2)(1 5)(3 4).

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

код

Для подсчета комбинаций нам нужна функция x!! (!! в том смысле, как описано выше):

size_t double_fac(int n){
    size_t result = 1;
    while(n > 0) {
        result*=n;
        n-=2;
    }
    return result;
}

Используя это, я могу вычислить общее количество комбинаций

size_t total_number_of_combinations(size_t n_students){ 
    return double_fac(n_students-1); 
}

Мне понадобится функция, чтобы найти индекс n-го, еще не назначенного студента, для этого я буду использовать некоторые вспомогательные функции:

template <typename IT>
IT find_skip(IT begin,IT end,size_t skip,typename IT::value_type x){
    if (skip){
        return find_skip( ++ std::find(begin,end,x), end, skip-1,x);
    } else {
        return std::find(begin,end,x);
    }
}

template <typename IT>
size_t find_skip_index(IT begin,IT end,size_t skip,typename IT::value_type x){
    return std::distance(begin,find_skip(begin,end,skip,x));
}

Также я буду использовать плоский индекс, а затем расширю его, как указано выше (на самом деле мне не очень нравится приведенное выше объяснение, но я надеюсь, что оно достаточно убедительно ...):

std::vector<size_t> expand_index(size_t n_students, size_t flat_index){
    std::vector<size_t> expanded_index;
    auto students_to_be_assigned = n_students;
    for (unsigned step=0;step<n_students/2;++step){
        int size_of_subspace = total_number_of_combinations(students_to_be_assigned-2);
        auto x = flat_index / size_of_subspace;
        expanded_index.push_back(x);
        flat_index -= x*size_of_subspace;
        students_to_be_assigned-=2;
    }
    return expanded_index;
}

В двух словах: на каждом этапе я буду выбирать партнера для первого бесплатного студента. Для flat_index == 0 первая пара - (0 1). Поскольку после выбора этой пары есть комбинации size_of_subspace == total_number_of_combinations(n_students-2), индекс выбора (0 2) в качестве первой пары равен flat_index==size_of_subspace. Однако, пожалуйста, не смущайтесь, я не преобразую flat_index непосредственно в индекс студентов, а скорее expandend_index == n относится к n -ому, еще не назначенному студенту.

Собираем это вместе:

using combination = std::vector<std::pair<size_t,size_t>>;

combination nth_combination(size_t n_students,size_t flat_index){
    combination result;
    auto expanded_index = expand_index(n_students,flat_index);      
    std::vector<bool> available(n_students,true);
    for (const auto& index : expanded_index) {
        std::pair<size_t,size_t> next_pair;
        next_pair.first = find_skip_index(available.begin(),available.end(),0,true);
        available[next_pair.first] = false;
        next_pair.second = find_skip_index(available.begin(),available.end(),index,true);
        available[next_pair.second] = false;
        result.push_back(next_pair);
    }
    return result;
}

Теперь снова возьмем n_students == 6 в качестве примера:

template <typename T>
void print_pairs(const T& t){
    for (auto e: t) std::cout << "(" << e.first << "," << e.second << ") ";    
    std::cout << "\n";
}

int main(){
    size_t n_students = 6;
    for (size_t i=0;i<total_number_of_combinations(n_students);++i){
        std::cout << i << "\t";
        print_pairs(nth_combination(n_students,i));
    }
}

печатает:

0   (0,1) (2,3) (4,5) 
1   (0,1) (2,4) (3,5) 
2   (0,1) (2,5) (3,4) 
3   (0,2) (1,3) (4,5) 
4   (0,2) (1,4) (3,5) 
5   (0,2) (1,5) (3,4) 
6   (0,3) (1,2) (4,5) 
7   (0,3) (1,4) (2,5) 
8   (0,3) (1,5) (2,4) 
9   (0,4) (1,2) (3,5) 
10  (0,4) (1,3) (2,5) 
11  (0,4) (1,5) (2,3) 
12  (0,5) (1,2) (3,4) 
13  (0,5) (1,3) (2,4) 
14  (0,5) (1,4) (2,3) 

Надеюсь, с этим выводом алгоритм также станет более понятным.После выбора первой пары есть 3 возможностей для второй пары и только одна комбинация для последней.

Live Demo

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

1 голос
/ 09 мая 2019

Итак, у вас есть квадратная матрица в каждой строке i-th, в которой представлены все возможные пары для студента i, и у каждого студента может быть только одна пара.

Чтобы получить все возможные комбинации пар, вы можете использовать следующую рекурсию:

  1. итерация броска всех возможных пар для i-го студента:

    • если пара возможна (не он сам, и пара не используется), то пометьте i-го ученика и его пару как «использованную» и сохраните ее.
    • если (i + 1) меньше, чем число студентов, перейдите к шагу 1 с (i + 1) -ым студентом, в противном случае верните сохраненные пары.

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

Вот фрагмент кода Java одного из алгоритмов, который находит все возможные варианты пар:

    List<List<Integer[]>> allVariationsOfPairs = new ArrayList<>();

    void retrieveAllVariationsOfPairs(int[][] array, int studentIndex, List<Integer[]> pairs) {
        if (studentIndex == array.length) {
            allVariationsOfPairs.add(pairs);
            return;
        }
        boolean hasPair = false;
        for (int i = 0; i < array[studentIndex].length; ++i) {
            if (studentIndex == i || array[studentIndex][i] == 1 || array[studentIndex][studentIndex] == 1) {
                continue;
            }
            hasPair = true;
            List<Integer[]> copyPairs = new ArrayList<>(pairs);
            copyPairs.add(new Integer[]{studentIndex, i});
            int[][] copyArray = Arrays.stream(array).map(r -> r.clone()).toArray(int[][]::new);
            for (int[] row : copyArray) {
                row[studentIndex] = 1;
                row[i] = 1;
            }
            retrieveAllVariationsOfPairs(copyArray, studentIndex + 1, copyPairs);
        }
        if (!hasPair) {
            retrieveAllVariationsOfPairs(array, studentIndex + 1, pairs);
        }
    }

Пример использования:

retrieveAllVariationsOfPairs(new int[6][6], 0, new ArrayList<>());

Выход:

[0, 1]
[2, 3]
[4, 5]

[0, 1]
[2, 4]
[3, 5]

[0, 1]
[2, 5]
[3, 4]

[0, 2]
[1, 3]
[4, 5]

[0, 2]
[1, 4]
[3, 5]

[0, 2]
[1, 5]
[3, 4]

[0, 3]
[1, 2]
[4, 5]

[0, 3]
[1, 4]
[2, 5]

[0, 3]
[1, 5]
[2, 4]

[0, 4]
[1, 2]
[3, 5]

[0, 4]
[1, 3]
[2, 5]

[0, 4]
[1, 5]
[2, 3]

[0, 5]
[1, 2]
[3, 4]

[0, 5]
[1, 3]
[2, 4]

[0, 5]
[1, 4]
[2, 3]

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

...