无向图染色

一、题目描述

给一个无向图染色,可以填红黑两种颜色,必须保证相邻两个节点不能同时为红色,输出有多少种不同的染色方案?

二、输入输出描述

输入描述

  • 第一行:两个整数 M(节点数,1 ≤ M ≤ 15)、N(边数,0 ≤ N ≤ M×3);
  • 后续 N 行:每行两个整数 V1V2,表示 V1V2 之间存在一条无向边(节点编号默认从 0/1 开始,需统一处理);

**备注:**图中节点不一定连通,边为无向边。

输出描述

  • 一个整数,表示符合约束的染色方案总数。

三、示例

|----|-----------------------------------------------------------------------------------------|
| 输入 | 4 4 1 2 2 4 3 4 1 3 |
| 输出 | 7 |
| 说明 | 4个节点,4条边,1号节点和2号节点相连, 2号节点和4号节点相连,3号节点和4号节点相连, 1号节点和3号节点相连, 若想必须保证相邻两个节点不能同时为红色,总共7种方案。 |

|----|-----------------|
| 输入 | 3 3 1 2 1 3 2 3 |
| 输出 | 4 |
| 说明 | |

|----|-----------------|
| 输入 | 4 3 1 2 2 3 3 4 |
| 输出 | 8 |
| 说明 | |

四、解题思路

  1. 核心思想

采用DFS + 回溯 + 剪枝 的策略,枚举所有可能的红色节点子集,通过「邻接表快速判断相邻」和「按顺序枚举避免重复」,剪枝掉 "红色节点相邻 " 的非法子集,最终统计所有合法染色方案数。核心是 "枚举不重复、剪枝提效率、回溯复状态"。

  1. 问题本质分析
  • 表层问题:统计无向图中「红色节点互不相邻」的染色方案数;
  • 深层问题:
  1. 组合枚举问题:本质是求图的「独立集」总数(独立集:任意两点不相邻的顶点子集,包含空集);

  2. 重复规避问题:按节点编号升序枚举(i 从 index 开始),避免同一子集的不同排列被重复计数;

  3. 效率优化问题:通过「已选节点的邻居集合」快速剪枝,无需遍历所有已选节点判断相邻,减少无效递归;

  4. 状态复用问题:回溯时恢复path状态,保证不同分支的枚举互不干扰。

  5. 核心逻辑

  • 图的构建:用邻接表存储每个节点的邻居,支持快速相邻判断;
  • 初始计数:默认全黑方案(count=1);
  • DFS 枚举:从节点 1 开始,按升序枚举每个节点是否选作红色;
  • 剪枝规则:若当前节点与已选红色节点相邻,跳过该节点;
  • 计数更新:合法节点选作红色时,计数 + 1;
  • 回溯恢复:递归后移除当前节点的邻居集合,恢复 path 状态,继续枚举下一分支。
  1. 步骤拆解

  2. 图的构建

    • 读取节点数、边数、所有边,构建无向图的邻接表(每个节点对应其邻居集合)。
  3. DFS 初始化

    • 启动 DFS,参数:邻接表、节点总数 m、起始枚举节点 index=1、初始计数 count=1(全黑方案)、空 path(无红色节点)。
  4. DFS 枚举与剪枝

    • 循环枚举节点 i(从 index 到 m):
    1. 剪枝检查:遍历 path(已选红色节点的邻居集合),若 i 在任意集合中,跳过该节点;
    2. 计数更新:count+1(新增「选 i 为红色」的合法方案);
    3. 递归处理:
      • 若 i 有邻居:将其邻居集合加入 path,递归枚举 i+1 开始的节点;
      • 若 i 无邻居:直接递归枚举 i+1 开始的节点;
    4. 回溯恢复:递归结束后,移除 path 中 i 的邻居集合(若有),恢复状态。
  5. 结果返回

    • 枚举完所有节点后,返回最终 count,即为合法方案总数。

五、代码实现

java 复制代码
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.Scanner;

public class Main {
  public static void main(String[] args) {
    Scanner sc = new Scanner(System.in);

    int m = sc.nextInt();
    int n = sc.nextInt();

    int[][] edges = new int[n][2];
    for (int i = 0; i < n; i++) {
      edges[i][0] = sc.nextInt();
      edges[i][1] = sc.nextInt();
    }

    System.out.println(getResult(edges, m));
  }

  /**
   * @param edges 边,即[v1, v2]
   * @param m 点数量
   * @return
   */
  public static int getResult(int[][] edges, int m) {
    // connect用于存放每个节点的相邻节点
    HashMap<Integer, HashSet<Integer>> connect = new HashMap<>();

    for (int[] edge : edges) {
      connect.putIfAbsent(edge[0], new HashSet<>());
      connect.get(edge[0]).add(edge[1]);

      connect.putIfAbsent(edge[1], new HashSet<>());
      connect.get(edge[1]).add(edge[0]);
    }

    // 节点从index=1开始,必有count=1个的全黑染色方案
    return dfs(connect, m, 1, 1, new LinkedList<>());
  }

  // 该方法用于求解给定多个节点染红的全组合数
  public static int dfs(
      HashMap<Integer, HashSet<Integer>> connect,
      int m,
      int index,
      int count,
      LinkedList<HashSet<Integer>> path) {
    if (path.size() == m) return count;

    outer:
    for (int i = index; i <= m; i++) {
      // 如果新加入节点i和已有节点j相邻,则说明新加入节点不能染成红色,需要进行剪枝
      for (HashSet<Integer> p : path) {
        if (p.contains(i)) continue outer;
      }

      count++;

      if (connect.containsKey(i)) {
        path.addLast(connect.get(i));
        count = dfs(connect, m, i + 1, count, path);
        path.removeLast();
      } else {
        count = dfs(connect, m, i + 1, count, path);
      }
    }

    return count;
  }
}
相关推荐
蜗牛去旅行吧2 小时前
面试宝典集锦
面试·职场和发展
J_HelloWorld2 小时前
缺页中断:Java高性能存储的隐形推手
java·缺页中断
一代明君Kevin学长2 小时前
记录一个上手即用的Spring全局返回值&异常处理框架
java·网络·python·spring
坚持就完事了2 小时前
扫描线算法
算法
鱼跃鹰飞2 小时前
Leetcode尊享面试100题:252. 会议室
算法·leetcode·面试
教游泳的程序员2 小时前
【面试问题精选】java开发工程师
python·面试·职场和发展
悟空码字2 小时前
SpringBoot整合MyBatis-Flex保姆级教程,看完就能上手!
java·spring boot·后端
程序员-King.2 小时前
二分查找——算法总结与教学指南
数据结构·算法
爬山算法2 小时前
Hibernate(43)Hibernate中的级联删除如何实现?
java·python·hibernate