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 = weight
self.value = value
self.value_per_weight = value / weight
def fractional_knapsack(coins, T):
coins.sort(key=lambda x: x.value_per_weight, reverse=True)
total_value = 0
remaining_capacity = T
for item in coins:
if remaining_capacity == 0:
break
if remaining_capacity >= item.weight:
total_value += item.value
remaining_capacity -= item.weight
else:
total_value += item.value_per_weight * remaining_capacity
break
return total_value
if __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] = 0
for 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) - 1
m = [[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 - 1
m[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] = q
s[i][j] = k
return 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)
return
for i in range(len(s)):
if not visited[i]:
visited[i] = True
backtrack(path + s[i], visited, s, res)
visited[i] = False
def permutation(s):
res = []
visited = [False] * len(s)
backtrack("", visited, s, res)
return res
s = "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.0
temp = 0
for weight in weights:
tsum += weight
if tsum <= c:
temp += 1
else:
break
return temp
def 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