动态规划 - 打家劫舍、机器人路径、0-1背包


一、动态规划

关于动态规划算法,和分治策略类似。都是将问题分解然后求解,但是又和分治策略有所不同,动态规划把每次求得的解都记录在了一张表中,省去了重复递归的过程。

二、例子

1. 打家劫舍

(1)题目描述
在这里插入图片描述
(2)解题思路
在这里插入图片描述
(3)代码展示

//使用vector表示dp
int rob(const vector<int>& num)
{
	int n = num.size();	//房子的个数

	vector<int> dp(n);	
	if (n == 1)
	{
		return num[0];
	}
	if (n == 2)
	{
		return std::max(num[0], num[1]);
	}
	dp[0] = num[0];
	dp[1] = std::max(num[0], num[1]);
	for (int i = 2; i < n; i++)	//n为个数,i到n-1退出
	{
		dp[i] = std::max(dp[i - 1], dp[i - 2] + num[i]);	//不能是连续的两家
	}
	return dp[n - 1];	//第n-1个是最优解
}
int main()
{
	vector<int> num1 = { 1,2,3,1 };
	vector<int> num2 = { 2,7,9,3,1 };
	cout << rob(num1) << endl;
	cout << rob(num2) << endl;
	return 0;
}

结果:
在这里插入图片描述

//不使用vector表示dp
//把原来的dp换成三个变量就可以了
int rob1(const vector<int>& num)
{
	int n = num.size();
	if (n == 1)
	{
		return num[0];
	}
	if (n == 2)
	{
		return std::max(num[0], num[1]);
	}
	int pre = 0;	//当前的前一个
	int cur = num[0];	//当前
	int tmp = 0;
	for (int i = 1; i < n; i++)
	{
		tmp = std::max(cur, pre + num[i]);
		pre = cur;
		cur = tmp;
	}
	return tmp;
}

2.机器人路径

(1)题目描述
在这里插入图片描述(2)解题思路

dp[i][j] = dp[i - 1][j] + dp[i][j - 1];  

(3)代码展示:

int uniquePaths(int m, int n) 
{
	if (m <= 0 || n <= 0)
		return 1;
	vector<vector<int>> dp(m, vector<int>(n, 0));
	int i, j;
	for (i = 0; i < n; i++)
	{
		dp[0][i] = 1;
	}
	for (j = 0; j < m; j++)
	{
		dp[j][0] = 1;
	}
	for (i = 1; i < m; i++)		//注意越界,i == 1, 不要写成i = 0了,下面有i-1,j-1
	{
		for (j = 1; j < n; j++)		//注意越界,j == 1
		{
			dp[i][j] = dp[i - 1][j] + dp[i][j - 1];
		}
	}
	return dp[m - 1][n - 1];
}

3. 0-1背包问题

题目描述
给定n种物品和一背包。物品i的重量是wi;,其价值为vi, ,背包的容量为C。问:应该如何选择装人背包的物品,使得装入背包中物品的总价值最大?在选择装入背包的物品时,对每种物品i只有两种选择,即装入背包或不装人背包。不能将物品i装入背包多次,也不能只装入部分的物品i。因此,该问题称为0-1背包问题。
解题思路
首先定义一些变量:Vi表示第 i 个物品的价值,Wi表示第 i 个物品的体积,定义V(i,j):当前背包容量 j,前 i 个物品最佳组合对应的价值,同时背包问题抽象化(X1,X2,…,Xn,其中 Xi 取0或1,表示第 i 个物品选或不选)。V(i,j)为什么要用两个值来表示,因为价值与两个属性有关。它与空间和元素都有关系。

1、建立模型,即求max(V1X1+V2X2+…+VnXn);

2、寻找约束条件,W1X1+W2X2+…+WnXn<capacity;

3、寻找递推关系式,面对当前商品有两种可能性:①容量不够 ②容量足够

  • 包的容量比该商品体积小,装不下,此时的价值与前i-1个的价值是一样的,即V(i,j)=V(i-1,j);

  • 还有足够的容量可以装该商品,但装了也不一定达到当前最优价值,所以在装与不装之间选择最优的一个,即V(i,j)=max{V(i-1,j),V(i-1,j-w(i))+v(i)}。V(i-1,j),V(i-1,j-w(i))的值通过读表可以获得。

    注意:价值是由i和j共同决定的。

由此可以得出递推关系式:

  • j<w(i) V(i,j)=V(i-1,j)
  • j>=w(i) V(i,j)=max{V(i-1,j),V(i-1,j-w(i))+v(i)}

4、填表,首先初始化边界条件,V(0,j)=V(i,0)=0;然后执行递推关系式。
(1)递归求解:通过逐次将规模减小,来递归的求解最大的值。

①正序: 从前向后规模递增。i表示从1到i的范围内。
在这里插入图片描述

②逆序: 从后向前规模递增。i表示从i到n。

代码展示

//递归求解-正序
int Knapsack(int W[], int V[],  int i, int j)
{
	if (i == 1)		//只有一个物品,第一个
	{
		return j > W[i] ? V[i] : 0;
	}
	else if (W[i] > j)	//剩余空间过小,放不下第i个
	{
		return Knapsack(W, V, i - 1,j);
	}
	else	//能放下,进行比较,取最优
	{
		int max1 = Knapsack(W, V, i - 1, j);
		int max2 = Knapsack(W, V, i - 1, j - W[i]) + V[i];
		return max1 > max2 ? max1 : max2;
	}
}
//递归求解-逆序
int Reverse_knapsack(int W[], int V[], int i, int n, int j)
{
	if (i == n)		//只有一个物品,最后一个
	{
		return j >= W[n] ? V[n] : 0;
	}
	else if (W[i] > j)	//剩余空间过小,放不下第i个
	{
		return Reverse_knapsack(W, V, i + 1, n, j);
	}
	else	//能放下,进行比较,取最优
	{
		int max1 = Reverse_knapsack(W, V, i + 1, n, j);
		int max2 = Reverse_knapsack(W, V, i + 1, n, j - W[i]) + V[i];
		return max1 > max2 ? max1 : max2;
	}
}
int main()
{
	const int n = 5;
	const int c = 10;
	int W[n + 1] = { 0,2,2,6,5,4 };	//第一个元素不是数据
	int V[n + 1] = { 0,6,3,5,4,6 };	//第一个元素不是数据
	int max1 = Knapsack(W, V, n, c);
	cout << "max1:" << max1 << endl;
	int max2 = Reverse_knapsack(W, V, 1,n,c);
	cout << "max2:" << max2 << endl;
	return 0;
}

结果:
在这里插入图片描述
(2)非递归求解
解题思路
用表格来记录值,用的时候直接查表格。从小到大开始记录。

代码展示

void BackPack(vector<vector<int>>& dp, int W[], int n, int c, vector<bool>& X)
{
	int j = c;
	for (int i = n; i > 0; i--)
	{
		if (dp[i][j] != dp[i - 1][j])
		{
			X[i] = true;
			j -= W[i];
		}
	}
}
void Print_Vector(vector<vector<int>>& ar, int m, int n)
{
	for (int i = 0; i <= m; i++)
	{
		for (int j = 0; j <= n; j++)
		{
			printf("%4d", ar[i][j]);
		}
		printf("\n");
	}
	printf("\n");
}
int Knapsack(int W[], int V[], int n, int c, vector<vector<int> >& dp)
{
	for (int j = 1; j <= c; j++)
	{
		if (W[1] < j)
		{
			dp[1][j] = V[1];
		}
		else
		{
			dp[1][j] = 0;
		}
	}
	for (int i = 2; i <= n; i++)
	{
		for (int j = 1; j <= c; j++)
		{
			if (j < W[i])
			{
				dp[i][j] = dp[i - 1][j];
			}
			else
			{
				dp[i][j] = std::max(dp[i - 1][j], dp[i - 1][j - W[i]] + V[i]);
			}
		}
	}
	return dp[n][c];
}

int main()
{
	const int n = 5;
	const int c = 10;
	int W[n + 1] = { 0,2,2,6,5,4 };
	int V[n + 1] = { 0,6,3,5,4,6 };
	vector<vector<int> > dp(n+1,vector<int>(c+1,0));
	vector <bool> X(n + 1, false);
	int max = Knapsack(W, V, n, c,dp);
	cout << max << endl;
	Print_Vector(dp,n,c);
	BackPack(dp, W, n, c, X);
	int count = 0;
	for (auto x : X)
	{
		if (x)
		{
			cout << "第" << count << "个放进去了!价值是" << V[count] << endl;
		}
		count++;
	}
	return 0;
}

结果:
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值