C++ 算法进阶系列之剖析树型动态规划算法思想

news2024/12/27 0:01:55

1. 前言

什么是树型动态规划?

概念中有 2 个子概念:

  • 一个是动态规划概念。动态规划可以简单理解为通过对已经计算出来的子问题的状态值进行修改(基于子问题的状态值找到当前子问题的最优值)而得到当前子问题的状态值。

Tips: 本文侧重于动态规划在树型结构中的应用,更多关于动态规划的基础理论和常规知识,可以翻阅相关资料。

  • 指树数据结构。树型动态规划算法指在树逻辑结构之上提供的动态规划思想。

动态规划最重要环节查找到子问题间发生变化的状态量以及状态转移表达式。树型动态规划的状态转移过程一般都是由子树向根结点转移,这也符合动态规划的由底向上的逻辑思想。

本文通过讲解几个经典的树型动态规划案例,揭开其神秘面纱。

2. 经典案例

2.1 没有上司的舞会

没有上司的舞会是树型动态规划最经典的案例。下文将剖析此案例的细节,从而深度理解树型动态规划的逻辑流程。

问题描述:

某公司有 n 个职员,编号为 1∼N。公司吗,必然存上、下级关系。员工之间的关系从逻辑结构而言,就是典型的树结构。

在树结构中,父结点是子结点的直接上司。如下图所示:

2.png

现在有个联欢舞会会,每邀请来一个职员都会给舞会增加一定的快乐指数,但是,如果某个员工的上司来参加舞会了,那么这个职员不会过来参加舞会。

题目要求,邀请哪些员工可以使整个舞会的快乐指数最大。

问题分析:

分析题目可得到如下的的一些信息:

**第一:**每一个员工都有一个权重:快乐指数。如下图,节点括号内数字表示快乐指数。

3.png

**第二:**此问题是否能使用动态规划算法思想?

动态规划要求有子问题且须有最优子结构。

  • 树结构本身就是子树的逻辑结构,子问题是天然存在的。
  • 至于有没有最优子结构,可以把问题规模先缩小。

如下图所示,现在是一个上司两个下属。

4.png

站在邀请方可有 2 种方案:

  • 邀请总经理节点,因此节点是树中其它 2 个节点的父节点,一旦邀请它,子结点不会参加。此时的快乐指数为 10。此种方案是不是最优方案,现还不能下结论。
  • 不邀请总经理,显然市场总监的最优值是11,技术总监的最优值是12。 邀请市场总监技术总监后快乐指数为 23

从现状而言(仅3 个节点),显然选择不邀请父结点能得到最大快乐指数:23

通过分析可知:

当知道子结点的最优值后可以判断出当前结点的最终结论(是去得到最大值还是不去得到最大值)。所以,此问题是存在最优子结构的,并且符合动态规划的由下向上的求解思想。

基于动态规划思想,先计算最底层节点的最优快乐指数,然后一层一层向上更新父节点的快乐指数。最终得到问题的最优解。如下图所示:

5.png

对于任何一个节点而言,都会有 2 个最优状态值。

  • 如果去:提供的快乐指数就是自身指数加上子节点不去时的快乐指数值。
  • 如果不去:自身快乐指数加上子节点去还是不去两者间的较大值。

可以借助一个二维数组存储每个节点的状态值。

6.png

编码实现:

编码的核心逻辑:

  • 使用 DFS(深度搜索)算法由叶节点最优值向上更新至父节点的最优值。
  • 更新(状态)值存储在二维状态(dp)数组中。

设计流程:

#include <iostream>
#include <vector>
using namespace std;
/*
*节点类
*/
struct Emp {
	//员工编号
	int empId;
	//员工姓名
	string empName;
	//权重:快乐指数
	int happy;
	Emp() {

	}
	Emp(int empId,string empName,int happy) {
		this->empId=empId;
		this->empName=empName;
		this->happy=happy;
	}
};

没有上司的舞会封装类:

