第五章 回溯算法习题

文章介绍了如何使用回溯算法解决三个问题:1)宝石排列的多样性计数,2)罗密欧与朱丽叶的最少转弯路径,3)石油传输网络的增压器最优设置。通过递归和剪枝策略优化搜索过程。
摘要由CSDN通过智能技术生成

1、现有n种不同形状的宝石,每种n颗,共n*n颗。同一形状的n颗宝石分别具有n种不同的颜色c1,c2,…,cn中的一种颜色。欲将这n*n颗宝石排列成n行n列的一个方阵,使方阵中每一行和每一列的宝石都有n种不同的形状和n种不同颜色。是设计一个算法,计算出对于给定的n,有多少种不同的宝石排列方案。

解题思路:利用回溯法首先需要明确搜索顺序,在本题中按第一行第一列开始移动,每次向右移动一列,到每行的最后一列时,从下一行第一列重新移动。搜索中的剪枝条件是判断每一行每一列宝石形状/颜色是否相同,同时该位置是否有宝石,若不满足条件则跳过该位置继续搜索下一位置。最后注意递归后需恢复标记。

解题代码:

#include<iostream>
using namespace std;
const int N = 100;
int n, ans;
bool rowshape[N][N], colshape[N][N]; // 判断每一行、列是否有宝石形状相同
bool rowcolor[N][N], colcolor[N][N]; // 判断每一行、列是否有宝石颜色相同
bool st[N][N]; // 该位置是否有i形状j颜色的宝石

void dfs(int x, int y)
{
    if (y == n)
    {
        y = 0; 
        x++; //换到下一行开头
    }
    if (x == n)
    {
        ans++;
        return;
    }

    for (int i = 0; i < n; i++)
    {
        for (int j = 0; j < n; j++)
        {
            if (!st[i][j] && !rowshape[x][i] && !colshape[y][i] && !rowcolor[x][j] && !colcolor[y][j])
            {
                st[i][j] = true;
                rowshape[x][i] = colshape[y][i] = rowcolor[x][j] = colcolor[y][j] = true;
                dfs(x, y + 1);
                st[i][j] = false;//恢复标记
                rowshape[x][i] = colshape[y][i] = rowcolor[x][j] = colcolor[y][j] = false;
            }
        }
    }
}

int main()
{
    cin >> n;
    dfs(0, 0);
    cout << ans << endl;
    return 0;
}

2、罗密欧与朱丽叶身处一个m×n的迷宫中。每一个方格表示迷宫中的一个房间。这 m × n 个房间中有一些房间是封闭的,不允许任何人进入。在迷宫中任何位置均可沿 8 个方向进入未封闭的房间。罗密欧位于迷宫的(p,q)方格中,他必须找出一条通向朱丽叶所在的(r,s)方格的路。在抵达朱丽叶之前,他必须走遍所有未封闭的房间各一次,而且要使到达朱丽叶的转弯次数为最少。每改变一次前进方向算作转弯一次。请设计一个算法帮助罗密欧找出这样一条道路。

解题思路:

回溯算法搜索的主要思路如下:

  1. 在当前位置按照8个方向搜索;
  2. 如果当前位置可以到达(没有越界和被封闭),则标记为当前步数+1,并进入下一层搜索;
  3. 在下一层搜索中,如果当前方向与上一个方向不同,则将dirs(转弯次数)加1;
  4. 如果当前位置是终点,且转弯次数小于等于当前最优解,则更新最少转弯次数best,并记录不同的最少转弯道路数count,并保存该路径;
  5. 回溯时需要将当前位置设为0(恢复现场),以便在其他分支中进行搜索。

解题代码:

#include<iostream>
#include<cstring>
using namespace std;
const int N = 1010;
int n, m, k; //分别表示迷宫的行数、列数和封闭的房间数
int p, q, r, s; //罗密欧与朱丽叶的房间号
int best = 1e8; // 最少转弯次数
int count = 0; // 不同的最少转弯道路数
int flag = 0;//判断是否存在最短路径
int board[N][N]; //迷宫的房间号
int bestb[N][N]; //存储最优路径
//方向数组,分别表示八个方向
int dx[] = { 0,1,1,1,0,-1,-1,-1 };
int dy[] = { 1,1,0,-1,-1,-1,0,1 };

void search(int dep, int x, int y, int dirs, int di); //在当前位置按照8个方向搜索
bool stepok(int x, int y); //用于判断是否越界
void save(); //保存找到的解

int main()
{
    cin >> m >> n >> k;
    int x, y;
    for(int i=1;i<=k;i++)
    {
        cin >> x >> y;
        board[x][y] = -1; //表示房间封闭
    }
    cin >> p >> q >> r >> s;
    board[p][q]=1;//第一个房间
    search(1, p, q, -1, 8);
    
    if(flag==1)
    {
        cout << best << endl;
        cout << count << endl;
        for (int i = 1; i <= m; i++)
        {
            for (int j = 1; j <= n; j++)
            {
                {
                cout << bestb[i][j] << " ";
                }
            }
            cout << endl;
        }
    }
    else
    {
        cout<<"No solution!"<<endl;
    }

    return 0;
}

void search(int dep, int x, int y,int dirs, int di)
{
    if (dep == m * n - k && x == r && y == s && dirs <= best)
    {
        if (dirs < best)
        {
            best = dirs;
            count = 1;
            flag = 1;
            save();
        }
        else
        {
            count++;
        }
        return;
    }
    
        for (int i = 0; i < 8; i++)
        {
            int p = x + dx[i], q = y + dy[i];
            if (stepok(p, q))
            {
                board[p][q] = dep + 1;
                if (di != i)
                {
                    if(dirs+1 <= best)
                    {
                     search(dep + 1, p, q, dirs+1, i);
                    }
                }
                else
                {
                    search(dep + 1, p, q, dirs, i);
                }
                board[p][q] = 0;
            }
        }
    }

bool stepok(int x, int y)
{
    return (x > 0 && x <= m && y > 0 && y <= n && board[x][y] == 0);
}

void save()
{
    for (int i = 1; i <= m; i++)
    {
        for (int j = 1; j <= n; j++)
            bestb[i][j] = board[i][j];
    }
}

3、网络设计问题:石油传输网络通常可表示为一个非循环带权的有向图G.G中有一个称为源的顶点s,石油从顶点输送到G中其他顶点,图G中每条边的权表示该边连接的2个顶点间的距离,网络中的油压随距离的增大而减小,为保证整个输油网络的正常工作,需要维持网络的最低油压Pmin,为此需要在网络的某处或全部顶点处设置增压器,在设置增压器的顶点处油压可以升至Pmax,油压从Pmax减到Pmin可使石油传输的距离至少为d,试设计一个算法,计算网络中增压器的最优设置方案,使得用最少增压器保证石油运输的畅通。

解题思路:确定backtrack(i,num)函数,表示搜索子集树中第i+1层子树,在主函数中调用以 backtrack(0,0)形式实现对整个解空间的回溯搜索,具体过程如下:

(1)当 i>n-1 时,已得到一个解(因为最后一个结点不必设置增压器,所以只需判断 n-1 个结点即可),若此时获得的增压器数 num 小于当前最优解 bestx[n]所对应的的增压器数 best,则表示当前解优于之前所找到的最优解,故更新 best 和 bestx[n],若没有得到一个解,则不需要更新 best 和 bestx[n];

(2)当 i<n-1 时,当前扩展结点 z 是子集树中的一个内部结点,该结点有 x[i]=1 和 x[i]=0 两个分支,其左儿子分支表示 x[i]=0 的情形,这样仅当从第 i+1 个顶点将油输送到其他各顶点时油压都≥ Pmin 时进入左子树,递归的对左子树进行搜索;其右儿子结点表示 x[i]=1 的情形,由于可行结点的右儿子结点总是可行的,故进入右子树时不需要检查可行性。

解空间:可建立如图所示的解空间(0 表示不在该点设置增压器,1 表示在该点设置增压器)

剪枝函数的确定,约定在子集树的第 j+1 层的结点 z 处,num 记录当前的增压器数,best 记录当前已得到的最少增压器数,则在该顶点将会有如下剪枝情况发生:

(1)当 num>best 时,以结点 Z 为根结点的子树中所有结点都不 满足约束条件,因而该子树中的解均为不可行解,故可将该子树剪去;

(2)如果所对应的第 j 个待处理顶点处的油压处置不小于 Pmax,则将该顶点处不用设置增压器,不必考虑以 z 为根的右子树, 故可将其右子树剪去;

