布线问题_分支限界法

问题描述:在 N × M N×M N×M的方格阵列中,指定一个方格为 a a a,另一个方格为 b b b,问题要求找出 a a a b b b的最短布线方案(即最短路径)。布线时只能沿直线或直角,不能走斜线。黑色的单元格代表不可以通过的封锁方格。
W1BQ6LOOI6VEN1_OSMYX9.png
问题分析:

  • 将方格抽象为顶点,中心方格和相邻四个方向(上、下、左、右)能通过的方格用一条边连起来。这样,可以把问题的解空间定义为一个图。
  • 该问题是特殊的最短路径问题,特殊之处在于用布线走过的方格数代表布线的长度,布线时每布一个方格,布线长度累加1。
  • 只能朝上、下、左、右四个方向进行布线。

_T@Z_`_8I_IV_I41CQB_G_Q.png
表示左、上、右、下四个方向

  • // 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
*/

运行结果:
_3RE6JKG5V___GQ_KMQ~IX1.png
@W_J`16_2_SWF6NKBXU5UKJ.png

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

中小庸

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值