/*
*树(舞会)类
*/
class Dance {
	private:
		//使用一维数组存储所有员工
		Emp emps[100];
		//存储员工之间的从属关系
		vector<int> relationship[100];
		//员工数量
		int empCount;
		//员工编号,内部维护
		int num;
		//二维状态数组,行号为员工编号
		int happys[100][2];
	public:
		Dance() {
		}
		Dance(int empCount) {
			this->num=0;
			//员工数量
			this->empCount=empCount;
		}

		/*
		* 添加新员工
		* 返回员工的编号
		*/
		int addEmp(string empName,int happy) {
			Emp emp(this->num,empName,happy);
			this->emps[this->num]=emp;
			return 	this->num++;;
		}

		/*
		*添加员工之间从属关系
		*/
		void addRelation(int from,int to) {
			this->relationship[from].push_back(to);
		}

		/*
		*规定编号为 0 的员工为根节点
		*/
		int getRoot() {
			return 0;
		}

		/*
		* 深度搜索实现
		*树型动态规划
		*/
		void treeDp(int empId) {
			//不去
			happys[empId][0]=0;
			//去
			happys[empId][1]=this->emps[empId].happy;
			for(int subEmpId: this->relationship[empId]) {
				//基于子节点深度搜索
				treeDp(subEmpId) ;
				//搜索完毕 ,更新状态值,如果不去,查找子节点去与不去的最大值
				happys[empId][0] += max(happys[subEmpId][0],happys[subEmpId][1]);
				//去,添加子节点不去时的状态值
				happys[empId][1]+=happys[subEmpId][0];
			}
		}
        /*
        *输出状态值
        */
		void maxHappy() {
			cout<<"各节点的快乐指数状态值:"<<endl; 
			for(int i=0; i<this->num; i++) {
				for(int j=0; j<2; j++) {
					cout<<this->happys[i][j]<<"\t";
				}
				cout<<endl;
			}
			cout<<"最大快乐指数:"<< max(this->happys[0][0],this->happys[0][1] )<<endl;
		}
};

测试:

int main() {
	Dance dance(14);
	int root= dance.addEmp("总经理",10) ;
	int sczj= dance.addEmp("市场总监",11) ;
	dance.addRelation(root,sczj);
	int jszj= dance.addEmp("技术总监",12) ;
	dance.addRelation(root,jszj);
	int yyzg= dance.addEmp("运营主管",13) ;
	dance.addRelation(sczj,yyzg);
	int qdzg= dance.addEmp("渠道主管",14) ;
	dance.addRelation(sczj,qdzg);
	int yfzg= dance.addEmp("研发主管",15) ;
	dance.addRelation(jszj,yfzg);
	int sszg= dance.addEmp("实施主管",16) ;
	dance.addRelation(jszj,sszg);
	int yg= dance.addEmp("员工张三",17) ;
	dance.addRelation(yyzg,yg);
	yg= dance.addEmp("员工李四",18) ;
	dance.addRelation(yyzg,yg);
	yg= dance.addEmp("员工王五",9) ;
	dance.addRelation(yyzg,yg);
	yg= dance.addEmp("员工胡六",8) ;
	dance.addRelation(qdzg,yg);
	yg= dance.addEmp("员工周皮",7) ;
	dance.addRelation(yfzg,yg);
	yg= dance.addEmp("员工李杰",19) ;
	dance.addRelation(sszg,yg);
	yg= dance.addEmp("员工吴用",20) ;
	dance.addRelation(sszg,yg);
	dance.treeDp(root);
	dance.maxHappy();
	return 0;
}

输出结果:

7.png

2.2 没有上司的舞会的升级版

问题描述:

在上述没有上司的舞会的案例中,添加如下的限制。由于场地有大小限制,场地最多只能容纳 m(1≤m≤n) 个人。请求出快乐值最大是多少。

问题分析:

相比较上一个问题,对于每一个员工,都有去或不去的选择。上文使用二维数组存储当编号为 i的员工去或不去时的快乐指数状态值。此问题多了一个人数限制,对于每一个员工(节点)而言,除了考虑去或不去,还需要考虑他以及他的下属一共有多少人参加舞会。

