1、阿里巴巴走进了装满宝藏的藏宝洞。藏宝洞里面有N堆金币,第i堆金币的总重量和总价值分别是m,v。阿里巴巴有一个承重量为T的背包,但并不一定有办法将全部的金币都装进去。
他想装走尽可能多价值的金币,所有金币都可以随意分割,分割完的金币重量价值比(也就是单位价格)不变。请问阿里巴巴最多可以拿走多少价值的金币?
(1)请给出存储已知数据的数据结构〈2分)
(2)请给出你选择的算法策略(2分)
(3)请写出算法步骤〈任选一种自然语言·伪代码﹑程序进行描逑〉(6分)
(4)假设背包里有4个物品,请自行设计一个算法,并给出输出结果〈物品的重量﹑价值、背包容量自拟)
例题分析:
根据题意可以知道这是一个分数背包问题(金币重量价值比可知)。就是说在背包容量一定的情况下,每个物品可以选择放入部分或全部,要求放入背包中的物品的总价值最大化或者总重量最小化。(01背包不可以使用贪心策略,但是分数背包却是不二之选)
那么,这一题求的是在背包为T的情况下,能拿走最多的金币是多少。
数据结构:我们可以使用列表嵌套映射来存储每堆金币的重量和价值。
coins = [{"w": m1, "v": v1},{"w": m2, "v": v2},...{"w": mN, "v": vN}
]
算法策略:计算每堆金币的单位价值,即价值/重量。按单位价值(v/w)从高到低对金币堆进行排序。从单位价值最高的金币堆开始,尽可能多地装入背包,直到背包装满或没有金币可装。(贪心策略,只考虑目前的最好选择)
代码实现:
class Item:def __init__(self, weight, value):self.weight = weightself.value = valueself.value_per_weight = value / weightdef fractional_knapsack(coins, T):coins.sort(key=lambda x: x.value_per_weight, reverse=True)total_value = 0remaining_capacity = Tfor item in coins:if remaining_capacity == 0:breakif remaining_capacity >= item.weight:total_value += item.valueremaining_capacity -= item.weightelse:total_value += item.value_per_weight * remaining_capacitybreakreturn total_valueif __name__ == "__main__":n, T = map(int, input().split())values = list(map(int, input().split()))weights = list(map(int, input().split()))# python内部的列表生成式coins = [Item(weights[i], values[i]) for i in range(n)]max_value = fractional_knapsack(coins, T)print(f"{max_value:.1f}")
输入:
4 20
25 24 15 10
18 15 10 4
输出:
35.5
2、游艇俱乐部在长红上设置了n个游艇出租站,游客可以在上游的出租站租用游艇,在下游的任何一个游艇出租站归还游艇。游艇出租站i到出租站j(i<j)之间的租金为r(i,j)。求解从游艇出租站1到游艇出租站n所需的最少的租金?
(1〉写出选用的算法策略(2分)
(2)写出该算法策略的思想(4分)
(3)写出求解问题所用的数据结构(2分)
(4)写出求解问题的算法步骤(5分)
(5)分析算法的时间复杂度和空间复杂度(2分)
例题分析:
n个出租站,游艇i到j的租金为r(i,j),求从1到n所需的最少租金。
租金直接使用二维数组表示(r[i][j])。
那么,我们就可以想到是不是可以用 dp[j] 表示从1到j的最少租金,故所求结果为 dp[n]。
(1)动态规划。
(2)dp[j]=min(dp[j],dp[i]+r[i][j]) (i>j)
(3)列表
(4)初始化dp数组dp[1]=0,其余的dp[i]全部为inf,遍历每一个出租站i,对于每个i再遍历后边的出租站j,更新dp[j]。
(5)时间复杂度:o(n2)空间复杂度:o(n2)
def min_rent(n, r):# float('inf') 正无穷大 float('-inf')dp = [float('inf')] * (n + 1)dp[1] = 0for i in range(1, n):for j in range(i + 1, n + 1):dp[j] = min(dp[j], dp[i] + r[i][j])return dp[n]n = int(input())
r = [[0] * (n + 1) for _ in range(n + 1)]
for i in range(1, n + 1):values = list(map(int, input().split()))for j in range(i + 1, n + 1):r[i][j] = values[j - i - 1]r[j][i] = r[i][j]result = min_rent(n, r)
print(result)
3、三个矩阵大小分别为3*4·4*5·5*6,求其相乘的最优结合次序?
(1)写出所用的求解方法(4分)
(2)描述该方法求解的步骤(4分)
(3)给出求解过程(4分)
(4)给出求解结果(2分)
例题分析:
(1) 求解方法:动态规划
(2) 求解步骤:
1. 创建一个二维数组 `dp`,其中 `dp[i][j]` 表示从第 `i` 个矩阵到第 `j` 个矩阵相乘所需的最小计算次数。
2. 初始化 `dp`,将对角线上的元素设为 0,表示单个矩阵相乘的次数为 0。
3. 使用两重循环遍历所有可能的长度 `len`(从 2 开始到矩阵总数),在每个长度下再使用一个循环遍历所有起始位置 `i`,计算相应的结束位置 `j`。
4. 对于每个位置 `(i, j)`,遍历中间位置 `k`(从 `i` 到 `j-1`),更新 `dp[i][j]` 的值为 `dp[i][k] + dp[k+1][j] + matrix[i].row * matrix[k].col * matrix[j].col` 的最小值,其中 `matrix[i].row` 表示第 `i` 个矩阵的行数,`matrix[k].col` 表示第 `k` 个矩阵的列数,`matrix[j].col` 表示第 `j` 个矩阵的列数。
5. 最终 `dp[0][n-1]` 中存储的值即为所有矩阵相乘的最小计算次数。
(3) 求解过程:
def matrix_chain_order(p):n = len(p) - 1m = [[0 for _ in range(n)] for _ in range(n)]s = [[0 for _ in range(n)] for _ in range(n)]for l in range(2, n):for i in range(n - l + 1):j = i + l - 1m[i][j] = float('inf')for k in range(i, j):q = m[i][k] + m[k + 1][j] + p[i] * p[k + 1] * p[j + 1]if q < m[i][j]:m[i][j] = qs[i][j] = kreturn s
def print_optimal_parens(s, i, j):if i == j:print(f"A{i}", end="")else:print("(", end="")print_optimal_parens(s, i, s[i][j])print_optimal_parens(s, s[i][j] + 1, j)print(")", end="")
def main():p = [3, 4, 5, 6]s = matrix_chain_order(p)print_optimal_parens(s, 0, len(p) - 2)print()
if __name__ == "__main__":main()
4、输入一个无重复字符的字符串s,打印出该字符串中字符的所有排列,请使用回溯法求解该问题。
(1)请定义问题的解空间。(2分)
〔2)写出解空间的组织结构。〔1分)
(3)写出约束条件和限界条件。(2分)
(4)写出该问题的算法步骤。(6分)
〔5)给定‘abc’,请给出算法求解的结果。〔2分)
例题分析:
(1) 解空间:所有无重复字符的字符串的排列。
(2) 解空间的组织结构:使用一个数组 `visited` 记录每个字符是否被访问过,使用一个字符串 `path` 记录当前路径上的字符排列。
(3) 约束条件和限界条件:
- 约束条件:每个字符只能被使用一次,保证排列的唯一性。
- 限界条件:当当前路径上的字符数等于输入字符串的长度时,表示已经得到一个完整的排列。
(4) 算法步骤:
1. 初始化一个长度为字符串长度的 `visited` 数组,全部初始化为 `false`,表示所有字符均未被访问过。
2. 初始化一个空的字符串 `path`,用于记录当前路径上的字符排列。
3. 从第一个字符开始,调用回溯函数,遍历所有可能的排列。
4. 回溯函数的参数包括当前路径 `path`、访问状态数组 `visited`、输入字符串 `s`,以及结果集合 `res`。
5. 在回溯函数中,遍历字符串 `s` 的每个字符,如果该字符未被访问过,则将其加入到 `path` 中,并将 `visited` 对应位置设为 `true`,然后递归调用回溯函数。
6. 当 `path` 的长度等于 `s` 的长度时,表示已经得到一个排列,将 `path` 加入到结果集合 `res` 中。
7. 回溯结束后,返回结果集合 `res`。
(5) 算法求解结果:
def backtrack(path, visited, s, res):if len(path) == len(s):res.append(path)returnfor i in range(len(s)):if not visited[i]:visited[i] = Truebacktrack(path + s[i], visited, s, res)visited[i] = Falsedef permutation(s):res = []visited = [False] * len(s)backtrack("", visited, s, res)return ress = "abc"
result = permutation(s)
for perm in result:print(perm)
5、最优装载问题:有n个物品,第i个物品的重量为wi。在不超过最大承载量的范围内,你要选择尽可能多的物品。请问你最多可以选择多少件物品?
(1〉你有几种方法求解该问题,请写出方法名称。(要求至少写出2种)(4分)
(2〉请从你的解决方法中,选择1种方法,设计具体求解步骤。(6分)
(3〉根据你的求解步骤,使用给定的输入样例,写出每个步骤的处理结果。(4分)
例题分析:
可以使用数组存储每一个物品的重量,然后从小到大给数组排个序,循环小于最大承载量,可以累加判断也可以相减判断。
(1)贪心策略、动态规划
(2)贪心策略:每次选择当前最优解。将物品按照重量从小到大排序。从最轻的物品开始依次尝试装载,直到超过最大承载量为止。返回装载的物品数量。
(3)代码:
def load_problem(weights, c):weights.sort()tsum = 0.0temp = 0for weight in weights:tsum += weightif tsum <= c:temp += 1else:breakreturn tempdef main():c, num = map(float, input().split())weights = list(map(float, input().split()))result = load_problem(weights, c)print(result)if __name__ == "__main__":main()
输入:
10 3
1 3 13
输出:
2