游艇租赁

问题描述

长江游艇俱乐部在长江上设置了 n 个游艇出租站,游客可以在这些游艇出租站租用游艇,并在下游的任何一个游艇出租站归还游艇。游艇出租站 i 到游艇出租站 j 之间的租金为rij),1i<jn。试设计一个算法,计算从游艇出租站 i 到出租站 j 所需的最少租金。

问题分析

长江游艇俱乐部在长江上设置了 n 个游艇出租站,游客可以在这些出租站租用游艇,并在下游的任何一个游艇出租站归还游艇。游艇出租站 i 到游艇出租站 j 之间的租金为 rij)。现在要求出从游艇出租站 1 到游艇出租站 n 所需的最少的租金。

当要租用游艇从一个站到另外一个站时,中间可能经过很多站点,不同的停靠站策略就

有不同的租金。那么我们可以考虑该问题,从第 1 站到第 n 站的最优解是否一定包含前 n1的最优解,即是否具有最优子结构和重叠性。如果是,就可以利用动态规划进行求解。

如果我们穷举所有的停靠策略,例如一共有 10 个站点,当求子问题 4 个站点的停靠策略时,子问题有(1234),(2345),(3456),(4567),(5678),(6789),(78910)。如果再求其子问题 3 个站点的停靠策略,(1234)产生两个子问题:(123),(234)。(2345)产生两个子问题:(234),(345)。如果再继续求解子问题,会发现有大量的子问题重叠,其算法时间复杂度为,暴力穷举的办法是很不可取的。

下面分析第 i 个站点到第 j 个站点(ii+1,…,j)的最优解(最少租金)问题,考查是否具有最优子结构性质。

1)分析最优解的结构特征

• 假设我们已经知道了在第 k 个站点停靠会得到最优解,那么原问题就变成了两个子

问题:(ii+1,…,k)、(kk+1,…,j)。如图 4-32 所示。

 

• 那么原问题的最优解是否包含子问题的最优解呢?

假设第 i 个站点到第 j 个站点(ii+1,…,j)的最优解是 c,子问题(ii+1,…,k)的最优解是 a,子问题(kk+1,…,j)的最优解是 b,那么 c=a+b,无论两个子问题的停靠策略如何都不影响它们的结果,因此我们只需要证明如果 c是最优的,则 a b 一定是最优的(即原问题的最优解包含子问题的最优解)。

2)建立最优值的递归式

• 用 m[i][j]表示第 i 个站点到第 j 个站点(ii+1,…,j)的最优值(最少租金),那   么两个子问题:(ii+1,…,k)、(kk+1,…,j)对应的最优值分别是 m[i][k]   m[k][j]

• 游艇租金最优值递归式:

 

3)自底向上计算最优值,并记录

先求两个站点之间的最优值,再求 3 个站点之间的最优值,直到 n 个站点之间的最优值。

4)构造最优解

上面得到的最优值只是第 1 个站点到第 n 个站点之间的最少租金,并不知道停靠了哪些站点,我们需要从记录表中还原,逆向构造出最优解。

算法设计

采用自底向上的方法求最优值,分为不同规模的子问题,对于每一个小的子问题都求最

优值,记录最优策略,具体策略如下。

1)确定合适的数据结构

采用二维数组 r[][]输入数据,二维数组 m[][]存放各个子问题的最优值,二维数组 s[][]存放各个子问题的最优决策(停靠站点)。

2)初始化

根据递推公式,可以把 m[i][j]初始化为 r[i][j],然后再找有没有比 m[i][j]小的值,如果有,则记录该最优值和最优解即可。初始化为:m[i][j]=r[i][j]s[i][j]=0,其中,i=12,…,nj=i+1i+2,…,n

3)循环阶段

• 按照递归关系式计算 3 个站点 ii+1jj=i+2)的最优值,并将其存入 m[i][j],同时将最优策略记入 s[i][j]i=12,…,n2

• 按照递归关系式计算 4 个站点 ii+1i+2jj=i+3)的最优值,并将其存入 m[i][j],同时将最优策略记入 s[i][j]i=12,…,n3

• 以此类推,直到求出 n 个站点的最优值 m[1][n]

4)构造最优解

根据最优决策信息数组 s[][]递归构造最优解。s[1][n]是第 1 个站点到第 n 个站点(12,…,n)的最优解的停靠站点,即停靠了第 s[1][n]个站点,我们在递归构造两个子问题(12,…,k)和(kk +1,…,n)的最优解停靠站点,一直递归到子问题只包含一个站点为止。

代码


#include<iostream>
using namespace std;
const int ms = 1000;
int r[ms][ms],m[ms][ms],s[ms][ms]; //i 到 j 站的租金
int n; //共有 n 个站点
void rent()
{
    int i,j,k,d;
    for(d=3;d<=n;d++) //将问题分为小规模为 d
    {
        for(i=1;i<=n-d+1;i++)
        {
            j=i+d-1;
            for(k=i+1;k<j;k++) //记录每一个小规模内的最优解
            {
                int temp;
                temp=m[i][k]+m[k][j];
                if(temp<m[i][j])
                {
                    m[i][j]=temp;
                    s[i][j]=k;
                }
            }
        }
    }
}
void print(int i,int j)
{
    if(s[i][j]==0 )
    {
        cout <<j;
        return ;
    }
    print(i,s[i][j]);
    print(s[i][j],j);
}
int main()
{
    int i,j;
    cin >> n;
    for(i=1;i<=n;i++)
        for(j=i+1;j<=n;++j)
        {
            cin>>r[i][j];
            m[i][j]=r[i][j];
        }
    rent();
    cout <<m[1][n] << endl;
    cout <<1;
    print(1,n);
    return 0;
}

算法复杂度分析

1)时间复杂度:由程序可以得出:语句 temp=m[i][k]+m[k][j],它是算法的基本语句,

3 for 循环中嵌套,最坏情况下该语句的执行次数为 O()print()函数算法的时间主要取决于递归,最坏情况下时间复杂度为 O(n)。故该程序的时间复杂度为 O()

(2)空间复杂度:该程序的输入数据的数组为 r[][],辅助变量为 ijrtkm[][]s[][],空间复杂度取决于辅助空间,该程序的空间复杂度为 O()

算法优化拓展

如果只是想得到最优值(最少的租金),则不需要 s[][]数组;m[][]数组也可以省略,直

接在 r[][]数组上更新即可,这样空间复杂度减少为 O(1)



猜你喜欢

转载自blog.csdn.net/zxy2016011117/article/details/80960788