所以,需要添加一个新的维度,团队中参加舞会的人数。如下图所示:

8.png

如果员工编号使用 i表示,人数使用 k,去或不去使用 j表示,则状态数组happys[1][1][0]表示编号为1的员工不去时,且人数限制为 1的最优快乐指数。

知道状态信息后,需要找出状态转移方程式。

//编号为 i 的员工不去。p 取值范围为 0~k
f[i][k][0] = max(f[i][k][0], f[i][k - l][0] + max(f[子节点编号][p][0], f[子节点编号][p][1]));
//编号为 i  的员工去。
f[i][k][1] = max(f[i][k][1], f[i][k - l][1] + f[子节点编号][p][0]);

编号实现:

在上面代码基础之上,修改 2 处位置,一个是状态数组。一个是深度搜索算法。

//省略……

/*
*节点类 省略……
*/

/*
*树(舞会)类
*/
class Dance {
	private:
         //省略……
		//三维状态数组,行号为员工编号
		int happys[100][100][2];
	public:

         //省略…… 
		/*
		* 深度搜索实现
		* 树型动态规划
		* empId:  当前员工编号 
		* count:  限制人数
		*/
		void dfs(int empId,int count) {
			//对于当前节点:去但是人数限制为 0
			happys[empId][0][1] = 0;
			for (int k =count; k; --k)
				//对于当前节点:去,人数限制不同的时候的状态值
				happys[empId][k][1] = happys[empId][k - 1][1] +this->emps[empId].happy;
			//查询子节点信息
			for(int subEmpId: this->relationship[empId]) {
				//基于子节点深度搜索
				dfs(subEmpId,count) ;
				for (int k = count; k >= 0; --k)
					for (int l = 0; l <= k; l++) {
                          //不去
						happys[empId][k][0] = max(happys[empId][k][0], happys[empId][k - l][0] + max(happys[subEmpId][l][0], happys[subEmpId][l][1]));
						happys[empId][k][1] = max(happys[empId][k][1], happys[empId][k - l][1] + happys[subEmpId][l][0]);
					}
			}
		}
         //输出最大快乐指数
		void maxHappy(int count) {
			cout<<"最大快乐指数:"<< max(this->happys[0][count][0],this->happys[0][count][1] )<<endl;
		}
};

int main() {
	Dance dance(14);
    //省略……
	dance.dfs(root,3);
	dance.maxHappy(3);
	return 0;
}

输出结果:

9.png

2.3 城堡守卫者

问题描述:

一座城堡的所有的道路形成一个n个节点的树,如果在一个节点上放上一个士兵,那么和这个节点相连的边就会被看守住,问把所有边看守住最少需要放多少士兵。

问题分析:

此题目和上述没有上司的舞会的题意差不多。同样可以使用二维数组存储第一个节点的状态值。这里状态值指以当前节点为根节点时子树所需要的最小士兵值。

soldiers[100][2];
//soldiers[1][0] 表示编号为 1 的节点处不放置士兵时树的士兵总数
//soldiers[1][1] 表示编号为 1 的节点处放置士兵时树的士兵总数

