資源分配問題
一、問題描述
已知一塊土地被劃分成6*6的網格,在這些網格中共有4個煤礦和4個鐵礦,每個礦藏佔一格。要將這些資源平均分配給四個公司A,B,C,D進行開發(每個公司開發一個煤礦和一個鐵礦),同時要將這塊土地平分成四塊給這四個公司管理。為了集中管理,中心的四個網格已經分給四個公司建立總控站,每個公司分得的土地必須都與本公司的總控站連通,且土地只能沿網格線進行劃分,四塊土地的大小,形狀必須相同,每個公司的土地必須連成一片。要求求出合理的劃分方案,以及所有可能的劃分方案總數。
二、需求分析
由於題目要求土地只能沿網格線劃分,每個公司土地必須與總控站連通且連成一片,四塊圖地大小、形狀必須相同,每個公司土地恰好包含一個煤礦一個鐵礦,所以如果能求出一個公司的土地連在一塊,而且這個公司土地的形狀與其它三個公司的土地形狀一樣,並且每個公司都包含一個鐵礦和一個煤礦,即可得到一個合理的劃分方案。
由於中心的四個網格已經分給四個公司建立總控站,假設我們已知滿足題目要求的A公司的土地形狀,那麼把公司A的土地形狀分別旋轉90°,180°,270°即可得到公司B,D,C的土地形狀,並且每個公司都包含一個鐵礦和一個煤礦,那麼這樣得到的劃分方案肯定是一種解。因此我們就只需搜尋A公司的土地形狀,其他公司的土地形狀就可以推出來,這樣複雜度就大大的降了下來。
三、演算法與資料結構設計
3.1演算法思想分析
(1)假設根據A公司的土地形狀可通過旋轉90°,180°,270°得到公司B,D,C的土地形狀,那麼我們需要先求得A公司土地形狀。
(2)由於要將6*6的網格的土地平分成四塊給這四個公司管理,所以每個公司佔有九個網格,因此A公司的土地形狀是與公司A總控站相連的九個網格。由於每個公司都要有一個煤礦和一個鐵礦,所以公司A土地的生成是要受其他公司影響的,在給A公司分配土地時也要判斷該土地旋轉90°,180°,270°後與B,D,C三個公司對應的三個土地分配給B,C,D三個公司後是否滿足分配條件。
(3)公司擴張土地的約束條件:
①由於每個公司的土地都必須連成一片,假設公司A目前已確定k-1個連通網格作為其屬地,接下來公司A的第K個網格必須在公司A所屬的網格1,2…K-1的四個相鄰的方向上選擇。
②判斷一個網格能否劃入公司A的土地:
由於一個網格僅僅能被一個公司佔有,因此要求該網格不被任一公司佔有;
由於每個公司僅開發一個煤礦和一個鐵礦,如果把這塊土地以及與它成中心對稱的3塊土地分別劃分給公司A,B ,C,D,如果發現某個公司已經擁有的煤礦數或鐵礦數大於1,或擁有的空地數大於7,那麼這個土地就不能劃分給公司A。
③由於把一個解旋轉所得到得一組解都視為同解,這樣的話就會得到重複的解。為了防止產生重複的解,使效率可以大大的提高,我們在判斷某個網格能否劃入公司A的時候,要求該網格的四個相鄰要麼在界外,要麼不在公司A的第1至K-1個網格的重合。這樣就可以無重複的搜尋到所有的解。
3.2 資料結構
(1)解陣列comp。comp[x][y]表示座標為(x,y)的網格屬於哪個公司。
comp[x][y]=0,表示網格(x,y)未被佔領;
comp[x][y]=1,表示網格(x,y)屬於A公司;
comp[x][y]=2,表示網格(x,y)屬於B公司;
comp[x][y]=3,表示網格(x,y)屬於C公司;
comp[x][y]=4,表示網格(x,y)屬於D公司。
(2)土地型別陣列map,表示(x,y)網格所代表的土地型別。
map[x][y]=0,表示網格(x,y)的土地無礦;
map[x][y]=1,表示網格(x,y)的土地是煤礦;
map[x][y]=2,表示網格(x,y)的土地是鐵礦。
(3)資源記錄陣列note。note陣列記錄各公司的資源分配情況。
note[comp[x][y]][ map[x][y]]
表示comp[x][y]公司擁有的map[x][y]型別的土地個數。
(4)記錄座標陣列list。List是記錄A公司網格的座標的陣列,記錄每個網格位置。
list[1][0] = 2;//表示A公司第一個網格的列座標是2
list[1][1] = 2;// 表示A公司第一個網格的行座標是2
(5)A公司網格陣列tab。Tab陣列記錄座標(x,y)放置的是A公司的第幾個網格。
tab[2][2] = 1;表示行座標為2,列座標為2的網格放置的是A公司的第一個網格
3.3 演算法流程
1,初始化,把各個公司的總控站分給各公司。
2. 繼續為公司A分配符合約束條件的網格。
為公司A分配第deep個網格的過程:
由於每個公司的土地都必須連成一片,在搜尋第deep塊網格時,應從公司A所屬的網格1,2…deep-1的網格的四個相鄰的方向上選擇,假設現在正在擴充套件第K塊的四個方向。
int dirx[] = { 0, 1, 0, 0, -1};// 列座標增量
int diry[] = { 0, 0, 1, -1, 0};// 行座標增量
if (k > deep - 1)// 由於各塊相連,所以第deep塊是之前的某塊的擴充套件
return;
if (deep > 9) {
輸出一個解
return;
}
for (i = 1; i <= 4; i++) {
//求出第deep塊網格的座標
list[deep][0] = list[k][0] + dirx[i];
list[deep][1] = list[k][1] + diry[i];
if符合分配條件{
確定第deep塊的位置;
分配給A,B,C,D公司相應的土地塊;
search(deep + 1, k);// 繼續查詢下一塊
釋放第deep塊的位置;
返回到上一節點
}
}
// 將第deep點的座標清空
list[deep][0] = 0;
list[deep][1] = 0;
search(deep, k + 1); // 擴充套件第K+1塊
end
3.4 模組劃分
資源分配問題主要分為四個模組,分別是為A,B,C,D四個公司分配資源,判斷網格(x,y)是否符合分配的約束條件,返回上一節點狀態以及遞迴搜尋符合要求的解。
①為A,B,C,D公司分配資源的虛擬碼:
assign( x, y) {
根據A公司網格(x,y)分別求出對應的B,D,C網格位置;
將對應位置分配給相應的公司,comp[x][y] = 1-4;
將各位置對應的土地資源型別與對應公司記入note陣列+1,記錄各公司的資源分配情況
}
②返回上一節點狀態的虛擬碼:
revert( x, y) {
根據A公司網格(x,y)分別求出對應的B,D,C網格位置;
將各位置對應的各公司的資源分配情況-1,返回未分配前的資源狀態;
將對應位置的分配狀態置零,返回未分配的狀態,comp[x][y] = 0。
}
③判斷網格是否符合分配的約束條件的虛擬碼
Boolean 可擴充套件{
If 網格(x,y)已被佔有或不在網格範圍內
Return false;
If 把這塊土地以及與它成中心對稱的3塊土地分別劃分給公司A,B ,C,D後,
發現某個公司已經擁有的煤礦數或鐵礦數大於1,或擁有的空地數大於7
Return false;
If (x,y)的四個相鄰在界內並且與公司A的第1至K-1個網格的重合(重複解)
Return false;
Return true;
}
④遞迴搜尋符合要求的解的虛擬碼
Search(deep,k){
// 搜尋第deep塊,擴充套件第K塊的四個方向
求出第K塊的四個方向的位置座標,
判斷各位置座標是否符合分配的約束條件,
If(符合約束條件){
確定第deep塊的位置;
Assign(list[deep][0],list[deep][1]);//分配給A,B,C,D公司相應的土地塊
Search(deep+1,k);
清空第deep塊的位置;
Revert(list[deep][0],list[deep][1]);//返回上一節點狀態
}
Search(deep,k+1);//繼續搜尋第K+1的四個方向
}
各個模組之間的呼叫關係如下圖所示:
3.5 核心演算法的時間複雜度分析
在採用回溯法對A公司的其他網格進行搜尋的過程中,
最壞情況下:T(deep,k)= 4T(deep+1,k)+ T(deep,k+1)
最好情況下:T(deep,k)= T(deep+1,k)
四、程式設計實現與程式測試
4.1 核心程式碼
// 判斷某個網格是否能擴充套件
public static boolean check(int x, int y,int deep, int[][] comp, int[][] tab, int[][] note, int[][] map) {
int i, temp, Rx, Ry;
// 網格(x,y)已被佔有或不在網格範圍內
if (!inside(x, y) || comp[x][y] > 0){
return false;
}
// 要求(x,y)的四個相鄰要麼在界外或不在公司A的第1至K-1個網格的重合。這樣就可以無重複的搜尋到所有的解。
for (i = 1; i <= 4; i++) {
Rx = x + dirx[i];
Ry = y + diry[i];
if (inside(Rx, Ry) &&tab[Rx][Ry] > 0 && tab[Rx][Ry] < deep)
return false;
}
/*
*把這塊土地以及與它成中心對稱的3塊土地分別劃分給公司A,B ,C,D。判斷髮現某個公司已經擁有的煤礦數或鐵礦數大於1,或擁有的空地數大於7
*/
Rx = x;
Ry = y;
for (i = 0; i < 4; i++) {
int s = c[i];// 公司
int j = map[Rx][Ry];// 土地性質
// 已有煤礦/鐵礦,新加入的土地為煤礦/鐵礦
if ((map[Rx][Ry] > 0) &&(note[s][j] > 0))
return false;
// 已有六塊空地,新加入的仍為空地
if ((map[Rx][Ry] == 0) &&(note[s][j] == 7))
return false;
else {
//迴圈獲取對應的B,D,C公司土地塊座標
temp = Rx;
Rx = Ry;
Ry = 5 - temp;
}
}
return true;
}
// 遞迴搜尋
public static void search(int deep, int k,int[][] comp, char[] COMP, int[][] list, int[][] tab,
int[][] note, int[][] map) {// 搜尋第deep塊,擴充套件第K塊的四個方向
int i = 0;
if (k > deep - 1)// 由於各塊相連,所以第deep塊是之前的某塊的擴充套件
return;
if (deep > 9) {
count++;// 解的個數
print(comp, COMP, note);// A公司有9塊網格
return;
}
for (i = 1; i <= 4; i++) {
// int flag;
list[deep][0] = list[k][0] + dirx[i];
list[deep][1] = list[k][1] + diry[i];
if (check(list[deep][0],list[deep][1], k, comp, tab, note, map)) {// 檢查是否符合分配條件
tab[list[deep][0]][list[deep][1]]= deep;// 確定第deep塊
assign(list[deep][0],list[deep][1], comp, note, map);// 分配給A,B,C,D公司相應的土地塊
search(deep + 1, k, comp, COMP, list, tab, note, map);// 繼續查詢下一塊
tab[list[deep][0]][list[deep][1]]= 0;
revert(list[deep][0],list[deep][1], comp, note, map);// 回溯
}
}
// 將第deep點的座標清空
list[deep][0] = 0;
list[deep][1] = 0;
search(deep, k + 1, comp, COMP, list, tab, note, map); // 擴充套件第K+1塊
}
參考: GodofTheFallen. 平分資源Resource http://blog.csdn.net/qq_22141519/article/details/47168935