图的m着色问题(回溯法)(C++)

题目解释

题目要求解决 图的m着色问题。给定一个无向图 和 种不同的颜色,你需要为图的每个顶点分配一种颜色,且满足以下条件:

每条边的两个端点必须着不同的颜色

目标:找出所有合法的着色方案,并输出其数量。

输入格式:

1. 第一行给出三个整数:(顶点数)、(边数)、(可用颜色数)。

2. 接下来 行,每行包含两个整数 和 ,表示图中一条边()。这是一个无向图,因此边 和 被视为相同。

输出格式:

• 输出一个整数,表示所有不同的合法着色方案的数量。

示例解析:

输入

5 8 4

1 2

1 3

1 4

2 3

2 4

2 5

3 4

4 5

这个图有 5 个顶点(编号从 1 到 5)和 8 条边。我们有 4 种颜色可以为每个顶点着色。

图结构:

      1

     /|\

    2 3 4

     \|/

      5

输出

48

意味着有 48 种不同的着色方案可以满足题目要求,即每条边的两个顶点的颜色不同。

代码解释

接下来我们一步步解释代码的实现。

1. 定义数据结构

int n, e, m; // n: 顶点数,e: 边数,m: 可用颜色数

vector<vector<int>> adj; // 图的邻接表

vector<int> colors; // 记录每个顶点的颜色

int result = 0; // 记录有效的着色方案数

n: 顶点数。

e: 边数。

m: 可用的颜色数。

adj: 邻接表,表示图中每个顶点的邻接顶点。

colors: 记录每个顶点的颜色,初始时顶点没有被着色,默认值是 0。

result: 记录合法的着色方案总数。

2. 读取输入数据并构建图

cin >> n >> e >> m; // 读取顶点数、边数和颜色数

adj.resize(n); // 初始化邻接表

colors.resize(n, 0); // 初始化颜色数组,0表示未着色



for (int i = 0; i < e; i++) {

    int u, v;

    cin >> u >> v;

    adj[u - 1].push_back(v - 1); // 无向图,所以要同时添加两条边

    adj[v - 1].push_back(u - 1);

}

• 通过 cin 读取顶点数 、边数 和颜色数 。

• 初始化邻接表 adj,其大小为 ,每个顶点的邻接顶点列表开始为空。

• 使用 resize 初始化颜色数组 colors,初始值为 0,表示所有顶点均未着色。

• 使用 for 循环输入图中的边,将边的信息填充到邻接表中。需要注意的是,输入的顶点编号是从 1 开始的,但是数组的索引从 0 开始,因此我们将顶点编号减 1。

3. 回溯算法

3.1 合法性检查函数 isValid

bool isValid(int vertex, int color) {

    for (int neighbor : adj[vertex]) {

        if (colors[neighbor] == color) {

            return false;

        }

    }

    return true;

}

• isValid 函数用于检查当前顶点 vertex 是否可以涂上颜色 color。

• 我们检查顶点 vertex 的所有邻接顶点(在 adj[vertex] 中),如果某个邻接顶点已经着了相同的颜色,则当前选择的颜色 color 就不合法,返回 false。

• 如果所有邻接顶点都没有着相同的颜色,则返回 true,表示颜色合法。

3.2 回溯函数 backtrack

void backtrack(int vertex) {

    if (vertex == n) { // 如果所有顶点都已着色

        result++; // 找到一种有效的着色方案

        return;

    }

    

    // 尝试为当前顶点vertex选择不同的颜色

    for (int color = 1; color <= m; color++) {

        if (isValid(vertex, color)) { // 如果当前颜色合法

            colors[vertex] = color; // 为当前顶点着色

            backtrack(vertex + 1); // 递归处理下一个顶点

            colors[vertex] = 0; // 回溯,撤销选择

        }

    }

}

• backtrack 函数用于尝试为每个顶点选择合法的颜色,并递归地为下一个顶点选择颜色。

终止条件:当 vertex == n 时,表示所有顶点都已经着色,且这是一个合法的着色方案,因此我们将 result 增加 1。

颜色选择:对于每个顶点,尝试所有 1 到 的颜色。如果某个颜色合法(通过 isValid 检查),则为当前顶点着色,并递归地尝试下一个顶点。

回溯:递归调用结束后,恢复当前顶点的颜色为 0,即撤销颜色的选择,以尝试其他可能的选择。

4. 开始回溯并输出结果

backtrack(0);

cout << result << endl; // 输出结果

• 从第一个顶点(顶点 0)开始回溯,尝试为所有顶点选择颜色。

• 当回溯完成后,输出所有合法的着色方案数。

复杂度分析:

1. 时间复杂度

• 每个顶点有 种可能的颜色选择,因此在最坏情况下,回溯算法的时间复杂度是 ,其中 是顶点数, 是颜色数。

• 因为每个顶点都可以有 种选择,所以总的回溯搜索树的规模是 。

2. 空间复杂度

• 主要是图的邻接表和颜色数组的存储空间,空间复杂度是 ,其中 是顶点数, 是边数。

• 邻接表需要存储 条边,颜色数组需要存储 个顶点的颜色。

总结:

这道题目要求我们使用回溯算法来解决图的着色问题。回溯算法的核心思想是尝试所有可能的着色方式,并检查每种方式是否合法。通过递归和回溯,我们可以遍历所有顶点的颜色选择,从而计算出所有合法的着色方案。

完整代码

#include <iostream>
#include <vector>

using namespace std;

int result = 0; // 存储合法的着色方案数
vector<int> path; // 存储每个顶点的颜色

// 判断当前的着色是否合法
bool isValid(const vector<vector<int> >& graph, int x, int color) {
    for (size_t i = 0; i < graph[x].size(); ++i) {
        int neighbor = graph[x][i];
        if (path[neighbor] == color) {
            return false; // 如果相邻的顶点有相同的颜色,返回false
        }
    }
    return true;
}

// 回溯函数
void backtrack(int n, const vector<vector<int> >& graph, int x, int m) {
    if (x == n) { // 如果所有顶点都已经着色
        result++; // 找到一种合法的着色方案
        return;
    }

    // 尝试为顶点x选择不同的颜色
    for (int i = 1; i <= m; i++) { // 遍历颜色1到m
        if (isValid(graph, x, i)) { // 如果当前着色合法
            path[x] = i; // 为顶点x着色
            backtrack(n, graph, x + 1, m); // 递归处理下一个顶点
            path[x] = 0; // 回溯,撤销选择
        }
    }
}

int main() {
    int n, e, m;
    cin >> n >> e >> m; // 输入顶点数、边数和颜色数
    vector<vector<int> > graph(n); // 初始化邻接表

    // 读取图的边
    for (int i = 0; i < e; i++) {
        int u, v;
        cin >> u >> v;
        graph[u - 1].push_back(v - 1); // 无向图,所以添加两条边
        graph[v - 1].push_back(u - 1);
    }

    path.resize(n, 0); // 初始化path数组,0表示未着色
    backtrack(n, graph, 0, m); // 从第0个顶点开始回溯
    cout << result << endl; // 输出合法的着色方案数
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值