算法课第五章(回溯)课后习题

题目:

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

思路:

   约束条件:每种形状的宝石都具有n种不同的颜色,且每种颜色恰好出现一次。

   根据约束条件判读是否符合,然后用回溯法暴力历遍。

程序代码:

#include <iostream>
#include <vector>

using namespace std;

bool isValid(vector<vector<int>>& grid, int row, int col, int n) {
    // 检查行是否合法
    for (int i = 0; i < n; i++) {
        if (i != col && grid[row][i] == grid[row][col]) {
            return false;
        }
    }

    // 检查列是否合法
    for (int i = 0; i < n; i++) {
        if (i != row && grid[i][col] == grid[row][col]) {
            return false;
        }
    }

    // 检查同一形状的宝石是否具有不同的颜色
    for (int i = 0; i < n; i++) {
        if (i != row && grid[i][col] == grid[row][col]) {
            return false;
        }
        if (i != col && grid[row][i] == grid[row][col]) {
            return false;
        }
    }

    return true;
}


void backtrack(vector<vector<int>>& grid, int row, int col, int n, int& count) {
    if (row == n && col == 0) {
        count++;
        return;
    }

    for (int i = 1; i <= n; i++) {
        bool colorExists = false;
        for (int j = 0; j < n; j++) {
            if (grid[row][j] == i || grid[j][col] == i) {
                colorExists = true;
                break;
            }
        }
        if (colorExists) {
            continue;
        }

        grid[row][col] = i;
        int next_col = col + 1;
        int next_row = row;
        if (next_col == n) {
            next_col = 0;
            next_row++;
        }
        backtrack(grid, next_row, next_col, n, count);
        grid[row][col] = 0;
    }
}

// 计算给定形状和颜色数量时的不同宝石排列方案数
int countGemArrangements(int n) {
    vector<vector<int>> grid(n, vector<int>(n, 0)); // 初始化宝石排列二维数组
    int count = 0;
    backtrack(grid, 0, 0, n, count); // 调用回溯函数
    return count;
}

int main() {
    int n = 2;
    int result = countGemArrangements(n);
    cout << "对于给定的n=" << n << ",共有" << result << "种不同的宝石排列方案。" << endl;
    return 0;
}

结果:

题目:

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

思路(参考ChatGPT):

       创建一个m×n的二维数组maze来表示迷宫,其中0表示封闭的房间,1表示未封闭的房间。
        创建一个m×n的二维数组visited来表示每个房间是否被访问过,初始值为false。
        定义一个变量turns来记录转弯次数,初始值为0。
        调用DFS函数,传入罗密欧的起始位置坐标和朱丽叶的位置坐标。
        在DFS函数中,首先检查当前位置是否为朱丽叶的位置,如果是,则检查所有房间是否都被访问过,如果是,则返回true;否则返回false。
        如果当前位置不是朱丽叶的位置,将当前位置标记为已访问,并遍历8个方向进行递归搜索。
在每个方向上,如果下一个位置是未封闭的房间且未被访问过,递归调用DFS函数,并根据情况更新转弯次数。
        在递归调用结束后,将当前位置标记为未访问,恢复转弯次数,返回上一层。
最终返回DFS函数的结果。

程序代码:

#include <iostream>
#include <vector>
using namespace std;

const int MAX_M = 100;
const int MAX_N = 100;

int maze[MAX_M][MAX_N];
bool visited[MAX_M][MAX_N];
int m, n;
int minTurns = INT_MAX;

// 定义8个方向的偏移量
int dx[] = {-1, -1, 0, 1, 1, 1, 0, -1};
int dy[] = {0, 1, 1, 1, 0, -1, -1, -1};

bool isValid(int x, int y) {
    return x >= 0 && x < m && y >= 0 && y < n;
}

bool isDestination(int x, int y, int r, int s) {
    return x == r && y == s;
}

void DFS(int x, int y, int r, int s, int turns, int totalRooms) {
    if (isDestination(x, y, r, s)) {
        if (totalRooms + 1 == m * n) {
            minTurns = min(minTurns, turns);
        }
        return;
    }

    visited[x][y] = true;

    for (int i = 0; i < 8; ++i) {
        int nextX = x + dx[i];
        int nextY = y + dy[i];

        if (isValid(nextX, nextY) && maze[nextX][nextY] && !visited[nextX][nextY]) {
            int newTurns = (i % 2 == 0) ? turns : turns + 1;
            DFS(nextX, nextY, r, s, newTurns, totalRooms + 1);
        }
    }

    visited[x][y] = false;
}

int main() {
    cout << "请输入迷宫的行数和列数:";
    cin >> m >> n;

    cout << "请输入迷宫的具体数据(1表示可通行,0表示不可通行):" << endl;
    for (int i = 0; i < m; ++i) {
        for (int j = 0; j < n; ++j) {
            cin >> maze[i][j];
        }
    }

    int startX, startY, targetX, targetY;
    cout << "请输入罗密欧的起始位置坐标(x, y):";
    cin >> startX >> startY;
    cout << "请输入朱丽叶的位置坐标(x, y):";
    cin >> targetX >> targetY;

    DFS(startX, startY, targetX, targetY, 0, 0);

    if (minTurns == INT_MAX) {
        cout << "未找到通向朱丽叶的路径" << endl;
    } else {
        cout << "找到通向朱丽叶的路径!最少转弯次数为: " << minTurns << endl;
    }

    return 0;
}

结果:

题目(参考ChatGPT):

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

思路:

在生成最小生成树后,可以根据最小生成树的拓扑结构来确定增压器的设置。具体步骤如下:

  1. 找到最小生成树中的所有叶子节点(即只有一个邻接节点的节点),将它们作为候选的增压器位置。
  2. 对于每个候选增压器位置,计算从该位置到其他所有叶子节点的距离,并选择距离最长的一条路径。
  3. 如果选定的路径长度小于设定的最小传输距离d,则在该路径上设置增压器。
  4. 返回增压器的设置结果。

程序代码:

#include <iostream>
#include <vector>
#include <queue>
#include <climits>
using namespace std;

const int MAX_V = 100; // 最大顶点数

struct Edge {
    int v; // 目标顶点
    int weight; // 边的权重
};

vector<Edge> graph[MAX_V]; // 图的邻接表表示
bool visited[MAX_V]; // 顶点是否已访问
int minDistance[MAX_V]; // 顶点到生成树的最短距离

int prim(int start, int Pmin, int Pmax, int d) {
    priority_queue<pair<int, int>, vector<pair<int, int>>, greater<pair<int, int>>> pq;
    fill_n(minDistance, MAX_V, INT_MAX); // 初始化最短距离为无穷大

    minDistance[start] = 0; // 起始顶点到生成树的最短距离为0
    pq.push(make_pair(0, start)); // 将起始顶点加入优先队列

    int numBoosters = 0; // 增压器数量

    while (!pq.empty()) {
        int u = pq.top().second; // 当前最短距离对应的顶点
        int dist = pq.top().first; // 当前最短距离
        pq.pop();

        if (visited[u]) {
            continue;
        }

        visited[u] = true;

        if (dist > d && dist <= Pmax) { // 如果当前最短距离超过d且小于等于Pmax,则需要设置增压器
            numBoosters++;
            minDistance[u] = 0; // 将当前顶点到生成树的最短距离设为0
        }

        for (const auto& edge : graph[u]) {
            int v = edge.v;
            int weight = edge.weight;

            if (!visited[v] && weight < minDistance[v]) {
                minDistance[v] = weight;
                pq.push(make_pair(weight, v));
            }
        }
    }

    return numBoosters;
}

int main() {
    int V = 5; // 顶点数
    int E = 7; // 边数

    // 预定义每条边的起点、终点和权重
    vector<pair<pair<int, int>, int>> edges = {
        {{0, 1}, 2},
        {{0, 3}, 4},
        {{1, 2}, 1},
        {{1, 3}, 3},
        {{1, 4}, 6},
        {{2, 4}, 5},
        {{3, 4}, 7}
    };

    cout << "请输入起始顶点的编号:";
    int start = 0; // 起始顶点编号
    cout << start << endl;

    int Pmin = 3; // 最低油压 Pmin
    int Pmax = 6; // 最高油压 Pmax
    int d = 2; // 至少传输距离 d

    for (int i = 0; i < E; ++i) {
        int u = edges[i].first.first;
        int v = edges[i].first.second;
        int weight = edges[i].second;
        graph[u].push_back({v, weight});
        graph[v].push_back({u, weight});
    }

    int numBoosters = prim(start, Pmin, Pmax, d);

    cout << "需要设置的最少增压器数量为:" << numBoosters << endl;

    return 0;
}

结果:

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

辣鲨椒鱼

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

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

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

打赏作者

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

抵扣说明:

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

余额充值