1、問題
1.1 團團坐
有一張圓桌,坐了A,B,C,D四個人,已知,D在A的右邊,C在D的對面,請問A,B,C,D,的坐次?
解答:這個問題相對簡單,我們紙上畫一畫,就能畫出他們的可能的位置了
但是,可能還有一種解,比如我們把A,B,C,D依次右轉一個位,也是滿足條件的,而且只要保持他們的相對位置不變,依次右轉n個位都是問題的解,而且還有個有趣的事情,當他們轉了一圈(即右轉4個位)后,他們右回到原位了
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的右邊?
我們來換一張相對好看一點的圖:
圖中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; }
說明:算法實現中是固定最后一位不動的,即D的位置不動,所以最后一位都是3(即D始終在3號位),但該解也是符合原問題的。