问题描述:在
N
×
M
N×M
N×M的方格阵列中,指定一个方格为
a
a
a,另一个方格为
b
b
b,问题要求找出
a
a
a到
b
b
b的最短布线方案(即最短路径)。布线时只能沿直线或直角,不能走斜线。黑色的单元格代表不可以通过的封锁方格。
问题分析:
- 将方格抽象为顶点,中心方格和相邻四个方向(上、下、左、右)能通过的方格用一条边连起来。这样,可以把问题的解空间定义为一个图。
- 该问题是特殊的最短路径问题,特殊之处在于用布线走过的方格数代表布线的长度,布线时每布一个方格,布线长度累加1。
- 只能朝上、下、左、右四个方向进行布线。
表示左、上、右、下四个方向
- // direction[0] 向下
- direction[0].x = 1;
- direction[0].y = 0;
- // direction[1] 向上
- direction[1].x = -1;
- direction[1].y = 0;
- // direction[2] 向右
- direction[2].x = 0;
- direction[2].y = 1;
- // direction[3] 向左
- direction[3].x = 0;
- direction[3].y = -1;
表示阵列的边界:用BLACK去表示
布线问题的复杂度分析:由于每个方格成为活结点进入活结点队列最多一次,因此,活结点队列最多有n*m个结点。如果扩展每个结点需要O(1)的时间,因此,算法共耗时O(nm)。构造相应的最短路距离需要O(L)的时间,其中L是最短布线路径的长度。
具体代码如下:
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
#include <cstring>
#include <queue>
#include <stack>
using namespace std;
// 宏定义黑色格子的数值
#define BLACK -2
#define NUM_DIRECTION 4
// 格子结构体
struct grid
{
int x;
int y;
grid(int x = 0, int y = 0)
{
this->x = x;
this->y = y;
}
};
// n1 : 棋盘的行 n2 : 棋盘的列
int n1 = 0, n2 = 0;
// a : 棋盘
int **a = NULL;
// path : 记录路径的数组
int **path = NULL;
// 开始格子的横纵坐标
int start_x = 0, start_y = 0;
// 结束格子的横纵坐标
int end_x = 0, end_y = 0;
// 方向数组
grid direction[NUM_DIRECTION];
// 显示棋盘
void DisPlayGrid()
{
printf("---------------------------------------------------\n");
printf("棋盘如下 :\n");
for (int i = 1; i <= n1; i++)
{
for (int j = 1; j <= n2; j++)
{
if (i == start_x && j == start_y)
{
printf("From\t");
continue;
}
if (i == end_x && j == end_y)
{
printf("To\t");
continue;
}
printf("%d\t", a[i][j]);
}
printf("\n");
}
printf("---------------------------------------------------\n");
}
// 显示路径数组
void DisPlayPath()
{
printf("---------------------------------------------------\n");
for (int i = 1; i <= n1; i++)
{
for (int j = 1; j <= n2; j++)
{
printf("%d\t", path[i][j]);
}
printf("\n");
}
printf("---------------------------------------------------\n");
}
// 绘制路径
void DrawPath()
{
printf("---------------------------------------------------\n");
int x = end_x, y = end_y;
// 寻找路径的时候是逆序的,这里需要进行逆序处理
stack<grid> st;
st.push(grid(x, y));
while (path[x][y] != 0)
{
// 对四个方向进行探测
for (int i = 0; i < NUM_DIRECTION; i++)
{
int index_x = x + direction[i].x;
int index_y = y + direction[i].y;
// 找到上一个可能走过来的格子
if (path[index_x][index_y] == path[x][y] - 1)
{
x = index_x;
y = index_y;
// 进栈
st.push(grid(x, y));
break;
}
}
}
printf("路径如下 :\n");
// 输出路径
printf("(%d,%d)", st.top().x, st.top().y);
st.pop();
while (st.empty() == false)
{
printf(" -> (%d,%d)", st.top().x, st.top().y);
st.pop();
}
printf("\n---------------------------------------------------\n");
}
int main()
{
// direction[0] 向下
direction[0].x = 1;
direction[0].y = 0;
// direction[1] 向上
direction[1].x = -1;
direction[1].y = 0;
// direction[2] 向右
direction[2].x = 0;
direction[2].y = 1;
// direction[3] 向左
direction[3].x = 0;
direction[3].y = -1;
printf("请输入棋盘的大小为 n1 x n2\n");
scanf("%d%d", &n1, &n2);
a = new int *[n1 + 2];
path = new int *[n1 + 2];
for (int i = 0; i < n1 + 2; i++)
{
a[i] = new int[n2 + 2];
path[i] = new int[n2 + 2];
// 初始化的时候棋盘全部置零
memset(a[i], 0, sizeof(int) * (n2 + 2));
// 初始化的时候路径全部置为-1
memset(path[i], -1, sizeof(int) * (n2 + 2));
}
// 对棋盘进行外面的黑色格子包裹
for (int i = 0; i < n1 + 2; i++)
{
a[i][0] = BLACK;
a[i][n2 + 1] = BLACK;
}
for (int i = 0; i < n2 + 2; i++)
{
a[0][i] = BLACK;
a[n1 + 1][i] = BLACK;
}
// 黑色格子的数量
int cnt = 0;
printf("请输入棋盘中黑色单元格的数量 :\n");
scanf("%d", &cnt);
printf("请输入黑色单元格横纵坐标\n");
int x = 0, y = 0;
for (int i = 0; i < cnt; i++)
{
scanf("%d%d", &x, &y);
a[x][y] = BLACK;
}
printf("请输入开始单元格的坐标:\n");
scanf("%d%d", &start_x, &start_y);
if (start_x < 1 || start_x > n1 || start_y < 1 || start_y > n2)
{
printf("输入的开始单元格横纵坐标异常\n");
return -1;
}
path[start_x][start_y] = 0;
a[start_x][start_y] = BLACK;
printf("请输入目的单元的坐标:\n");
scanf("%d%d", &end_x, &end_y);
if (end_x < 1 || end_x > n1 || end_y < 1 || end_y > n2)
{
printf("输入的目的单元格横纵坐标异常\n");
return -2;
}
// 显示棋盘信息
DisPlayGrid();
queue<grid> q;
// 标记是否能找到出口
bool flag = false;
// 这里采取的是广度优先搜索的方式进行的,使用队列
q.push(grid(start_x, start_y));
while (q.empty() == false)
{
grid tmp = q.front();
q.pop();
if (tmp.x == end_x && tmp.y == end_y)
{
printf("可以到达终点\n");
flag = true;
break;
}
// 四个方向进行探测
for (int i = 0; i < NUM_DIRECTION; i++)
{
int index_x = tmp.x + direction[i].x;
int index_y = tmp.y + direction[i].y;
if (a[index_x][index_y] != BLACK)
{
q.push(grid(index_x, index_y));
// 入队的直接让它变为黑色格子,避免再次访问
a[index_x][index_y] = BLACK;
// 路径标记从哪里走过来的
path[index_x][index_y] = path[tmp.x][tmp.y] + 1;
}
}
}
if (flag)
{
printf("%d\n", path[end_x][end_y]);
DisPlayPath();
DrawPath();
}
else
{
printf("无法到达终点\n");
}
return 0;
}
/*
9 7
13
1 5
2 3
3 3
3 5
3 6
4 3
5 6
6 4
6 5
6 6
7 6
8 3
8 5
3 2
6 7
*/
/*
3 3
3
1 2
2 2
3 2
1 1
3 3
*/
运行结果: