排列組合問題之圓形分布


1、問題
1.1 團團坐
有一張圓桌,坐了A,B,C,D四個人,已知,D在A的右邊,C在D的對面,請問A,B,C,D,的坐次?

解答:這個問題相對簡單,我們紙上畫一畫,就能畫出他們的可能的位置了

image

但是,可能還有一種解,比如我們把A,B,C,D依次右轉一個位,也是滿足條件的,而且只要保持他們的相對位置不變,依次右轉n個位都是問題的解,而且還有個有趣的事情,當他們轉了一圈(即右轉4個位)后,他們右回到原位了

image

2、圓形分布
上面這個問題就是一種圓形分布,那么他和直線分布的區別在哪里呢?又有什么聯系呢?
上面文章《排列組合問題之線型排列》中講過,當有4個球時,可能的排列共有4! = 24種,那么我們把A,B,C,D四個人的坐位分別標為{0, 1, 2, 3}的號,那么A,B,C,D四個人可能坐的位置就是一個線型排列。

假設我們用計算機來解析這個問題,給出一種可能的分布方式。我們的思路是:
1> 列出所有可能的分布
2> 然后解析每種組合是否滿足題目的要求
當然,我們也可以每找到一種組合,就判斷一次是否滿足題目的要求,這樣找到一種后就可以退出了,可以減少時間復雜度。
如果我們按線型排列處理,我們一共需要找出24種排列出來,根據前面的解析可知,某一種排列還有3種排列都可以滿足題目的解得,我們只需要求出一種解即可,找出24種比較費時間,而且當問題復雜化后,比如是一個16邊形的桌子,給出上述類似的問題,那么最壞情況下共需要列出16!=20922789888000種可能,如果我們能夠去掉其中重復的,就可以減少計算。如我們這里實際只需要列出24 / 4 = 6種分布即可

3、如何找出線型分布中不一樣的分布
我們再來看看問題的解答:A,B,C,D四個人的坐次 = {0, 2, 3, 1}
假設A固定不動,那么B, C, D可能的坐次仍是一個線型分布,即共有3!=6種,因為A不動,所有B,C,D不可能發生轉動了,所以不會有上面多個解的問題
這時我們每右轉一次A,那么上面的6種分布又可以得到新的6種線型分布,但他們和轉動前的分布都是問題的解(因為他們的相對位置不變,而問題給出的條件是相對位置),所以對於圓形分布,即是把線型分布的首位連接成一個圓,圓轉動后他們的位置仍然會保持相對不變,那么我們只需要求出A=0,{B,C,D}的線型分布組合起來的解,就可以用來判定題目給出的條件

4、圓形分布算法

為了減少篇幅,創建線型分布的函數 SetBallNum() 見《排列組合問題之線型排列》

為了處理方便,重復利用代碼,生成圓形分布時,我們是固定最后一位不動的,即D的位置不動

bool    CPermutation::CreateCirclePermutaion(int nNum, std::vector<std::vector<int>>& vectorPermutation)
{
    //先創建nNum - 1個位的的直線排布
    std::vector<int>    vectorBallSet(nNum - 1, 0);
    std::vector<int>    vectorBall(nNum - 1, 0);
    SetBallNum(0, vectorBall, vectorBallSet, vectorPermutation);

    //然后將nNum - 1的位添加到最后
    for (int i = 0; i < vectorPermutation.size(); i++)
    {
        vectorPermutation[i].push_back(nNum - 1);
    }

    return    true;
}

說明:為了減少代碼,處理方便,算法中用到了STL的vector容器,需要少許STL容器使用的相關知識,當然用數組也是可以實現的,代碼會略麻煩

5、如何表達A,B,C,D的相對位置
5.1 什么是x在y的右邊?
我們來換一張相對好看一點的圖:

image

圖中B在A的右邊,他們的位置是(0, 1),C在B的右邊他們的位置是(1, 2)....

 

A, B

B, C

C, D

D, A

相對位置

(0, 1)

(1, 2)

(2, 3)

(3, 0)

通過C之前的位置我們可以總結出,所謂x在y的右邊就是f(x)+1 = f(y),這里f(x)是指x的位置號。
但當到D后,我們發現上面這個公式不適用了A也在D的右邊,但他們的位置卻是(3, 0),該如何處理這種情況?
我們再仔細看,當到D后,他的右邊的位置理應是4,但由於是圓形分布,最后一位和第一位首位相接了,這實際上就和時鍾轉滿了一圈是一樣的,這時4要變成0了,這實際就是一個模的運算,取其余數即可,即:
f(x)+1 = f(y)%4 = f(y)%桌子的邊數

5.2 什么是x在y的對面?
再看一次上面那張圖, C在A的對面,他們的位置是(0, 2),D在B的對面,他們的位置是(1, 3),因為如果2個人相對,那么他們之間就隔了一半的人
於是我們能總結出公式:f(y)-fx(x) = 4 / 2 = 2
但是又發現也可以說A在C的對面,f(a)-f(c) = -2,修改下公式:
|f(y)-f(x)| = 2 = 4 / 2 = 桌子的邊數/2

6、自動判斷算法

bool    CPermutation::TestCircleDesk()
{
    int    nSideNum = 4;
    std::vector<std::vector<int>> vectorPermutation;

    CreateCirclePermutaion(nSideNum, vectorPermutation);
    PrintPermutation(vectorPermutation);

    //{A, B, C, D}對應分布中的{0, 1, 2, 3}
    //遍歷所有的分布
    for (int i = 0; i < vectorPermutation.size(); i++)
    {
        std::vector<int>&    vectorCirclePermutation = vectorPermutation[i];
        //根據輸入條件做判斷
        //f(x)+1 = f(y)%4
        //D在A的右邊,即f(0)+1 = f(3)%4
        //如果不滿足該條件,則繼續下一組
        if ((vectorCirclePermutation[0] + 1) != (vectorCirclePermutation[3] % nSideNum))
        {
            continue;
        }

        //|f(y)-f(x)| = 2 = 4 / 2
        //C在D的對面,即|f(2)-f(3)| = 2 = 4 / 2
        if (abs(vectorCirclePermutation[2] - vectorCirclePermutation[3]) == nSideNum / 2)
        {
            printf("Find matched permutation : ");
            for (int j = 0; j < nSideNum; j++)
            {
                printf("%d ", vectorCirclePermutation[j]);
            }
            printf("\n");
            return    true;
        }

    }

    return    false;
}

image   image

說明:算法實現中是固定最后一位不動的,即D的位置不動,所以最后一位都是3(即D始終在3號位),但該解也是符合原問題的。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM