6.动态规划 背包问题 最短路径 区间调度 bellmanford

动态规划

一、带权的区间调度问题

现在所有的区间都带上了一个权。我们要做的也不仅仅是选择最多相容的区间,而是选择最大权组合的区间(即原来所有的权都是1)。关于这个问题的解法到目前还没有一个贪心算法可以解决。我们采用的是贪心算法。下面将介绍这种算法。

我们先将这些区间按照结束时间从小到大的顺序排序。然后令OPT(j)表示区间1-j之中的最优解,p(j)表示在需求j的前一个不和j冲突的需求,vj表示j的权。那我们有以下结论

OPT(j)=max(vj+OPT(P(j)),OPT(j-1))

我们立马就得到了算法的伪代码:


OPT(j)

​ if j=0

​ return 0

​ else

​ return max(vj+OPT(P(j)),OPT(j-1))


正确性证明可以由归纳法得出。

但是这个问题的复杂度会相当的大,因为每个问题都会分出两个子问题,每个子问题又会分出两个子问题,我们的时间复杂度是指数级的,那么有没有一种方法能够降低到多项式级别呢?

观察可知,对于每个OPT(i)其实我们都只用计算一次即可。所以我们可以保存下来他们的值。我们在伪代码的实现中只需要加上对数组是否存在的判断即可。

这个新的算法复杂度应该是递归被调用的次数,但是我们并不能很好的知道这一点,但是我们发现每次都会算出一个OPT(?)的值,因此总的复杂度就是O(n)

好,现在我们获得了最优解的值,但是如果我们想要知道具体的最优解该怎么办呢?因为我们已经把OPT()的每个值都已经存在某个数组M中了,我们想要知道某个需求有没有被选择,只需要看vj+OPT(P(j))≥OPT(j-1)有没有被满足不就可以了吗?因此我们设计了另个单独的递归方法实现这一点。

Find(j)
  if j=0
    不输出
  else
    if vj+M[P(j)]≥M[j-1]
      输出j和Find(P(j))
    else
      输出Find(j-1)

可以看出这个算法的复杂度也是O(n)

二、子集和背包问题(加了一个变量)

问题是你有1-n个物品要放入背包,每个物品有不同的价值vi和重量wi,要求是你所放的所有的物品重量要小于某个值w的同时达到w最大。同样,令OPT(j)表示从1-j上的最优解。

我们先给出状态方程:

OPT(j,w)=max(OPT(j-1,w), OPT(j-1,w-wj)+wj)

可以观察到OPT的变量多了一个,这也是由于我们的限定条件多了一个所造成的。

下面给出伪代码:

bag(j,w)
	create M[n][w]
	let M[0][0...w]=0
	for j=1,2,3...n
		for w=0,1,2...w
			M[j][w]=max(M[j-1][w],M[j-1][w-wj]+wj)
		end
	end
	return M

显然,这个算法的复杂度是O(nW)

三、RNA的结构问题

对于一个RNA我们通常说他满足以下几个条件:

  • 不能弯转的太尖,即两个碱基对之间至少有四个碱基分隔
  • 所有碱基对都由(A,U)(C,G)组成
  • 没有碱基出现在一个以上的pair中
  • 不交叉

假设OPT(i,j)表示碱基i到j之间能产生的最多pair的数量,我们有:

OPT(i,j)=max(OPT(j-1),maxt(1+OPT(i,t-1)+OPT(t+1,j-1)))

这个式子表示要么j不和任何碱基匹配:OPT(j-1)

要么j和某个碱基t匹配,由于不交叉原理,剩余的匹配只能产生在i到t-1和t+1到j-1之中,所以数量是max(1+OPT(i,t-1)+OPT(t+1,j-1))这里的max表示遍历所以可能的t得到的结果。

*接下来要实现这个算法我们要做一件很重要的事情,就是考虑这个算法增长的顺序,可以发现我们每次都是在更小的区间里去选择最优解,且我们已知最小区间里的值(初始化的内容),因此我们得到了伪代码:

初始化:若i-j≤4,则OPT(i,j)=0
for k=5,6,7...n-1
	for i=1,2,3...n-k
		j=i+k
		OPT(i,j)=max(OPT(j-1),max~t~(1+OPT(i,t-1)+OPT(t+1,j-1)))

时间复杂度分析:因为总共有n2个子问题要求解,且每次问题都要遍历得到最优的t,所以总的时间复杂度是O(n2

四、序列比对

这个问题一般可以用于探究两串数列或两个单词M,N的相似性。怎么去衡量相似性?这里我们定义两个参数a,b。a表示错配代价,若M和N中相同位置的字母配对,则没有发生错配,那么错配代价是0。我们再定义一个空配代价b,表示b没有跟任何字母配对时的代价。我们选取两个代价和最小的配对方式作为我们的最优解。

假设用m,n表示当前M,N最后的字母。那么对于m,n一定存在以下三种情况之一:

  • m,n配对
  • ·m不发生配对
  • n不发生配对

一定不存在m,n都和另外某两个字母配对的情况,因为这样一定会发生交叉配对的情况,这种情况在我们目前的要求下时不存在的。

那我们可以写出动态规划关系式(令OPT(i,j))表示在1……i和1……j两串字符串匹配中的最小代价):

OPT(i,j)=min(OPT(i-1,j-1)+aij, OPT(i,j-1)+b,OPT(i-1,j)+b)

算法复杂度是O(mn)

但是算法存在一个缺陷,就是其空间复杂度也是巨大的—O(mn),这对于一些很长的序列,比如DNA序列,开销无疑是巨大的,接下来将介绍一种减少这个开销的技术。·

五、利用分治策略减少序列对比的空间开销

我们首先很容易想象到的方法是折叠这个数组,在极限情况下甚至能够折叠到2*2的空间复杂度,因为我们发现我们每次都是在用i或着i-1行的数据,也是在用j或j-1列的数据,因此如果我们让数据覆盖以前的数据,我们就只要两行或两列就能完成这个算法了。

但是问题来了,由于我们只能保存2*2=4个数据,我们根本无法复原从而得到在最优解下两个数列比对的方式。因此我们引入了以下的方法。

首先我们并不压缩到这么极致的情况,我们只把空间压缩到O(m)的大小,即我们知道在最后一行(第n行),各个OPT(i,n)的结果,我们保存了一行的结果。

然后我们创造一个逆向的这样的算法,我们不再用OPT(i,j)表示(0,0)到(i,j)的最优解,而是用他表示(i,j)到m,n的最优解。我们只需要略微改动状态方程即可:

OPT(i,j)=max(OPT(i+1,j+1)+ai+1,j+1, OPT(i+1,j)+b,OPT(i,j+1)+b)

接着我们用一种新式的方式表示这个问题,我们用一个图来表示,横着走的花费是空位,斜着走的花费是错位,这样两点之间的最短距离就是最优解:

然后我们将这个正向和逆向的算法相结合,令f(i,j)表示正向算法的结果,即(0,0)到(i,j),用g(i,j)表示逆向的结果。那么有:f(i,j)+g(i,j)就是整个的最优解。那么假设i不确定,j确定,那么必定存在某个i使得f(i,j)+g(i,j)达到最小值,且这个点(i,j)必定是结果中路径上的一个点。这样我们结合两个算法之后,就能确认某一列上的某个点在结果之中。

然后我们用分治的方式,每次都去确认第n/2列上最优解对应的点,从而达到收集所有的点的目的。

算法的伪代码给出

declare list L
DCA(X,Y)
 let m be the number of words in X
 let n be the number of words in Y
 if m or n ≤2
  just computer it
 else
 	正向计算X,Y[1:n/2]
 	逆向计算X,Y[n/2+1:n]
 	let q be the index of min(f(q,n/2)+g(q,n/2))
 	put (q,n/2) into a list L
 	DCA(X[1:q],Y[1:n/2])
 	DCA(X[q:n],Y[n/2:n])

空间复杂度分析,通过这个递归的执行项目的位置可以看到我们执行是在递的过程而不是归的过程,我们每次只执行一层递归操作,所用的空间最多就是O(m+n),在进行下一次递归的时候这些空间就会被释放。因此我们的复杂度是O(m+n)。

算法的时间复杂度分析,假设我们每次q都是一次完美的平均分隔,且m=n,那么我们的复杂度可以这样写T(n)=2T(n/2)+n2=n2。即O(mn)因此我们做到了在复杂度不变的情况下减少了空间复杂度,且能通过list L追踪到结果。

当然这个时间复杂度的证明是在完美情况下的证明,对于更一般的情况,我们可以使用归纳法。

proof:我们假设T(m’,n’)≤km’n’对于较小的m’和n’成立。那么对于T(m,n)≤cmn+T(q,n/2)+T(m-q,n/2)≤cmn+kqn/2+k(m-q)n/2=(c+k/2)mn

六、图中的最短路径(Bellman-Ford)

直接给出动态方程,令OPT(i,v)表示至多使用i条边的v-t路径的最短花费。

我们分成两种情况考虑:

  • 如果路径至多只需n-1条边,那么OPT(i,v)=OPT(i-1,v)
  • 如果路径恰好用n条边,且第一条边是w-v那么OPT(i,v)=OPT(i-1,w)+Cwv

OPT(i,v)=min(OPT(i-1,v),min(OPT(i-1,w)+Cwv))

算法的复杂度可能不敬人意,因为每次算法的计算时间要遍历所有可能的入边这将花费时间复杂度O(n),另外OPT存在两个变量,可能的子问题也有n2,所以总复杂度是O(n3

其实这样的说法并不是特别准确,因为我们只是遍历所有的入边而不是所有的边,尽管有些十分稠密的图(m近似等于n2)可能会存在每个点都有近乎n-1条入边的情况,但是大部分图并非如此。因此我们可以换一种说法来表述这一点。

因为我们要遍历的是每一个结点的入边,所以其实总复杂度可以被写成O(mn)

同前面所述,由于这个算法只使用i-1的数据,因此我们也可以压缩空间复杂度,使其从n2—>n。并且这样的操作并不会使得追踪这条路径边的更加困难,因为我们只需要简单的在每次list[v]被修改的时候记录下此时的节点就可以了。

此时我们记录的节点如果包含了一个圈,那么这一定是一个负圈。

七、最短路径和距离向量协议

在路由器我们通常会计算某个路由器到一个网络终点的延迟。为了计算这一点我们当然可以使用dijkstra,因为延迟是非负的。但是dijkstra需要获得全局的许多信息才能得到最短路径。然而我们上述提到了bellman-fort算法只需要获得其邻居的到终点距离的值就可以了。

这里存在一个问题,由于这些值是动态变化的,我们用获取的方式其实并不好,因为我们并不知道自己邻居的延迟在什么时候会被改变。因此这里采用另一个“推”而不是“拉”的方法,每次当自己的延迟被改变的时候我们都将自己的延迟推给附近的邻居。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值