日撸 Java 三百行(35 天: 涂色问题)

article/2025/9/11 23:10:16

注意:这里是JAVA自学与了解的同步笔记与记录,如有问题欢迎指正说明

目录

一、关于涂色问题

二、代码实现思路

三、代码实现过程

1、初始化

2、核心代码(DFS部分)

3、染色合理性判断

四、数据模拟

总结


一、关于涂色问题

涂色问题是一类非常经典的组合排列的问题,想必诸位很多在高中就接触过这类问题。我这里以一道题目的形式让大家回顾下这类经典问题:

         现在有上述的地图区域,为了明显表示地图边界,我们要求给地图每个区域图上不同颜色,但由于色彩优先,只能用5种不同的颜色给图中标A、B、C、D的各部分涂色,每部分只涂一种颜色,相邻部分涂不同颜色以保证区分,则不同的涂色方法有多少种?

这类问题的解法在高中也许我们已经驾轻就熟了,简单来说就是枚举。我们先假设A选择5个颜色当中的其中一种,这样就有5种情况;而B这个时候先不管C怎么样,只知道A已经使用了一个颜色,那么相邻的B只能选择A用剩下的其中4个中的一个;C同理先不管D怎么样,它只能在A、B用剩下的色彩中选择一个,显然是3中情况;D同理4种。综上总情况为5*4*3*4=240种。

        今天特地在图的练习中提到这个问题,显然,这类问题具有非常明显的数据结构图的特征。因为这种地图区域之间紧密相连的关系等同于图当中的结点相连,具体来说,因为这个相邻是双向的,所以对应于我们的无向图结构。就如下图所示:

         假设今天的任务是:用代码求出每种染色的组合,你会这么想?我们当时写这道题的时候采用了枚举的思路,实际在程序中也是如此。我们会采用具有枚举搜索性质的方法去完成这个过程,我们俗称为“ 暴力 ”解法。因此对于图结构的枚举,同时又涉及暴力,因此自然而然得出结论:使用DFS完成。

二、代码实现思路

        首先抽象下问题:涂色问题可以抽象为一个无向图,这个无向图每个结点可以携带一个权值作为图的颜色,然后任何一个结点的权值必须与其相邻结点不同;然后确定下我们的信息载体,我们可以按照结点的序号依次给出每个结点的权值(颜色),这样就无需额外设置一个图结构去存储颜色信息;最后确定数据类型特征,色彩不用专门用字符去说明,我们默认以自然序列0,1,2,...,n去表示某个颜色就好(我们不考虑这些下标与哪些颜色对应),默认颜色标记数组默认为-1表示没有尚未染色,同时线性表下标索引与结点序号对应(在下图看来就是ABCD)。

        虽然这道题计算采用的是DFS思路同时又涉及图的模拟,但其完成的套路却并不是基于DFS的结点遍历。通过上面的分析,算法目标是得到一个颜色标记数组paraCurrentColoring,因为这个数组与区域通过下标对应,能指导我们染色。正好,便可只通过遍历颜色数组,得到对应下标的结点,然后判断结点相连的其余结点的染色情况从而决定当前结点的颜色。总结来看,本题虽然涉及图,但是本质上还是一个线性DFS的枚举问题,图的作用只体现于:线性枚举的规则是基于图的连通性的。        

        我们在刚刚分析涂色问题的时候,我做了一个比较明显的强调(划线部分),我们在对一个涂色区域进行考虑的时候忽略了其余的我们不关心的部分,所以在程序实现考虑冲突的时候只用考虑已经涂色的区域就好了。介于本算法计划对颜色数组从左至右搜索,若当前位于颜色标记数组的\(i\)位置,那么这时,任意位置\(k(0\leqslant k <  i)\)都是已经被染色过的,所以我们考虑连通问题只需要考虑\(k\)结点就好。

三、代码实现过程

