动态规划实战:游艇租赁问题的最优解探索

问题背景

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

例如:有6个站点,各站点之间的租金如下

动态规划实战:游艇租赁问题的最优解探索_第1张图片

这是一个典型的最短路径问题,称为“区间动态规划”或“最优路径规划”。我们需要找到从游艇出租站1到游艇出租站n的最小租金。可以用动态规划高效解决。以下是详细的解决思路:

1. 问题建模

将站点看作图中的节点,,给定一个矩阵 r[i][j] 表示从游艇出租站 i 到 j 的租金,我们需要找到一条从1到n的路径,使得总租金最小。转化为求节点1到节点n的最短路径问题

2.动态规划解法

我们可以定义一个二维数组 dp[i][j],表示从游艇出租站 i 到 j 的最小租金。状态转移方程如下:

[ dp[i][j] = \min_{i < k < j} (dp[i][k] + dp[k][j]) ]

3. 状态转移方程

初始条件:

当 i == j 时,dp[i][j] = 0,因为从一个站点到自身不需要租金。
当 i < j 时,dp[i][j] = r[i][j]。

最终答案是 dp[1][n]。

4. 算法步骤

初始化二维数组 dp,大小为 n x n。
对于每个可能的区间长度 l(从2到n),遍历所有可能的起点 i 和终点 j。
对于每个区间 (i, j),尝试所有可能的分割点 k,更新 dp[i][j]。
最终返回 dp[1][n]。

5. 示例验证(6个站点)

给定租金矩阵:

r(1,2)=2, r(1,3)=6, r(1,4)=9, r(1,5)=15, r(1,6)=20
r(2,3)=3, r(2,4)=5, r(2,5)=11, r(2,6)=18
r(3,4)=3, r(3,5)=6, r(3,6)=12
r(4,5)=5, r(4,6)=8
r(5,6)=6

计算过程:

dp[1] = 0
dp[2] = dp[1]+r(1,2) = 0+2 = 2
dp[3] = min(dp[1]+r(1,3), dp[2]+r(2,3)) = min(0+6, 2+3) = 5
dp[4] = min(dp[1]+r(1,4), dp[2]+r(2,4), dp[3]+r(3,4)) 
       = min(0+9, 2+5, 5+3) = 7
dp[5] = min(dp[1]+r(1,5), ..., dp[4]+r(4,5))
       = min(0+15, 2+11, 5+6, 7+5) = 11
dp[6] = min(dp[1]+r(1,6), ..., dp[5]+r(5,6))
       = min(0+20, 2+18, 5+12, 7+8, 11+6) = 14

最优路径:1→3→5→6(2+3+6+6=14)

6. 算法实现(Python)/(C语言)

python

def min_rent(n, r):
    # 初始化 dp 表
    dp = [[0] * n for _ in range(n)]
    
    # 填充 dp 表
    for l in range(2, n + 1):  # 区间长度从2开始
        for i in range(n - l + 1):
            j = i + l - 1
            dp[i][j] = float('inf')
            for k in range(i + 1, j):
                cost = dp[i][k] + dp[k][j]
                if cost < dp[i][j]:
                    dp[i][j] = cost
            dp[i][j] += r[i][j]  # 加上直接从 i 到 j 的租金
    
    return dp[0][n-1]

# 示例输入
n = 6
r = [
    [0, 2, 6, 9, 15, 20],
    [0, 0, 3, 5, 11, 18],
    [0, 0, 0, 3, 6, 12],
    [0, 0, 0, 0, 5, 8],
    [0, 0, 0, 0, 0, 6],
    [0, 0, 0, 0, 0, 0]
]

# 计算最小租金
print(min_rent(n, r))  # 输出结果

dp[i][j] 表示从站 i 到站 j 的最小租金。
我们通过枚举所有可能的分割点 k 来更新 dp[i][j]。
最终返回 dp[0][n-1] 即为从站1到站n的最小租金。
这个算法的时间复杂度是 (O(n^3)),空间复杂度是 (O(n^2))。对于较大的 (n),可以考虑优化空间复杂度。

C语言

​
#include 
#include 

#define MAX_STATIONS 100

int calculateMinRent(int rent[MAX_STATIONS][MAX_STATIONS], int n) {
    int minRent[MAX_STATIONS];
    minRent[0] = 0; // 站点1到站点1的租金为0
    
    for (int j = 1; j < n; j++) {
        minRent[j] = INT_MAX;
        for (int i = 0; i < j; i++) {
            if (minRent[j] > minRent[i] + rent[i][j]) {
                minRent[j] = minRent[i] + rent[i][j];
            }
        }
    }
    return minRent[n-1]; // 返回站点1到站点n的最少租金
}

int main() {
    int n;
    printf("请输入出租站数量:");
    scanf("%d", &n);
    
    int rent[MAX_STATIONS][MAX_STATIONS];
    printf("请输入租金矩阵:\n");
    for (int i = 0; i < n; i++) {
        for (int j = i+1; j < n; j++) {
            printf("请输入出租站%d到出租站%d的租金:", i+1, j+1);
            scanf("%d", &rent[i][j]);
        }
    }
    
    int minRent = calculateMinRent(rent, n);
    printf("从出租站1到出租站%d所需要的最少租金为:%d\n", n, minRent);
    
    return 0;
}

​

7. 复杂度分析

时间复杂度:该算法使用了两层嵌套循环,外层循环遍历所有站点(n次)内层循环在最坏情况下也遍历n次
因此,时间复杂度为O(n²),对于大规模问题仍然保持高效。
空间复杂度:使用了一个长度为n的数组来存储中间结果,空间复杂度为O(n)

8. 算法正确性证明

实验验证
使用示例数据进行验证:
1→2→4→6:2 + 5 + 8 = 15
1→3→6:6 + 12 = 18
1→4→6:9 + 8 = 17
1→3→4→6:6 + 3 + 8 = 17
1→2→4→5→6:2 + 5 + 5 + 6 = 18
最优解:1→3→5→6:6 + 6 + 6 = 14
算法正确计算出了最优解14。

该问题满足动态规划的两个关键性质:
最优子结构:全局最优解包含局部最优解
无后效性:当前决策只依赖已计算的状态


9. 扩展思考

如果允许逆流租赁(i > j),则转化为带权有向图的最短路径问题,可用Dijkstra算法
如需输出具体路径,可额外维护prev数组记录前驱节点


10. 实际应用

该算法可应用于:
优化交通路线规划(最少费用路径)
网络数据传输路径选择(最低成本路由)
供应链优化(最小化物流运输成本)
通过动态规划,我们高效地解决了这个具有实际意义的优化问题。

你可能感兴趣的:(算法)