如下图,当只有一个节点时的节点状态值:

  • 对于此节点而言,有 2 种状态,放一个士兵或不放一个士兵。当只有一个节点时,理论上可以放或不放一个士兵,但从现实而言,至少需要放一个士兵。意味着 min(soldiers[0][1,soldiers[0][0])>0(最小值不能为零),如果结果为 0至少需要放 1个士兵。

10.png

  • 节点有子节点时。则状态值如下图所示。

11.png

  • 如下图所示,根据树型动态规划思想,到根结点时,最优状态值为 2, 只需要在B路口和C路口各放一个士兵,便能守住整棵树。

12.png

编码实现:

代码与上面案例的代码很类似,为了做些区分。此案例中,节点之间的关系使用邻接表的方式。直接上所有代码,细节处自行了解。

#include <iostream>
#include <vector>
using namespace std;
/*
*节点类
*/
struct Crossing {
	//路口编号
	int cid;
	//路口名
	string cname;
	//使用邻接表存储与之相信的节点的编号
	vector<int>  neighbours;
	Crossing() {}
	Crossing(int cid,string cname) {
		this->cid=cid;
		this->cname=cname;
	}
};
/*
*  城堡树
*/
class City {
	private:
		//所有路口
		Crossing crossings[100];
		//路口数量
		int crossingCount;
		//数量
		int count;
		//编号
		int num;
		//二维状态数组,行号为路口编号
		int soldiers[100][2];
	public:
		City() {}
		City(int count) {
			this->num=0;
			//路口数量
			this->crossingCount=count;
		}
		/*
		* 添加路口
		*/
		int addCrossing(string cname) {
			//新路口
			Crossing crossing(this->num,cname);
			//添加
			this->crossings[this->num]=crossing;
			return 	this->num++;;
		}
		/*
		*添加路口间父子关系
		*/
		void addRelation(int from,int to) {
             //邻接表
			this->crossings[from].neighbours.push_back(to);
		}
		/*
		*规定编号为 0 的员工为根节点
		*/
		int getRoot() {
			return 0;
		}
		/*
		* 深度搜索实现
		* 树型动态规划
		*/
		void dfs(int cid) {
			//不放
			soldiers[cid][0]=0;
			//放
			soldiers[cid][1]=1;
			//深度搜索子节点
			for(int i=0; i< this->crossings[cid].neighbours.size(); i++  ) {
				int subId= this->crossings[cid].neighbours[i];
				//基于子节点深度搜索
				dfs(subId) ;
				//放
				soldiers[cid][1]+= soldiers[subId][0];
				//不放
				soldiers[cid][0]+= min(soldiers[subId][1],soldiers[subId][0]) ==0?1:min(soldiers[subId][1],soldiers[subId][0]);
			}
		}
        /*
        *输出
        */
		void outInfo() {
			cout<<"各路口的士兵数:"<<endl;
			for(int i=0; i<this->num; i++) {
				for(int j=0; j<2; j++) {
					cout<<this->soldiers[i][j]<<"\t";
				}
				cout<<endl;
			}
			cout<<"最小士兵人数:"<< min(this->soldiers[0][0],this->soldiers[0][1] )<<endl;
		}
};
//测试
int main() {
	City city(7);
	int root= city.addCrossing("A路口") ;  //0
	int bRoad= city.addCrossing("B路口") ;
	city.addRelation(root,bRoad);
	int cRoad= city.addCrossing("C路口") ;
	city.addRelation(root,cRoad);
	int dRoad= city.addCrossing("D路口") ;
	city.addRelation(bRoad,dRoad);
	int eRoad= city.addCrossing("E路口") ;
	city.addRelation(bRoad,eRoad);
	int fRoad= city.addCrossing("F路口") ;
	city.addRelation(cRoad,fRoad);
	int gRoad= city.addCrossing("G路口") ;
	city.addRelation(cRoad,gRoad);
	city.dfs(root);
	city.outInfo();
	return 0;
}

输出结果:

13.png

3. 总结

本文讲解树型动态规划,是动态规划思想用于基于树结构的问题求解方案。

动态规划是一个很重要的算法思想,入门较易,但,因其可适用的场景较多,导致其变化性很大。虽如此,但万变不离其宗,找到子问题的状态值以及状态转换表达式,问题也将迎刃而解。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/442177.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

数智融合,生态链接丨 亚信科技“信伙伴”交流会(成都站)成功举办

日前&#xff0c;“数智融合&#xff0c;生态链接”亚信科技“信伙伴”交流会&#xff08;成都站&#xff09;在成都希顿酒店成功举办。本次会议由四川省信创产业联盟指导&#xff0c;亚信科技AntDB数据库事业部、四川省软件行业协会联合举办。 伴随我国信息技术应用创新不断向…

探究以太坊生态系统中的Consensys:产品技术细节与应用场景介绍

文章目录 前言一. Infura1. API简介&#xff08;1&#xff09;HTTP API&#xff08;2&#xff09;WebSocket API 2. Infura优势&#xff08;1&#xff09;稳定性&#xff08;2&#xff09;易用性&#xff08;3&#xff09;免费 二、Truffle1. Truffle框架&#xff08;1&#xf…

【大数据之Hadoop】十七、MapReduce之数据清洗ETL

ETL是将业务系统的数据经过抽取、清洗转换之后加载到数据仓库的过程&#xff0c;目的是将分散、零乱、标准不统一的数据整合到一起&#xff0c;为决策提供分析依据。 ETL的设计分三部分&#xff1a;数据抽取、数据的清洗转换、数据的加载。 1 ETL体系结构 ETL主要是用来实现…

Python无框架分布式爬虫,爬取范例:拼多多商品详情数据,拼多多商品列表数据

拼多多是中国领先的社交电商平台之一&#xff0c;是一家以“团购折扣”为主要运营模式的电商平台。该平台上有海量的商品&#xff0c;对于商家和消费者来说都具有非常大的价值&#xff0c;因此&#xff0c;拼多多商品数据的采集技术非常重要。本文将介绍拼多多商品数据的采集技…

元宇宙资讯|消息称苹果 MR 头显发售要等到 2025 年

长期担任苹果分析师的吉恩・蒙斯特 (Gene Munster) 表示&#xff0c;尽管该公司可能永远不会使用“元宇宙”这个词&#xff0c;但苹果公司的混合现实耳机将是对元宇宙的认可。 深水资产管理公司 (Deepwater Asset Management) 的管理合伙人吉恩・蒙斯特 (Gene Munster) 在周三表…

【系统安全及应用2】

目录 一、开关机安全控制二、终端登录安全控制2.1、限制root只在安全终端登录2.2、如何限制虚拟终端2.3、禁止普通用户登录 三、系统弱口令检测3.1、Joth the Ripper&#xff0c;简称为 JR 四、网络端口扫描4.1、NMAP4.2、nmap的使用nmap的常用选项netstat常用选项 一、开关机安…

【C++初阶】C++入门(二):引用内联函数auto关键字范围for循环(C++11)指针空值nullptr

​ ​&#x1f4dd;个人主页&#xff1a;Sherry的成长之路 &#x1f3e0;学习社区&#xff1a;Sherry的成长之路&#xff08;个人社区&#xff09; &#x1f4d6;专栏链接&#xff1a;C初阶 &#x1f3af;长路漫漫浩浩&#xff0c;万事皆有期待 上一篇博客&#xff1a;【C初阶】…

【pinia】新一代更好用的状态管理器Pinia

目录 一&#xff0c;Pinia状态管理库 1.Pinia介绍 2.Pinia的核心特性 3.核心概念 4.Pinia vs Vuex 5.Pinia & Vuex的不同 6.Pinia名字 二&#xff0c;Pinia基本使用 1.安装Pinia 2.配置main.ts文件 3.创建store/index.ts文件 4.使用数据 三&#xff0c;状态更新…

再获CSA大奖!顺丰科技腾讯安全iOA零信任联合方案获认可

随着千行百业数字化转型的加速&#xff0c;远程办公、业务协同、分支互联等需求涌现&#xff0c;传统的基于边界的网络安全防护理念难以有效抵挡层出不穷的威胁攻击&#xff0c;基于“无边界安全”理念的零信任技术模型逐渐成为企业关注的重点。 近日&#xff0c;第六届云安全…

千云物流 -车辆智能监控调度(一)-技术选型

技术选型 消息队列&#xff1a;rabbitMq 时序数据库&#xff1a; TDengine kv存储&#xff1a;redis 时序数据库 https://db-engines.com/en/ranking/timeseriesdbms 选择范围&#xff1a;Apache IoTDB&#xff0c;TDengine&#xff0c;OpenTSDB 对于存储车辆位置数据的时序数…

LabelImg安装记录

一 安装anaconda 安装conda主要是为了方便环境管理&#xff0c;避免软件版本冲突&#xff0c;安装简单&#xff0c;教程也很多&#xff0c;不做赘述 二 创建虚拟环境 在这里&#xff0c;我们创建一个专门用于标注数据的虚拟环境&#xff0c;取名为labelImg # 第一条命令c…

特斯拉 Tesla 热管理系统技术迭代分析(Model S/X/3/Y热管理系统介绍)

摘要&#xff1a; 特斯拉第三代热管理系统 为了更好地了解特斯拉的技术迭代以及集成度较高的热管理技术&#xff0c;今天我们针对特斯拉初代和第二代热管理系统做简单介绍。 特斯拉第一代热管理系统 系统架构原理图 第一代热管理系统应用在Model S和Model X上&#xff0c;共有…

4月14号软件资讯更新合集.....

PlayEdu v1.0-beta.2 版本发布&#xff0c;企业培训解决方案 PlayEdu 是基于 SpringBoot3 Java17 React18 开发的企业内部培训系统。它专注于提供私有化部署方案&#xff0c;包括视频&#xff0c;图片等资源的内网部署。目前主要支持有本地视频上传播放、学员邮箱登录、无限…

MIT6.824 Lecture18 Fork Consistency

Background 拜占庭问题&#xff08;Byzantine Generals Problem&#xff09;得名于一个古老的传说&#xff0c;讲述了拜占庭帝国在战争中的一个失败策略。在这个故事中&#xff0c;多名拜占庭将军要协调进攻或撤退的行动&#xff0c;但是其中一些将军可能会向其他帝国泄露假消…

在 Rocky linux 8.5 使用 Kubespray v2.21.0 离线部署 kubernetes v1.25.6 集群(草稿)

文章目录 前言创建7台虚拟机要求配置代理下载介质部署前准备安装部署工具配置 python venv配置部署容器 配置互信编写 inventory.ini创建 offline.yml部署 offline repokubespray v2.21.1 部署 kubernetes 失败报错1&#xff1a;Install packages requirements报错2&#xff1a…

各主流图床经历-尝试gitee,七牛云,smms,阿里云

目录 结论&#xff1a;都试过之后我还推荐用aliyun&#xff0c;反正不太贵 目的&#xff1a; 经历&#xff1a; typora用阿里云作图床的流程 结论&#xff1a;都试过之后我还推荐用aliyun&#xff0c;反正不太贵 目的&#xff1a; 想要让md文件中的本地相对链接转为网络图…

使用chatgpt一分钟帮你实现思维导图

前言 本篇基础篇课程&#xff0c;实操起来很简单&#xff0c;但却非常的实用。利用好这个功能&#xff0c;工作效率或能提升10倍&#xff01; 本篇内容的主题&#xff1a;利用ChatGPT&#xff0c;一分钟帮你实现详尽的思维导图。 创作内容大纲 格式转化 结合Xmind 创作内容…

2021地理设计组二等奖:城市三维空间格局对城市内涝的影响研究——以深圳市为例

作品简介 一、设计思想 内涝是指由于连续性降雨或强降雨导致城市地表径流超过地下管网排水能力从而引发的积水现象。内涝的发生会严重破坏城市基础服务设施&#xff08;如交通运输、通讯以及水、电、气的供应&#xff09;&#xff0c;甚至严重影响人民的财产和生命安全。为了降…

【Git基础】常用git命令(二)

文章目录 1. 合并commit为一个1.1 git commit --amend1.2 git rebase -i1.3 git reset1.4 示例 2. 修改commit的内容2.1 git commit --amend2.2 git rebase -i2.3 git cherry-pick2.4 git reflog和git reset 3. 查看commit内容3.1 git log3.2 git log --oneline3.3 git log -[l…

全网多种方法解决[rejected] master -> master (fetch first)的错误

文章目录 1. 复现错误2. 分析错误3. 解决错误4. 解决该错误的其他方法 1. 复现错误 今天使用git status查看文件状态&#xff0c;发现有一个文件未提交&#xff0c;如下代码所示&#xff1a; D:\project\test>git status On branch master Your branch is up to date with …