簡體   English   中英

獲取C ++中矩陣的所有可能組合

[英]Get all possible combinations for a matrix in C++

我們有一間教室。 我們的主要目標是使成對的學生一起工作。 我們該怎么做? 通過矩陣。 該矩陣(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)-依此類推。

因此,我們需要找到一種使用回溯算法的解決方案該解決方案可為此目的獲得最高價值。 解決方案是一個向量,其中包含成對的最大值。

我們認為正確的方法將是生成樹,但是我們不知道如何生成它。

我們嘗試的最后一件事是通過一種方法來計算程序將需要進行多少次迭代才能生成所有對,並使用模塊來知道何時需要更改解向量中的順序。 我不是這種方法的忠實擁護者。 無論如何,我們無法使其正常運行。

由於矩陣是隨機生成的,因此我們沒有實際的“預期結果”。 我們需要的是一種確保每對學生和可能的學生組合都能完成的方法。

因此,您在i-th行的每一行都有一個方矩陣,其中代表學生i所有可能對,而每個學生只能有一對。

要獲得所有可能的對組合,可以使用以下遞歸:

  1. 為第i個學生反復拋出所有可能的對:

    • 如果可以配對(不是他本人,並且不使用配對),則將第i個學生及其配對標記為“二手”並存儲。
    • 如果(i + 1)小於,則學生數,然后與第(i + 1)個學生一起進入步驟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]

之后,您可以計算所有對對的總體滿意度並選擇最合適的對。

您的問題使我想起了minimum transportation cost問題。 這是一種眾所周知的線性編程問題,您的問題可能是其中的特例。

以下是可能的費用表示例:

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

每個學生都需要一個“對”,每個學生都可以向其他人供餐。 作為運輸成本,我們可以使用對他們的不滿意程度。 解決該問題將滿足需求並最大程度地減少整體不滿。

當然,您可以找到很多用c ++解決此問題的庫。 或者您甚至可以嘗試一些在線計算器

我認為這是一個有趣的問題,它需要一些聰明的動態編程。 但是,我將從一些簡單的蠻力開始,然后嘗試對其進行完善。 據我了解您的問題,您目前處於這個階段,並嘗試找到一種方法來枚舉所有可能的配對。

可視化它

對於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個學生,看起來有點復雜。 因此,讓我們弄清楚這一點。

數數

假設您有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)!!

現在我們已經知道如何計算它們。 有8位學生的105種可能性。 通常, 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
  • 此時可用的Studnet: 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可能,而最后一種只有一種組合。

現場演示

免責聲明:如上所述,我並不是說這是一種有效的實現。 該代碼實際上是作為詳細的參考實現。 對於每個flat_index我基本上是從樹的根部遍歷到其葉子flat_index 在下一次迭代中,您可能會想到僅從某些初始配置開始視需要遍歷整個樹。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM