2020-02-12

动态规划算法的发现

Views: 3596 | Add Comments

1. 问题可分而治之且 BFS

首先, 问题必须是可分而治之的, 并在最后合并. 分而治之(递归)是为了穷举, 合并是为了找最优.

Result r(costs[], target){
	args = [];
	for(cost in costs){
		tmp = r(costs - cost, target - cost) + cost;
		args += tmp;
	}
	return G(args);
}

虽然上面的代码是 DFS, 但形式上是 BFS, 而且也应该写成 BFS, 只不过 BFS 的代码不简洁而已.

思考: 与贪婪算法的区别.

2. 合并函数 G(...) 可迭代处理

因为 G() 是可以转换成迭代的, 所以代码变成:

Result r(costs[], target){
	ret = PRE;
	for(cost in costs){
		tmp = r(costs - cost, target - cost) + cost;
		ret = G(ret, tmp);
	}
	return ret;
}

PRE(开始之前)是引入的边界外的参数, 以便让代码处理逻辑简化, 不然要加 if 条件判断, 就无法在形式化上统一.

3. 增加缓存

Result r(costs[], target, dp){
	cache_key = make_cache_key(costs, target);
	if(dp[cache_key]){
		return dp[cache_key];
	}
	ret = PRE;
	for(cost : costs){
		tmp = r(costs - cost, target - cost, dp) + cost;
		ret = G(ret, tmp);
	}
	dp[cache_key] = ret;
	return ret;
}

4. 将递归转成迭代

#### 推导型

Result forward(costs, target){
	init(dp);
	cc[PRE] = costs;
	for(curr in range(PRE, target)){
		costs = cc[curr];
		for(cost : costs){
			dp[next] = G(dp[next], dp[curr] + cost);
			cc[next] = costs - cost if dp[next] updated;
		}
	}
	return dp[target];
}

#### 回溯型

Result backtrack(costs[], target){
	dp[PRE] = PRE;
	cc[PRE] = costs;
	for(curr in range(atomic, target)){
		for(prev in get_prev_list(curr)){
			costs = cc[prev];
			cost = costs.link(prev, curr); 
			dp[curr] = G(dp[curr], dp[prev] + cost);
			cc[curr] = costs - cost if dp[curr] updated;
		}
	}
	return dp[target];
}

5. 缓存可淘汰: 滑动窗口

这一条件不是必须的, 因为很多动态规划解法无法淘汰缓存. 如果缓存可淘汰, 而且是可以用滑动窗口的方式淘汰, 那么就是非常**经典且巧妙的**动态规划解法.

对于推导型动态规划, 只需要缓存最长的推导距离. 对于回溯型动态规划, 只需要缓存最长的回溯距离.

这里举的例子是单节点推导, 下一节点只依赖单一的前序节点, 但有些问题需要依赖多个前序节点才能出方案, 例如斐波那契数列. 需要注意.

Related posts:

  1. HTTP POST using PHP cURL
  2. Objective-C 对二进制数据 NSData 进行 URL 编码
  3. 消除JavaScript闭包的一般方法
  4. iOS 应用修改状态栏和导航栏颜色
  5. C#封装log4net
Posted by ideawu at 2020-02-12 15:57:31

Leave a Comment