(3)如果所对应的第 j 个待处理顶点处的油压处置小于 Pmin, 则将该顶点处不用设置增压器,不必考虑以 z 为根结点的左子树,故可将其左子树剪去;

(4)如果所对应的第 j 个待处理顶点沿管道将油输送至与其相邻的其它结点时油压处置小于 Pmin,则将该顶点处必须设置增压器, 不必考虑以 z 为根节点的左子树,可将左子树剪去。

解题代码:

#include <iostream>
using namespace std;

const int MAXN = 100; // 最大顶点数
const int Pmin = 0;   // 油压下限
const int Pmax = 100; // 油压上限

int graph[MAXN][MAXN]; // 图的邻接矩阵表示
int dig[MAXN];  // 存储拓扑排序结果
int bestx[MAXN]; // 存储最优解
int x[MAXN];    // 当前搜索路径
int yy[MAXN];   // 顶点油压
int n, m = 0;   // 顶点数、输出顶点计数、最优解路径长度
int best = INT_MAX; // 最优解路径长度

void topsort() {
    int i, j, k, t = 0, top = -1;
    int indegree[MAXN] = {0}; // 记录每个顶点的入度
    for (i = 0; i < n; i++) {
        for (j = 0; j < n; j++) {
            if (graph[j][i] != 0) {
                indegree[i]++;
            }
        }
    }
    for (i = 0; i < n; i++) {
        if (indegree[i] == 0) {
            indegree[i] = top;
            top = i;
        }
    }
    while (top != -1) {
        j = top;
        top = indegree[top];
        dig[t++] = j;
        m++;
        for (k = 0; k < n; k++) {
            if (graph[j][k] != 0) {
                indegree[k]--;
                if (indegree[k] == 0) {
                    indegree[k] = top;
                    top = k;
                }
            }
        }
    }
    if (m < n) {
        cout << "The network has a cycle";
    }
}

void backtrack(int i, int num, int s, int ave) {
    int j, k, v, flag = 0;
    if (i >= n - 1) {
        if (num < best) {
            best = num;
            for (k = 0; k < n; k++) 
                bestx[k] = x[k];
        }
        return;
    }
    v = dig[i];
    yy[v] = 0;
    if (i == 0 && (s < Pmin || s > Pmax)) {
        yy[v] = Pmax;
        if (s < Pmin) {
            x[0] = 1;
            backtrack(1, 1, s, ave);
        }
        if (s >= Pmax) {
            x[0] = 0;
            backtrack(1, 0, s, ave);
        }
    }
    else {
        if (i == 0) yy[v] = s;
        else {
            for (k = 0; k < i; k++) {
                if (graph[dig[k]][v] != 0)
                    yy[v] = yy[v] + yy[dig[k]] - ave * graph[dig[k]][v];
            }
        }
        for (j = 0; j < 2; j++) {
            if (yy[v] >= Pmax) {
                yy[v] = Pmax;
                x[i] = 0;
                backtrack(i + 1, num, s, ave);
                break;
            }
            if (yy[v] <= Pmin) {
                yy[v] = Pmax;
                x[i] = 1;
                num++;
                if (num >= best) return;
                else {
                    backtrack(i + 1, num, s, ave);
                    break;
                }
            }
            if (j == 0) {
                for (k = 0; k < n; k++) {
                    if (graph[v][k] != 0 && yy[v] - ave * graph[v][k] < Pmin) {
                        flag = 1;
                        break;
                    }
                }
            }
        }
        if (flag == 1 || j == 1) {
            x[i] = 1;
            num++;
            yy[v] = Pmax;
            if (num >= best) return;
            else {
                backtrack(i + 1, num, s, ave);
                return;
            }
        }
        else {
            x[i] = 0;
            backtrack(i + 1, num, s, ave);
        }
    }
}

int main() {
    // 初始化图的邻接矩阵表示
    int n; // 顶点数
    cin>>n;
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < n; j++) {
            cin>>graph[i][j];
        }
    }


    topsort();
    backtrack(0, 0, 10, 2); // 假设初始油压为10,平均油耗为2

    cout << "Best path length: " << best << endl;
    cout << "Best path: ";
    for (int i = 0; i < n; i++) {
        cout << bestx[i] << " ";
    }
    cout << endl;

    return 0;
}

  • 23
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值