目录
一、线性规划
1.线性规划定义:线性规划(Linear Programming,LP)是一种用于优化线性目标函数的数学方法,该目标函数受制于一组线性不等式或等式约束。线性规划广泛应用于资源分配、生产调度、物流规划等领域。
一般有一个要求max或者min的目标函数,在问题给出的约束条件下求解。
2.规划问题数学模型的3个要素:
决策变量 | 问题中要确定的未知量,用于表明规划问题中用数量表示的方案措施等 |
目标函数 | 是决策变量的函数,优化目标通常求max或者min |
约束条件 | 决策变量的取值所受到的约束和限制条件,通常由含决策变量的等式不等式表示 |
3.线性规划模型的一般步骤:
第一步:分析问题,找出决策变量
第二步:根据问题所给条件,找出决策变量必须满足的一组线性等式或不等式约束,即约束条件
第三步:根据问题的目标,构造关于决策变量的一个线性函数,即为目标函数
有了决策变量、约束条件和目标函数这三个要素之后,一个线性规划模型就建立起来了。
4.例题:
目标:最大化z = 3x + 2y
约束条件:
1.x + y <= 4
2.x +2y <= 5
3.x >= 0, y >= 0
5.代码:
使用python语言,并且需要pulp库
from pulp import LpMaximize, LpProblem, LpVariable
# 创建一个最大化问题
problem = LpProblem("Maximize_Z", LpMaximize)
# 定义变量
x = LpVariable("x", lowBound=0) # x >= 0
y = LpVariable("y", lowBound=0) # y >= 0
# 添加目标函数
problem += 3 * x + 2 * y, "Objective"
# 添加约束条件
problem += x + y <= 4, "Constraint 1"
problem += x + 2 * y <= 5, "Constraint 2"
# 求解问题
problem.solve()
# 输出结果
print(f"Status: {problem.status}")
print(f"Objective value: {problem.objective.value()}")
for variable in problem.variables():
print(f"{variable.name} = {variable.varValue}")
二、整数规划
1.整数规划定义:整数规划(Integer Programming, IP)是线性规划的一个分支,其特点是在求解过程中,要求所有或部分决策变量必须取整数值。整数规划在很多实际问题中有重要应用,例如生产计划、排班、物流和运输等。
2.几个关于整数线性规划的问题:背包问题、标准指派问题、旅行商问题
3.非线性约束条件的线性化:在实际问题中,有时引入0-1变量可以把一些特定的非线性约束条件进行线性化
4.蒙特卡洛法
蒙特卡洛发也称为计算机随机模拟法,原于赌城摩纳哥的蒙特卡洛。它是基于大量时间的统计结果来实现一些确定性问题。
因为非线性(整数)规划目前尚未有成熟准确的求解方法,所以用蒙特卡洛法可以得到一些结果。
例如求解圆周率,用蒙特卡洛求解代码为:
import random
def estimate_pi(num_samples):
# 初始化在圆内的点的计数
inside_circle = 0
# 循环生成随机样本点
for _ in range(num_samples):
# 生成一个随机点 (x, y),x 和 y 的值在 [0, 1] 区间内
x = random.uniform(0, 1)
y = random.uniform(0, 1)
# 判断点 (x, y) 是否在单位圆内,即满足 x^2 + y^2 <= 1
if x**2 + y**2 <= 1:
inside_circle += 1
# 估计 π 的值,π ≈ 4 * (在圆内的点数 / 总点数)
pi_estimate = (inside_circle / num_samples) * 4
return pi_estimate
# 设置样本数量
num_samples = 100000
# 调用函数计算 π 的估计值
pi_estimate = estimate_pi(num_samples)
# 输出估计的 π 值
print(f"Estimated value of π: {pi_estimate}")
三、非线性规划
1.非线性规划定义:非线性规划(Nonlinear Programming, NLP)是优化问题的一种,其中目标函数或约束条件至少有一个是非线性的。非线性规划问题在许多实际应用中非常常见,如工程设计、经济学、能源管理等领域。
2.非线性规划的求解:
无约束非线性规划:用函数求极值的方法,求函数的梯度(偏导),一般有梯度下降法、牛顿法和拟牛顿法。
有约束非线性规划:一般尽量将约束问题转化为无约束问题。一般对只有等式约束的非线性规划可以用拉格朗日乘子法,对一般形式(可能有不等式约束无法用拉格朗日乘子法)可以用罚函数法。
3.非线性规划的python实现
问题目标:最小化f(x, y) = x^2 + y^2
约束条件:1.x + y = 1 2.x >= 0, y >= 0
一般还是用库来直接求解非线性规划问题
import numpy as np
from scipy.optimize import minimize
# 定义目标函数
def objective_function(x):
return x[0]**2 + x[1]**2
# 定义约束条件
def constraint_eq(x):
return x[0] + x[1] - 1
# 定义变量的边界
bounds = [(0, None), (0, None)]
# 定义约束
constraints = {'type': 'eq', 'fun': constraint_eq}
# constraints = [{'type': 'ineq', 'fun': lambda x: 1 - x[0] - x[1]}] 若约束是x+y>=1
# 初始猜测值
initial_guess = [0.5, 0.5]
# 使用SciPy的minimize函数进行优化
result = minimize(objective_function, initial_guess, bounds=bounds, constraints=constraints)
# 输出结果
print(f"Status: {result.success}")
print(f"Objective value: {result.fun}")
print(f"Solution: x = {result.x[0]}, y = {result.x[1]}")
4.凸规划:凸规划是非线性规划的一个重要子集,它的特征是在目标函数和约束条件上都有特定的“凸性”要求。可以求得全局最优解。
四、图与网络模型及方法
1.图与网络的基础理论
定义:图G是由一对集合(V,E)组成,V是顶点集,E是边集
分类:无向图、有向图、权重图
表示:邻接矩阵、邻接表
2.最短路算法
最短路算法是图论中的一个重要问题,旨在寻找从一个顶点到另一个顶点的最短路径。实际问题如管道铺设、线路安排、厂区布置、设备更新等,都可被归结为最短路径问题来解决。
固定起点的最短路:Dijkstra算法,适用于无负权边的图
原理:
- 使用优先队列(最小堆)来实现,逐步扩展到距离源点最近的点
- 每次从未访问的顶点中选择距离源点最近的点,更新其邻居的最短路径估计值
算法步骤:
- 初始化源顶点到所有其他顶点的距离为无穷大,源顶点到自己的距离为0。
- 使用优先队列(最小堆)选择当前距离最小的顶点。
- 更新与当前顶点相邻的顶点的距离。
- 重复步骤2和3,直到所有顶点都被处理。
代码:
import heapq
def dijkstra(graph, start):
# 初始化距离
dist = {vertex: float('infinity') for vertex in graph}
dist[start] = 0
priority_queue = [(0, start)]
while priority_queue:
current_distance, current_vertex = heapq.heappop(priority_queue)
# 如果当前距离大于记录的最短距离,跳过
if current_distance > dist[current_vertex]:
continue
# 更新相邻顶点的距离
for neighbor, weight in graph[current_vertex].items():
distance = current_distance + weight
if distance < dist[neighbor]:
dist[neighbor] = distance
heapq.heappush(priority_queue, (distance, neighbor))
return dist
# 示例图:顶点与边的权重
graph = {
'A': {'B': 1, 'C': 4},
'B': {'A': 1, 'C': 2, 'D': 5},
'C': {'A': 4, 'B': 2, 'D': 1},
'D': {'B': 5, 'C': 1}
}
# 计算从顶点 'A' 到其他顶点的最短路径
print(dijkstra(graph, 'A'))
所有顶点对之剑的最短路算法:Floyd(带负权边的图也适用)
步骤:
- 初始化距离矩阵,矩阵中[ i ][ j ]代表顶点 i 到顶点 j 的初始距离。
- 对每对顶点,尝试通过每个中间顶点更新最短路径。
- 遍历所有可能的中间顶点,更新最短路径。
代码:
def floyd_warshall(graph):
# 初始化距离矩阵
dist = {u: {v: float('infinity') for v in graph} for u in graph}
for u in graph:
dist[u][u] = 0
for v, w in graph[u].items():
dist[u][v] = w
# 更新距离矩阵
for k in graph:
for i in graph:
for j in graph:
if dist[i][j] > dist[i][k] + dist[k][j]:
dist[i][j] = dist[i][k] + dist[k][j]
return dist
# 示例图:顶点与边的权重
graph = {
'A': {'B': 1, 'C': 4},
'B': {'C': 2, 'D': 5},
'C': {'D': 1},
'D': {}
}
# 计算所有顶点对之间的最短路径
print(floyd_warshall(graph))
3.最小生成树
定义:最小生成树(Minimum Spanning Tree, MST)是一个包含图中所有顶点的无环子图,其边的权重之和最小。在图论和网络设计中,最小生成树有着广泛的应用,比如在设计和构建物理网络(如电信、电力网络)时,最小生成树可以帮助找到成本最低的网络连接方式。
基础概念:
- 生成树:包含图中所有顶点的树。
- 最小生成树:在所有生成树中,边的权重之和最小的那棵树。
- 边的权重:通常表示连接两个顶点的代价或距离。
两个算法:
kruiskal算法
原理:Kruskal算法基于贪心策略,逐步选择权重最小的边,加入生成树中,直到所有顶点都被连接。
步骤:
- 将图中的所有边按权重从小到大排序。
- 初始化一个空的最小生成树。
- 从最小权重的边开始,逐一检查是否会形成环路:
- 使用并查集(Union-Find)来检测是否形成环路。
- 如果不形成环路,则将这条边加入最小生成树。
- 重复步骤3,直到加入的边数等于顶点数减一。
代码:
class UnionFind:
def __init__(self, size):
self.root = [i for i in range(size)]
self.rank = [1] * size
def find(self, x):
if self.root[x] != x:
self.root[x] = self.find(self.root[x]) # 路径压缩
return self.root[x]
def union(self, x, y):
rootX = self.find(x)
rootY = self.find(y)
if rootX != rootY:
if self.rank[rootX] > self.rank[rootY]:
self.root[rootY] = rootX
else:
self.root[rootX] = rootY
if self.rank[rootX] == self.rank[rootY]:
self.rank[rootY] += 1
def kruskal(graph):
result = [] # 存储最小生成树的边
i, e = 0, 0 # 边的索引和已加入最小生成树的边数
graph = sorted(graph, key=lambda item: item[2]) # 按边的权重排序
uf = UnionFind(len(graph[0][0]))
while e < len(graph[0][0]) - 1:
u, v, w = graph[i]
i += 1
x = uf.find(u)
y = uf.find(v)
if x != y:
e += 1
result.append((u, v, w))
uf.union(x, y)
return result
# 示例图的边列表表示,每条边为 (起点, 终点, 权重)
graph = [
('A', 'B', 10),
('B', 'C', 15),
('A', 'C', 20),
('C', 'D', 30),
('B', 'D', 50)
]
print(kruskal(graph))
prim算法
原理:Prim算法也是基于贪心策略,从一个起始顶点出发,逐步扩展生成树,选择权重最小的边,使得生成树不断增加一个顶点。
步骤:
- 选择一个起始顶点,将其加入生成树。
- 初始化一个空的最小生成树。
- 从当前生成树中的顶点出发,选择一条权重最小的边,使得边的另一端的顶点不在生成树中。
- 将选中的边的另一端的顶点加入生成树,重复步骤3,直到生成树包含所有顶点。
代码:
import heapq
def prim(graph):
mst = [] # 存储最小生成树的边
visited = set() # 存储已访问的顶点
start = list(graph.keys())[0] # 随便选择一个起始顶点
visited.add(start)
edges = [(weight, start, to) for to, weight in graph[start].items()]
heapq.heapify(edges) # 将边转换为最小堆
while edges:
weight, from_node, to_node = heapq.heappop(edges)
if to_node not in visited:
visited.add(to_node)
mst.append((from_node, to_node, weight))
for next_to, next_weight in graph[to_node].items():
if next_to not in visited:
heapq.heappush(edges, (next_weight, to_node, next_to))
return mst
# 示例图的邻接表表示
graph = {
'A': {'B': 1, 'C': 4},
'B': {'A': 1, 'C': 2, 'D': 5},
'C': {'A': 4, 'B': 2, 'D': 1},
'D': {'B': 5, 'C': 1}
}
print(prim(graph))
4.着色问题
着色问题的基本概念
- 图着色:给图中的每个顶点分配一个颜色,使得相邻的顶点颜色不同。
- 图的染色数:图的着色问题的目标是找到使用的最小颜色数,这个最小颜色数称为图的染色数。
着色问题的类型
- 节点着色问题(Vertex Coloring):给图的每个顶点分配颜色。
- 边着色问题(Edge Coloring):给图的每条边分配颜色,使得相邻的边颜色不同。
着色问题的难度
- NP-难问题:节点着色问题是一个NP-难问题,特别是对于一般图来说,找到最小的染色数是一个计算复杂的问题。
- 特殊图的处理:一些特殊类型的图(如平面图、树等)可以用更高效的方法解决。
常见的算法和方法
- 贪心算法:一种简单而有效的方法,通过逐步分配颜色来解决着色问题。
- 回溯法:通过递归尝试不同的颜色分配,回溯到上一步寻找其他可能的解。
- 图的分解法:将图分解成多个子图,分别求解着色问题。
- 启发式算法和元启发式算法:如模拟退火、遗传算法、局部搜索等,用于近似求解。
5.最大流与最小费用流问题
最大流问题和最小费用流问题是图论中的两个重要问题,广泛应用于网络设计、资源分配等领域。下面分别介绍这两个问题及其解决算法。
最大流问题
概述
最大流问题是在一个流网络中找到从源点(source)到汇点(sink)能够传输的最大流量。流网络是一个有向图,其中每条边都有一个容量限制,表示流量的上限。
基本概念
- 流网络:包含一个源点 s 和一个汇点 t。
- 容量:每条边 (u,v)的容量 c(u,v)。
- 流量:每条边 (u,v)的实际流量 f(u,v)。
- 流守恒:对于除源点和汇点外的每个顶点 v,进入 v 的流量等于离开 v的流量。
Ford-Fulkerson算法
Ford-Fulkerson算法是一种增广路径算法,基于寻找增广路径并不断增加流量,直到没有增广路径为止。
步骤
- 初始化:所有边的初始流量为0。
- 寻找增广路径:在残余网络中找到一条从源点到汇点的路径,使得所有边都有剩余容量。
- 更新流量:沿增广路径更新流量。
- 重复:重复步骤2和3,直到找不到增广路径。
代码
from collections import deque
def bfs(rGraph, s, t, parent):
# 创建一个 visited 数组,标记节点是否已访问
visited = [False] * len(rGraph)
queue = deque([s])
visited[s] = True
# BFS遍历寻找增广路径
while queue:
u = queue.popleft()
for ind, val in enumerate(rGraph[u]):
if not visited[ind] and val > 0: # 未访问且有剩余容量的边
queue.append(ind)
visited[ind] = True
parent[ind] = u
if ind == t:
return True
return False
def ford_fulkerson(graph, source, sink):
# 复制图的邻接矩阵作为残余图
rGraph = [row[:] for row in graph]
parent = [-1] * len(graph)
max_flow = 0
# 当存在增广路径时,继续寻找并更新流量
while bfs(rGraph, source, sink, parent):
path_flow = float('Inf')
s = sink
# 找到增广路径中最小的边容量
while s != source:
path_flow = min(path_flow, rGraph[parent[s]][s])
s = parent[s]
v = sink
while v != source:
u = parent[v]
rGraph[u][v] -= path_flow # 更新残余图的容量
rGraph[v][u] += path_flow # 反向边的容量增加
v = parent[v]
max_flow += path_flow
return max_flow
# 示例图
graph = [
[0, 16, 13, 0, 0, 0],
[0, 0, 10, 12, 0, 0],
[0, 4, 0, 0, 14, 0],
[0, 0, 9, 0, 0, 20],
[0, 0, 0, 7, 0, 4],
[0, 0, 0, 0, 0, 0]
]
source = 0
sink = 5
print("The maximum possible flow is", ford_fulkerson(graph, source, sink))
最小费用问题
概述
最小费用流问题是在一个流网络中找到一种流方案,使得从源点到汇点的总流量满足需求,并且总费用最小。每条边除了容量之外,还具有一个单位流量的费用。
基本概念
- 费用:每条边 (u,v) 的单位流量费用 cost(u,v)。
- 总费用:流量方案的总费用为所有边的费用乘以流量的总和。
解决方法
最小费用流问题可以通过修改后的Bellman-Ford算法或Dijkstra算法结合增广路径算法来解决。
代码:
from collections import deque
def spfa(graph, cost, source, sink, n):
dist = [float('inf')] * n
in_queue = [False] * n
parent = [-1] * n
flow = [0] * n
dist[source] = 0
flow[source] = float('inf')
queue = deque([source])
in_queue[source] = True
while queue:
u = queue.popleft()
in_queue[u] = False
for v in range(n):
if graph[u][v] > 0 and dist[v] > dist[u] + cost[u][v]:
dist[v] = dist[u] + cost[u][v]
parent[v] = u
flow[v] = min(flow[u], graph[u][v])
if not in_queue[v]:
queue.append(v)
in_queue[v] = True
return parent, flow[sink] if dist[sink] != float('inf') else 0
def min_cost_max_flow(graph, cost, source, sink):
n = len(graph)
max_flow = 0
min_cost = 0
while True:
parent, path_flow = spfa(graph, cost, source, sink, n)
if path_flow == 0:
break
max_flow += path_flow
v = sink
while v != source:
u = parent[v]
graph[u][v] -= path_flow # 更新流量
graph[v][u] += path_flow # 反向边的流量增加
min_cost += path_flow * cost[u][v]
v = parent[v]
return max_flow, min_cost
# 示例图
graph = [
[0, 3, 2, 0, 0],
[0, 0, 1, 3, 0],
[0, 0, 0, 2, 3],
[0, 0, 0, 0, 4],
[0, 0, 0, 0, 0]
]
cost = [
[0, 2, 4, 0, 0],
[0, 0, 1, 2, 0],
[0, 0, 0, 1, 3],
[0, 0, 0, 0, 2],
[0, 0, 0, 0, 0]
]
source = 0
sink = 4
max_flow, min_cost = min_cost_max_flow(graph, cost, source, sink)
print("Maximum flow:", max_flow)
print("Minimum cost:", min_cost)