数据结构——图 动态规划

本文探讨了动态规划在解决图的最短路径问题中的应用,包括1/0背包问题和Floyd算法。动态规划通过递推公式减少了复杂度,避免了贪婪法和分治法的局限。文章详细解释了1/0背包问题的最优解,并介绍了Floyd算法在有向图中寻找最短路径的动态规划公式,以及其在处理负权重边时的优势。最后,给出了算法的迭代求解和伪代码实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

  动态规划难度相对较大。它的基础是最优原理。有很多问题,用贪婪法或者分而治之无法简洁高效的解决,但是动态规划就可以。

  在动态规划中,我们要考察一系列抉择,以确定一个最优抉择序列是否包含最优抉择子序列。当最优抉择包含最优抉择子序列时,可建立动态规划递归方程以帮助我们高效解决问题。

  动态规划常常基于一个递推式或一个或多个初始状态。当前子问题的解将由上一个子问题的解推出。一般是多项式复杂度。

1.0/1背包问题

  有n个物品和一个容量为c的背包,从n个物品中选取装包的物品。物品i的重量为wi,价值为pi。一个可行的背包装载是指,背包的物品总重量不超过背包的容量。一个最佳的背包装载是物品总价值最高的可行的背包装载。

  问题的公式描述是 maxΣpixi  ; 约束条件是 Σwixi <= c且 xi∈{0,1}.

  1)递归求解,F(1,c)返回f(1,c)的值。

int F(int i, int y)
{//Return f(i,y).
  if(i == n) return (y < w[n]) ? 0 : p[n];
  if(y < w[i]) return F(i+1,y);
  return max(F(i+1,y),F(i+1,y-w[i])+p[i]);
}
  2)迭代计算 f 和 x
template<class T>
void Knapsack(T p[], int w[], int c, int n, T** f){
//Compute f[i][y] for all i and y.
 
 //initialize f[n][]
 for(int y = 0; y < w[n]; y++)
  f[n][y] = 0;
 for(int y = w[n]; y <= c; y++)
  f[n][y] = p[n];

 //compute remaining f's
 for(int i = n - 1; i > 1; i--){
  for(int y = 0; y < w[i]; y++)
   f[i][y] = f[i+1][y];
  for (int y = w[i]; y <= c; y++)
   f[i][y] = max(f[i+1][y],f[i+1][y-w[i]] + p[i]);
 }
 f[1][c] = f[2][c];
 if(c >= w[1])
  f[1][c] = max(f[1][c], f[2][c-w[1]] + p[1]);
}
template<class T>
void Traceback(T **f, int w[], int c, int n, int x[])
{//Compute x for optimal filling.
  for(int i = 1; i < n; i++)
   if(f[i][c] == f[i+1][c]) x[i] = 0;
   else {
    x[i] = 1;
    c -= w[i];
   }
  x[n] = (f[n][c]) ? 1 : 0;
}

2.所有顶点对之间的最短路径

  i)问题描述

  在有向图中,寻找每一对顶点之间的最短路径。即对于每对顶点( i , j ),要寻找从顶点i到顶点j以及从顶点j到顶点i的最短路径。

  在无向图中,这两条路径等于一条。

  当边上的权都不是负值的时候,所有顶点对之间的最短路径可以用Dijkstra单元算法n次来求解,每一次都选择n个顶点中的一个顶点作为源点。这个过程的时间复杂为O(n^3).

  我们这里用动态规划算法——Floy算法,来求解,时间复杂度为Θ(n^3).Floy算法即使在边长为负值时也适用(不过环路的带权长度不能是负值)。与Floy算法相关的常数因子比Dijkstra算法的要小。因此Floy算法比最坏情况下的Dijkstra算法用时要少。

  ii)动态规划公式

  假定图可以有权值为负的边,但不能有带权长度为负值的环路。因此,每一对顶点(i,j)总有一条不含环路的最短路径。

  图G 有n个顶点,1~n。c(i,j,k)表示从顶点i到顶点j的一条最短路径的长度,其中间顶点的编号不都大于k。

  对任意k>0,如何确定c(i,j,k)呢? 从i到j,中间顶点不超过k的最短路径有两种可能:该路径包含或不包含中间顶点k。若不包含,则该路径长度应为c(i,j,k-1),否则长度为c(i,k,k-1)+c(k,j,k-1). c(i,j,k)可取两者中的较小值。由此可得如下递归式:

                c(i,j,k)=min{c(i,j,k-1), c(i,k,k-1)+c(k,j,k-1)} , k>0

  以上递归公式将一个k级运算转化为多个 k-1 级运算,而多个 k-1 级运算应比一个k级运算简单。

    1)递归求解时间复杂度极大。(Θ(n23n))

    2)迭代求解:

    通过多次计算c(i,j,k-1)的值,可以得到c(i,j,n)的值,这是一种非常高效的方法。如果c(i,j,k)的值不用重复计算,那么c值的计算用时可以减少到Θ(n3).

    伪代码:

Initial shortest-paths algorithm
//Find the lengths of the shortest paths.
 //initialize c(i,j,0)
 for(int i = 1; i <= n; i++)
  for (int j = i; j <= n; j++)
   c(i,j,0) = a(i,j); //a is the cost-agjacency matrix
 
 //compute c(i,j,k) for 0 < k <= n
 for(int k = 1; k <= n; k++)
  for(int i = 1; i <= n; i++)
   for(int j = 1; j <= n; j++)
    if(c(i,k,k-1)+c(k,j,k-1)<c(i,j,k-1))
     c(i,j,k)=c(i,k,k-1)+c(k,j,k-1);
    else c(i,j,k)=c(i,j,k-1);
注意到对于任意 i,c(i,k,k)=c(i,k,k-1)且c(k,i,k)=c(k,i,k-1),因而,若用c(i,j)代替上述伪代码中的c(i,j,*),c(i,j)的最后值将等于c(i,j,n)的值。

    代码实现:

    allPairs函数在c中返回最短路径的长度, 还计算kay[i][j],它是从i到j的最短路径中最大的k值。kay值可以用来构建从一个顶点到另一个顶点的最短路径(见函数outputPath)。程序的时间复杂度为Θ(n3).程序用时O(n)来输出一条最短路径。

template<class T>
void AdjacencyWDigraph<T>::AllPairs(T **c, int **kay)
{//All pairs shortest paths.
 //Compute c[i][j] and kay[i][j] for all i and j.
  //initialize c[i][j] = c(i,j,0)
  for(int i = 1; i <= n; i++)
   for(int j = 1; j <= n; j++){
    c[i][j] = a[i][j];
    kay[i][j] = 0;
   }
  for(int i = 1; j <= n; i++)
   c[i][i] = 0;

  //compute c[i][j] = c(i,j,k)
  for(int k = 1; k <= n; k++)
   for(int i = 1; i <= n; i++)
    for(int j = 1; j <= n; j++){
     T t1 = c[i][k];
     T t2 = c[k][j];
     T t3 = c[i][j];
     if (t1 != NoEdge && t2 != NoEdge &&(t3 == NoEdge || t1 + t2 < t3)){
      c[i][j] = t1 + t2;
      kay[i][j] = k;
     }
    }
}
//Output a shortest path
void outputPath(int **kay, int i, int j){//Actual code to output i to j path. if(i == j) return; if(kay[i][j] == 0) cout << j <<' '; else{ outputPath(kay, i, kay[i][j]); outputPath(kay, kay[i][j], j); } }template<class T>void OutputPath(T **c, int **kay, T NoEdge, int i, int j){//OutputPath shortest path from i to j if(c[i][j] == NoEdge){ cout <<"There is no path from " << i << "to"<< j << endl; return; } cout <<"The path is" << endl; cout << i <<' '; outputPath(kay,i,j); cout << endl;}

















评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值