一、问题描述
已知一块土地被划分成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