1、分治法
把一个复杂的问题分成两个或更多的相同或相似的子问题,再把子问题分成更小的子问题,直到最后子问题可以简单的直接求解,子问题的解的合并即是原问题的解。
二分查找算法:
# 查找的数字
array = [i for i in range(100)]
# 首位值
low = 0
# 末位值
height = len(array)-1
# 设定需要找到的数字
num = 3
# 循环查找
while True:
# 获取中位数
mid = int((low+height)/2)
print(array[mid])
# 如果中位数小于查找值
if array[mid] < num:
# 重置低位数
low = mid+1
# 如果中位数大于查找值 则锁定前半段
elif array[mid] > num:
# 重置低位数
low = mid-1
# 找到数字则打印该值下标 终止循环
elif array[mid]==num:
print('find it:',array[mid],'index:',mid)
break
2、动态规划
每次决策依赖于当前状态,又随即引起状态的转移。一个决策序列就是在变化的状态中产生出来的,这种多阶段最优化决策解决问题的过程就称为动态规划。
背包问题:
背包问题是动态规划中中一个很经典的问题 假设张三要去野营,他准备了以下物品:
每样东西都有相应的价值,可呆呆的他在收拾背包时发现,他的背包 最大容量只有6斤,装不下所有的东西,只能从这堆东西中挑选 组合价值 最高的物品。
约束条件: 将背包的容量大小划分成不同容量大小的子背包,计算每个子背包的最大价值
物品重量不能超过当前背包容量,不可能将重量为5斤的物品放进容量为2斤的背包里。
每个子问题取得一次物品,就不能再取相同的物品,要么这个要么这个,拿了就没
比如此时 背包容量为6斤,拿了一瓶水后 占用了背包容量3斤,剩余容量为3斤 我要么再拿一本书和一件衣服(1+2),要么再拿食物和一部手机(2+1)等,不可能再去拿一瓶水 。
def main() -> list:
items = [ # 物品项
{"name": "水", "weight": 3, "value": 10},
{"name": "书", "weight": 1, "value": 3},
{"name": "食物", "weight": 2, "value": 9},
{"name": "小刀", "weight": 3, "value": 4},
{"name": "衣物", "weight": 2, "value": 5},
{"name": "手机", "weight": 1, "value": 10}
]
max_capacity = 6 # 约束条件为 背包最大承重为6
dp = [[0] * (max_capacity + 1) for _ in range(len(items) + 1)]
for row in range(1, len(items) + 1): # row 代表行
for col in range(1, max_capacity + 1): # col 代表列
weight = items[row - 1]["weight"] # 获取当前物品重量
value = items[row - 1]["value"] # 获取当前物品价值
if weight > col: # 判断物品重量是否大于当前背包容量
dp[row][col] = dp[row - 1][col] # 大于直接取上一次最优结果 此时row-1代表上一行
else:
# 使用内置函数max(),将上一次最优结果 与 当前物品价值+剩余空间可利用价值 做对比取最大值
dp[row][col] = max(value + dp[row - 1][col - weight], dp[row - 1][col])
return dp
dp = main()
for i in dp: # 打印数组
print(i)
print(dp[-1][-1]) # 打印最优解的价值和
3、回溯算法
回溯法不同于动态规划。使用回溯法的关键是生成解空间,该问题的解空间是一个子集树,以深度优先的方式向下搜索判断,对于不符合条件的,采用剪枝函数直接结束本条路的循环。以节省时间。
输出一个数组中所有长度为k的组合。
def backtrack(combination, start, k):
if len(combination) == k: # 终止条件,找到一个长度为k的组合
print(combination)
return
for i in range(start, len(arr)):
combination.append(arr[i]) # 做出选择
backtrack(combination, i + 1, k) # 递归进入下一层,并从下一位置开始选择
combination.pop() # 撤销选择
arr = [1, 2, 3, 4, 5]
k = 3
backtrack([], 0, k)
4、分支界限法
分支界限法是一种用于求解最优化问题的算法。它通过将搜索空间划分为多个分支,并针对每个分支应用启发式策略来减少搜索空间,以便更快地找到最优解。
0-1背包问题 如何在给定的背包容量下,选择一组物品放入背包,以使得物品的总价值最大化。每件物品只能选择放入背包(即取 0 个或 1 个),不能切割物品。
class Item:
def __init__(self, weight, value):
self.weight = weight # 物品重量
self.value = value # 物品价值
def branch_and_bound_knapsack(items, capacity):
n = len(items)
items.sort(key=lambda x: x.value/x.weight, reverse=True) # 按照单位价值降序排序
curr_weight = 0 # 当前背包重量
curr_value = 0 # 当前背包价值
best_value = 0 # 最优解的背包价值
def bound(node):
nonlocal curr_weight, curr_value, best_value
if node.level >= n:
return 0
# 背包容量剩余不能放下任何一个完整的物品
if curr_weight + items[node.level].weight > capacity:
return 0
# 能够放下整个物品时,更新当前背包重量和价值
curr_weight += items[node.level].weight
curr_value += items[node.level].value
# 当前背包价值超过了最优解时,回溯
if curr_value > best_value:
curr_weight -= items[node.level].weight
curr_value -= items[node.level].value
return 0
# 计算对于下一级节点的期望上界
bound_value = node.value + (capacity - curr_weight) * items[node.level].value / items[node.level].weight
# 更新最优解
if bound_value > best_value:
best_value = bound_value
# 返回子问题的期望上界
return bound_value
class Node:
def __init__(self, level, value):
self.level = level # 当前层级
self.value = value # 到达该层的背包价值
queue = [Node(0, 0)] # 用于存放待处理的节点队列
while queue:
node = queue.pop(0) # 取出队列中的节点进行处理
# 左子节点:不放入背包
queue.append(Node(node.level + 1, node.value))
# 右子节点:放入背包
value = node.value + items[node.level].value # 新的背包价值
if value > best_value: # 只有当该节点的目标函数值大于当前最优解时才进行处理
best_value = value
if node.level + 1 < n:
queue.append(Node(node.level + 1, value))
return best_value
items = [
Item(2, 10),
Item(5, 20),
Item(3, 15),
Item(7, 25),
Item(4, 12)
]
capacity = 10
best_value = branch_and_bound_knapsack(items, capacity)
print("最优解的背包价值:", best_value)
5、贪心算法