1、初始化

	/************************ Coloring. Output all possible schemes.* * @param paraNumColors The number of colors.**********************/public void coloring(int paraNumColors) {// Step 1. Initialize.int tempNumNodes = connectivityMatrix.getRows();int[] tempColorScheme = new int[tempNumNodes];Arrays.fill(tempColorScheme, -1);coloring(paraNumColors, 0, tempColorScheme);}// Of coloring

        很多搜索问题都需要基本的搜索资料准备,常见的方法便是如此在搜索递归部分的外围套一个“套子”。在这个套子里面,我们可以完成众多基本的初始化(这里完成了对于颜色标记数组的初始化),同时也增加了递归部分的扩充性和维护性。

2、核心代码(DFS部分)

	/************************ Coloring. Output all possible schemes.* * @param paraNumColors The number of colors.* @param paraCurrentNumNodes The number of nodes that have been colored.* @param paraCurrentColoring The array recording the coloring scheme.**********************/public void coloring(int paraNumColors, int paraCurrentNumNodes, int[] paraCurrentColoring) {// Step 1. Initialize.int tempNumNodes = connectivityMatrix.getRows();System.out.println("coloring: paraNumColors = " + paraNumColors + ", paraCurrentNumNodes = "+ paraCurrentNumNodes + ", paraCurrentColoring" + Arrays.toString(paraCurrentColoring));// A complete scheme.if (paraCurrentNumNodes >= tempNumNodes) {System.out.println("Find one:" + Arrays.toString(paraCurrentColoring));return;} // Of if// Try all possible colors.for (int i = 0; i < paraNumColors; i++) {paraCurrentColoring[paraCurrentNumNodes] = i;if (!colorConflict(paraCurrentNumNodes, paraCurrentColoring)) {coloring(paraNumColors, paraCurrentNumNodes + 1, paraCurrentColoring);} // Of if} // Of for i}// Of coloring

        可以发现,这个函数是上一个函数的重载,这样写可以避免相同功能方向各种命名不好管理,当然这个看自己的习惯吧。

        DFS的递归体通常由二个部分构成,自上而下分别为(此结构忽略部分初始化操作):

  1. 条件退出判断部分
  2. 循环找分支部分(多用for)

        具体实现中,这两个部分可以玩出许多花样。比如,可以把队当前函数部分的访问操作放到第1与第2步中间或者第一步之前;第二步操作中,在函数入口后方放的内容可以用作回溯时特别执行的代码(本代码不需要,进程用于解除标记等作用)

        本递归题使用paraCurrentNumNodes表示当前线性部分的索引下标,递归体以此作为递归的深入标识,因此条件退出部分就是判断当前paraCurrentNumNodes是否越界。

        访问部分在退出入口之上,因为代码目标在于枚举所有染色编码,故访问操作为基本打印。

        循环分支部分分别枚举可用的颜色个数(paraNumColors)并将颜色记录在颜色标记数组中,并且判断染色是否合理,合理便进入下一步染色,进入递归体,递归的深入标识+1。这里“ 判断染色是否合理 ”的操作专门用一个函数封装了,见下。

3、染色合理性判断

        染色的合理性判断在第二部分已经描述过了,只需要判断颜色标记的线性数组当前下标之前的部分是否构成连通关系即可:

	/************************ Coloring conflict or not. Only compare the current last node with previous* ones.* * @param paraCurrentNumNodes The current number of nodes.* @param paraColoring        The current coloring scheme.* @return Conflict or not.**********************/public boolean colorConflict(int paraCurrentNumNodes, int[] paraColoring) {for (int i = 0; i < paraCurrentNumNodes; i++) {// No direct connection.if (connectivityMatrix.getValue(paraCurrentNumNodes, i) == 0) {continue;} // Of ifif (paraColoring[paraCurrentNumNodes] == paraColoring[i]) {return true;} // Of if} // Of for ireturn false;}// Of colorConflict

         这里采用的正向判断的逆判断,也就是冲突判断,简单来说,如果返回true说明染色不合理。for循环中先是筛选出连通部分,之后再判断颜色。

四、数据模拟

        为图的连通性过于复杂不方便验证,故采用了一个相对简单的无向图来模拟

        可见无向图的邻接矩阵是个严格的对称矩阵。 

         单元测试代码:

	/************************ Coloring test.**********************/public static void coloringTest() {int[][] tempMatrix = { { 0, 1, 1, 0 }, { 1, 0, 0, 1 }, { 1, 0, 0, 0 }, { 0, 1, 0, 0 } };Graph tempGraph = new Graph(tempMatrix);//tempGraph.coloring(2);tempGraph.coloring(3);System.out.println(tempGraph);}// Of coloringTest

        输出结果(只显示部分)

总结

        染色问题是个枚举问题,这个过程比较繁琐,但交给计算机的暴力搜索来看似乎就很方便。现实中的枚举现象不在少数,虽然部分问题可以用到组合数学的排列组合思想去计算,但是理论性的东西有时无法适应很灵活的环境。因此枚举依旧是个关键的方法,往往来说,计算机是实现这些方法的关键,毕竟现实生活中的大体量的枚举问题是不可能花时间人力枚举的。

        因此要用计算机去处理真实的现实问题,必须要学会的就是怎么用计算机去枚举和暴力搜索数据,这些应当作为数据抽象化后进行数据处理的基础思想。同时暴力的算法往往是优化的墙梁,因为在暴力算法实现的过程中总会显露出特定问题的特征,这些特征是在单独死扣问题时不容易发现的。这个过程我们习惯称之为剪枝


http://chatgpt.dhexx.cn/article/U43R2kDy.shtml

相关文章

cnpm 安装yarn

cnpm 安装yarn 一句命令搞定 cnpm install -g yarn --registryhttps://registry.npm.taobao.org再配置下源 yarn config set registry https://registry.npm.taobao.org -gyarn config set sass_binary_site http://cdn.npm.taobao.org/dist/node-sass -g下面是官网提供的两…

npm和cnpm安装配置

在安装目录D:\programIntall\nodejs 新建node_global和node_cache 两个文件夹 npm config set prefix "D:\programIntall\nodejs\node_global"npm config set cache "D:\programIntall\nodejs\node_cache"在系统环境变量中添加NODE_PATH 在系统环境变量 P…

mac安装cnpm安装失败

参考了网上一篇博客 完成的安装。写下来纯属方便自己以后好找 也方便更多人看到。 官网安装node npm install -g cnpm --registryhttps://registry.npm.taobao.org 如果报一堆warn说明安装失败 依次输入 npm set registry https://registry.npm.taobao.org npm set dist…

git修改历史提交(commit)信息

我们在开发中使用git经常会遇到想要修改之前commit的提交信息&#xff0c;这里记录下怎么使用git修改之前已经提交的信息。一、修改最近一次commit的信息 首先通过git log查看commit信息。 我这里一共有6次commit记录。 最新的commit信息为“Merge branch ‘master’ of https:…

win7更改计算机属性,win7修改系统属性OEM信息的方法

win7修改系统属性OEM信息的方法分析给大家&#xff0c;我们都知道更改电脑属性里面OEM信息&#xff0c;让电脑更加个性化&#xff0c;OEM就是代工的意思&#xff0c;OEM版一般是Windows赋予合作伙伴在生产电脑是可以预装的系统。但是很多用户不知道如何修改OEM属性信息。本文系…

git 修改远端 commit 信息

git 修改远端 commit 信息 git rebase -i HEAD~x( x 代表最近几条commit )&#xff0c;执行之后将出现以下界面上面的 pick 后面即远端的 commit 信息&#xff0c;最下面的是最后的 commit修改指定 commit 的 pick 为 edit ,然后 wq 保存退出根据提示信息执行 git commit --am…

linux 更改cpu信息,奸商要疯狂,新软件任意修改英特尔CPU信息

昨日&#xff0c;网上出现一款名为“英特尔处理器信息更新”软件&#xff0c;支持任意修改英特尔CPU信息&#xff0c;例如内部型号和频率&#xff0c;支持写入到主板BIOS&#xff0c;无需更改注册表&#xff0c;这次&#xff0c;广大用户以后买新机器要注意&#xff0c;奸商们不…

Mysql数据库和数据表的创建和信息更改的常用指令

文章目录 数据库和数据表的创建和信息更改后续小实验做准备一. 关于数据库和数据表的其它操作1&#xff09;数据库①创建数据库②显示目前所有的数据库③数据库重命名2.1 先创建新库&#xff1a;2.2 使用RENAME TABLE 命令修改表名&#xff0c;将表移动到新的库里&#xff1a;2…

如何修改PPT文档的原标题和作者信息

1.将鼠标放在PPT上可以看到原作者和标题的信息 2.右键PPT&#xff0c;选择属性 3.进入属性面板&#xff0c;点击详细信息选项卡&#xff0c;进入详细信息&#xff0c;可以看到作者和标题一栏。鼠标左键单击作者栏位或标题一栏&#xff0c;形成可编辑状态&#xff0c;直接修改…

win10计算机信息更改图,win10修改版本信息的简单方法【图文教程】

在某些特殊情况下&#xff0c;我们需要修改win10系统的版本信息&#xff0c;一般系统版本信息是本身就设置好的&#xff0c;能不能随意修改&#xff1f;大部分用户心理都没底&#xff0c;其实Win10系统版本号是可以任意修改&#xff0c;知识要掌握对的方法&#xff0c;如果你有…

web前端 | 博客(八)用户信息修改功能

用户信息修改功能 当点击用户后面的按钮时&#xff0c;要跳转到用户信息修改页面。而修改和添加实际上是同一个页面。 要区分跳转后是添加操作还是修改操作&#xff0c;在于携带的参数。 如果是添加操作&#xff0c;那就直接跳转过去&#xff1b;如果是修改操作&#xff0c;那…

SSM框架下对信息执行修改操作时的信息弹窗回显以及对信息修改后对数据库的更新问题

SSM框架下对信息执行修改操作时的信息弹窗回显以及对信息修改后的同步问题 概括主要说一下前端的实现 概括 今天在做实训作业时&#xff0c;有个对数据信息进行修改的操作&#xff0c;要求点击修改按钮后弹出修改框&#xff0c;栏目中需要显示出旧的数据信息&#xff0c;当将输…

svn修改提交日志信息

参考&#xff1a;唐小码个人博客 一、svn修改提交的msg信息和作者信息 鼠标右键找到show log> 选择要修改的日志行&#xff0c;第一个是修改作者信息&#xff0c;第二个是修改日志信息 二、svn修改提交的日期信息 修改日期信息的话&#xff0c;你得先有svn服务器的权限&…

【JSP】用户信息界面操作 ---- 用户信息修改

文章目录 用户信息界面操作 ---- 用户信息修改Ⅰ.修改userinfo.jsp 实现修改页面跳转Ⅱ.创建 userUpdate.jsp 修改页面Ⅲ.完善 dbHelper类&#xff0c;添加用户修改方法Ⅳ.创建 upDataServlet&#xff0c;实现用户信息修改功能Ⅴ.效果展示 用户信息界面操作 ---- 用户信息修改 …

windows本地git账户信息修改

git账户发生变化&#xff0c;比如密码修改或者项目种账户进行替换本地账户如何修改和切换&#xff1f; 1、进入控制面板 2、点击 用户账户 3、点击管理“管理Windows凭据”&#xff0c;进入凭据管理界面 4、选择需要修改的git账户对应的地址&#xff0c;点击右侧的箭头&#x…

Android设备信息修改器,如何更改android手机的设备号信息

工具/原料 手机信息修改器 手机已root Xposed框架 方法/步骤 首先我们看在没使用手机信息修改器(琢石模拟器)的情况下&#xff0c;手机的串号是多少&#xff0c;可以看到这个机器的串号是空的。 打开手机信息修改器(琢石模拟器)&#xff0c;进入虚拟环境中&#xff0c;一键生成…

Git系列之修改历史提交信息

文章の目录 1、查看 git 提交记录2、修改最近两个或者两次上的commit信息3、扩展&#xff1a;修改上一次git commit 提交的信息参考写在最后 1、查看 git 提交记录 git log2、修改最近两个或者两次上的commit信息 比如我这里有三次提交 使用命令&#xff1a; git rebase -i…

ssh服务器banner信息,几种情况下的banner信息修改

一.telnet 当telnet建立连接時&#xff0c;修改主机会出现的提示信息 1.先编辑一個要显示的信息 # vi /etc/telnet.msg this is a telnet message! 2.在/etc/inetd.conf中的telentd后加上-b 选项&#xff0c;指定包含信息的文件 telnetd stream tcp nowait root /usr/lbin/teln…

如何更改计算机的用户信息,如何更改电脑的账户信息

电脑已经成为生活中必不可少的工具之一&#xff0c;电脑中存放重要的数据资料等&#xff0c;因此保护我们的系统安全是非常重要的事。设置电脑的账户信息可以提升电脑的安全性能&#xff0c;对帐号定期管理也是很有必要的。下面小编就教教大家怎么更改电脑的账户信息。 步骤如下…

修改计算机基本信息,windows10系统下怎样更改基本信息中的制造商型号

如果用户是使用GHOST版安装的windows10系统&#xff0c;那么计算机基本信息里的型号都会提供商的信息&#xff0c;型号也会一样。有些朋友觉得不爽&#xff0c;就想要如何修改WIN10里基本信息中的制造商型号。这该如何操作呢&#xff1f;接下来&#xff0c;就随小编看看具体操作…