贪心算法是一种常见的算法范式,通常在解决最优化问题中使用。
贪心算法是一种在每一步选择中都采取当前状态下最优决策的算法范式。其核心思想是选择每一步的最佳解决方案,以期望达到最终的全局最优解。这种算法特点在于只考虑局部最优解,而不会回溯或重新考虑已经做出的决策,也不保证能够解决所有问题。尽管贪心算法并非适用于所有问题,但对于某些特定类型的问题,贪心算法的思路简单、高效。
题目描述:
作业j从sj开始,在fj结束
如果两个作业不重叠,则它们是兼容的。
目标:找到相互兼容作业的最大子集。
解题思路分析:
要使用贪心算法解决这个问题,我们可以按照以下步骤进行:
python代码:
# 定义一个job类
class Job:
def __init__(self, start_time, finsh_time):
self.start_time = start_time
self.finsh_time = finsh_time
def find_max_compatible_jobs(jobs):
# 使用sort函数对jobs列表进行排序 这里按照结束时间升序排列
jobs.sort(key=lambda x: x.finsh_time)
max_jobs_subset = []
last_time = float('-inf')
for job in jobs:
if job.start_time >=last_time:
max_jobs_subset.append(job)
last_time = job.finsh_time
# 这里直接返回最大公共子集
return max_jobs_subset
# 编写测试函数
# 用户首先输入作业的数量
while True:
try:
job_count = int(input('请输入作业总数:'))
if job_count < 1:
raise ValueError
break
except ValueError:
print('作业总数必须是一个正整数')
# 定义工作列表存储用户输入的工作
jobs = []
# 依次输入每个作业的起始时间和结束时间
for i in range(job_count):
while True:
try:
start_time = int(input('请输入第{}个作业的起始时间:'.format(i + 1)))
finsh_time = int(input('请输入第{}个作业的结束时间:'.format(i + 1)))
if finsh_time < start_time:
raise ValueError
break
except ValueError:
print('结束时间必须晚于开始时间')
job = Job(start_time,finsh_time)
jobs.append(job)
# 调用函数找到最大互相兼容作业子集
max_compatible_jobs = find_max_compatible_jobs(jobs)
print('相互兼容的最大工作子集:')
for job in max_compatible_jobs:
print('作业开始时间:{},作业结束时间:{}'.format(job.start_time,job.finsh_time))
题目描述:
讲座j 从sj开始,到fj结束;
目标:找到安排所有讲座的最少教室数量
这样就不会在同一个房间里同时发生两次讲座。
解题思路分析:
具体的求解过程如下:
sorted
函数,并将 key
参数设置为讲座的结束时间。这样可以保证在贪心算法中,我们首先处理最早结束的讲座。python代码:
class Lecture:
def __init__(self, start_time, end_time):
self.start_time = start_time
self.end_time = end_time
def get_valid_lectures():
lectures = []
while True:
try:
num_lectures = int(input("请输入讲座数量: "))
if num_lectures <= 0:
raise ValueError("讲座数量必须大于0")
for i in range(num_lectures):
start_time = int(input(f"请输入讲座{i + 1}的开始时间: "))
end_time = int(input(f"请输入讲座{i + 1}的结束时间: "))
if start_time >= end_time:
raise ValueError(f"讲座{i + 1}的开始时间必须早于结束时间")
if start_time < 0:
raise ValueError(f'讲座{i+1}的开始时间必须是正数')
if end_time < 0:
raise ValueError(f'讲座{i+1}的结束时间必须是正数')
lecture = Lecture(start_time, end_time)
lectures.append(lecture)
break
except ValueError as e:
print("输入错误:", e)
return lectures
def minimum_classrooms(lectures):
sorted_lectures = sorted(lectures, key=lambda x: x.end_time)
classrooms = []
for lecture in sorted_lectures:
found_classroom = False
for classroom in classrooms:
if lecture.start_time >= classroom[-1].end_time:
classroom.append(lecture)
found_classroom = True
break
if not found_classroom:
classrooms.append([lecture])
return len(classrooms)
# 获取有效的讲座数据
valid_lectures = get_valid_lectures()
# 调用函数获取最小教室数量
min_classrooms = minimum_classrooms(valid_lectures)
print("需要的最小教室数量:", min_classrooms)
题目描述:
・单个资源一次处理一个作业。
・作业j需要tj个处理时间单位,并且在时间dj到期
・如果j在时间sj开始,它在时间fj=sj+tj结束
・潜伏时间:ℓj=最大{0,fj–dj}。
・目标:安排所有作业以最大限度地减少最大延迟L=maxjℓj
解题思路分析:
dj
进行排序,以确保优先处理截止时间最早的任务。current_time
,并初始化为 0。fj = current_time + tj
,其中 tj
是当前任务的处理时间。ℓj = max(0, fj - dj)
。L
,如果当前任务的延迟时间 ℓj
大于 L
,则将 L
更新为 ℓj
。current_time
,将其设置为当前任务的完成时间 fj
。L
。Python代码:
class Job:
def __init__(self, processing_time, deadline):
self.processing_time = processing_time
self.deadline = deadline
def minimum_lateness(jobs):
jobs.sort(key=lambda x: x.deadline) # 按照截止时间排序
L = 0 # 最大延迟
current_time = 0 # 当前时间
for job in jobs:
# 验证处理时间和截止时间的格式和数值
if not isinstance(job.processing_time, int) or job.processing_time <= 0:
raise ValueError("Processing time should be a positive integer.")
if not isinstance(job.deadline, int) or job.deadline <= 0:
raise ValueError("Deadline should be a positive integer.")
# 计算完成时间和延迟时间
finish_time = current_time + job.processing_time
lateness = max(0, finish_time - job.deadline)
# 更新最大延迟
L = max(L, lateness)
# 更新当前时间
current_time = finish_time
return L
# 用户输入数据
n = None
while not isinstance(n, int) or n <= 0:
try:
n = int(input("Enter the number of jobs: "))
if n <= 0:
print("Number of jobs should be a positive integer.")
except ValueError:
print("Invalid input. Number of jobs should be an integer.")
jobs = []
for i in range(n):
print(f"Enter information for job {i+1}:")
processing_time = None
while not isinstance(processing_time, int) or processing_time <= 0:
try:
processing_time = int(input("Enter the processing time: "))
if processing_time <= 0:
print("Processing time should be a positive integer.")
except ValueError:
print("Invalid input. Processing time should be an integer.")
deadline = None
while not isinstance(deadline, int) or deadline <= 0:
try:
deadline = int(input("Enter the deadline: "))
if deadline <= 0:
print("Deadline should be a positive integer.")
except ValueError:
print("Invalid input. Deadline should be an integer.")
job = Job(processing_time, deadline)
jobs.append(job)
try:
max_lateness = minimum_lateness(jobs)
print("The maximum lateness is:", max_lateness)
except ValueError as e:
print("Invalid input:", str(e))
分治算法是一种重要的算法范式,它将一个大问题分解成几个小问题,分别解决这些小问题,然后将其合并以得到原始问题的解。其核心思想是递归地把问题分解成更小的子问题,并将这些子问题独立地解决。
分治算法的设计思路通常分为三个步骤:
分治算法常用于解决递归性质的问题,例如归并排序、快速排序、二叉树相关问题等。虽然它的效率通常较高,但并非适用于所有问题。
题目描述:
最近配对问题。给定平面上的n个点,找一对点,使得它们之间的欧几里得距离最小。
解题思路分析:
分治法是一种解决问题的方法,将问题划分为更小的子问题,然后将子问题的解合并起来得到原问题的解。下面是使用分治法解决最近配对问题的详细步骤:
python代码:
import math
# 定义一个坐标类 包括x和y
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
# 求解两个点的欧几里得距离
def distance(p1, p2):
return math.sqrt((p1.x-p2.x)**2+(p1.y-p2.y)**2)
# 使用分治法求解最近配对问题
def closest_pair(points):
n = len(points)
# 这里如果n小于等于3 即直接使用暴力法计算
if n <= 3:
# 默认最小距离为最大值
min_distance = float('inf')
closest_points = None
for i in range(n):
for j in range(i+1, n):
if distance(points[i], points[j]) < min_distance:
min_distance = distance(points[i], points[j])
closest_points = (points[i], points[j])
#函数直接返回最短距离点对和最小距离
return closest_points, min_distance
# 如果点集个数大于3 则使用分治法求解
# 首先对点集按照x升序排列
points.sort(key=lambda p: p.x)
# 然后将点集分为两部分
mid = n // 2
left_points = points[:mid]
right_points = points[mid:]
# 分别求解左 右区间的最小距离与最近点集
closest_left, d_left = closest_pair(left_points)
closest_right, d_right = closest_pair(right_points)
if d_left < d_right:
d = d_left
closest_points = closest_left
else:
d = d_right
closest_points = closest_right
mid_x = (points[mid-1].x+points[mid].x)/2
mid_points = [p for p in points if abs(p.x-mid_x) < d]
# 然后把这些点按照y值排序
mid_points.sort(key=lambda p: p.y)
min_distance = d
for i in range(len(mid_points)):
for j in range(i+1, min(i+8, len(mid_points))):# 最多计算后序7个点的距离
if distance(points[i], points[j]) < d:
min_distance = distance(points[i], points[j])
closest_points = (points[i], points[j])
return closest_points, min_distance
# 获取用户输入的点坐标
points = []
while True:
x_input = input("请输入点的 x 坐标(输入 'end' 结束输入): ")
if x_input == 'end':
break
y_input = input("请输入点的 y 坐标(输入 'end' 结束输入): ")
if y_input == 'end':
break
try:
x = float(x_input)
y = float(y_input)
# 进行校验逻辑,例如对 x 和 y 坐标进行合法性检查、范围检查等,根据需求自行定义
# 如果校验通过,则创建 Point 对象并添加到 points 列表中
point = Point(x, y)
points.append(point)
except ValueError:
print("输入的坐标不合法,请重新输入")
# 调用 closest_pair() 函数获取最近配对的结果
closest_points, min_distance = closest_pair(points)
point1, point2 = closest_points
# 输出最近配对的距离和坐标
print("The closest pair distance is:", min_distance)
print("The closest pair coordinates are:", (point1.x, point1.y), (point2.x, point2.y))
题目描述:
给定一个完全有序的宇宙中的n个元素,找出第k个最小的元素。
解题思路分析:
python代码实现:
def choose_pivot(arr, low, high):
mid = (low + high) // 2
if arr[low] <= arr[mid] <= arr[high] or arr[high] <= arr[mid] <= arr[low]:
return mid
elif arr[mid] <= arr[low] <= arr[high] or arr[high] <= arr[low] <= arr[mid]:
return low
else:
return high
# 分区 分为小于基准元素和大于基准元素两个数组
def partition(arr, low, high):
# 通过三数取中找到基准元素
pivot_index = choose_pivot(arr, low, high)
pivot = arr[pivot_index]
i = low - 1
arr[pivot_index], arr[high] = arr[high], arr[pivot_index]
for j in range(low, high):
if arr[j] <= pivot:
i += 1
arr[i], arr[j] = arr[j], arr[i]
arr[i+1], arr[high] = arr[high], arr[i+1]
return i+1
def kth_smallest(arr, low, high, k):
if low == high:
return arr[low]
pivot_index = partition(arr, low, high)
if pivot_index == k-1:
return arr[pivot_index]
elif pivot_index > k-1:
return kth_smallest(arr, low, pivot_index-1, k)
else:
return kth_smallest(arr, pivot_index+1, high, k)
# 用户输入数据
arr_valid = False
arr = []
while not arr_valid:
arr = input("请输入由空格分隔的整数数组:").split()
if arr:
try:
arr = [int(num) for num in arr]
arr_valid = True
except ValueError:
print("输入无效,请重新输入整数数组。")
else:
print("输入数组不能为空。")
# 校验数据有效性
k_valid = False
k = 0
while not k_valid:
k = input("请输入要查找的第几小的元素的值:")
if k.isdigit():
k = int(k)
if 1 <= k <= len(arr):
k_valid = True
else:
print("输入的值超出了数组范围,请重新输入。")
else:
print("输入无效,请重新输入一个整数。")
kth_smallest_element = kth_smallest(arr, 0, len(arr)-1, k)
print(f"The {k}th smallest element is: {kth_smallest_element}")
题目描述:
医院-医生算法是一种解决问题的方法,旨在以最佳方式将医生分配给医院。该算法确保每个医院都有足够数量的医生,并根据特定的标准将每个医生分配到合适的医院。
解题思路分析:
以下是使用稳定婚姻算法解决医院-医生分配问题的步骤:
python代码:
def stable_marriage(doctors, hospitals):
doctor_preferences = {}
hospital_preferences = {}
doctor_matches = {}
hospital_matches = {}
# 初始化医生和医院的偏好列表
for doctor in doctors:
doctor_preferences[doctor] = doctors[doctor]
for hospital in hospitals:
hospital_preferences[hospital] = hospitals[hospital]
# 将所有医生和医院都标记为未匹配状态
for doctor in doctors:
doctor_matches[doctor] = None
for hospital in hospitals:
hospital_matches[hospital] = None
while None in doctor_matches.values():
for doctor in doctors:
if doctor_matches[doctor] is None:
for hospital in doctor_preferences[doctor]:
if hospital_matches[hospital] is None:
doctor_matches[doctor] = hospital
hospital_matches[hospital] = doctor
break
else:
current_match = hospital_matches[hospital]
if hospital_preferences[hospital].index(doctor) < hospital_preferences[hospital].index(current_match):
doctor_matches[doctor] = hospital
doctor_matches[current_match] = None
hospital_matches[hospital] = doctor
break
return doctor_matches
# 用户输入医生和医院的偏好
doctors = {}
hospitals = {}
n_doctors = int(input("请输入医生人数:"))
n_hospitals = int(input("请输入医院数量:"))
for i in range(n_doctors):
doctor_name = input(f"请输入第{i+1}位医生的姓名:")
doctor_preferences = input(f"请输入{doctor_name}医生对医院的偏好(以空格分隔):").split()
doctors[doctor_name] = doctor_preferences
for i in range(n_hospitals):
hospital_name = input(f"请输入第{i+1}家医院的名称:")
hospital_preferences = input(f"请输入{hospital_name}医院对医生的偏好(以空格分隔):").split()
hospitals[hospital_name] = hospital_preferences
# 调用稳定婚姻算法
matches = stable_marriage(doctors, hospitals)
# 输出医生-医院匹配结果
for doctor, hospital in matches.items():
print(f"{doctor} 匹配到 {hospital}")
动态规划(Dynamic Programming)是一种优化问题求解的算法思想,适用于具有重叠子问题和最优子结构性质的问题。它将问题分解为一系列重叠的子问题,并通过保存子问题的解来避免重复计算,从而实现对整个问题的高效求解。
动态规划算法通常遵循以下步骤:
确定状态:将原问题划分为若干个子问题,并定义状态表示问题的解。
定义状态转移方程:通过递推关系式来描述子问题之间的关系,即如何通过已知的子问题的解来计算当前问题的解。
确定初始条件:确定最简单的子问题的解,作为递推的起点。
确定计算顺序:根据子问题之间的依赖关系,确定计算的顺序,通常采用自底向上的方式进行计算。
计算最优解:依据状态转移方程,按照确定的计算顺序,通过递推计算出问题的最优解。
构造最优解:根据计算得到的最优解和保存的状态信息,构造出原问题的最优解。
动态规划算法的核心思想是利用子问题的解来求解更大规模的问题,通过保存子问题的解,避免了重复计算,从而显著提高了算法的效率。动态规划常用于求解最优化问题,如最短路径问题、背包问题、序列比对等。
经典动态规划算法:接缝雕刻;文本相似和不同比较;样条曲线的赋值等。
以下是一些动态规划常用于解决的经典问题:
问题描述:
作业j从 s j s_{j} sj开始,在 f j f_{j} fj结束,且每个作业有个权重 w j w_{j} wj
如果两个作业不重叠,则它们是兼容的。
目标:找到相互兼容作业且权重最大的子集。
解题思路:
当解决加权区间调度问题时,我们希望找到一组相容的区间,使得它们的总权重和最大。
动态规划是一种常用的解决最优化问题的方法。在解决加权区间调度问题时,我们可以利用动态规划的思想来逐步计算出最优解。
首先,我们将所有的区间按照结束时间从小到大进行排序。这是因为结束时间早的区间有更大的潜力可以和后面的区间相容。
接下来,我们定义一个dp数组,dp[i]表示以第i个区间为最后一个区间时的最大权重和。我们需要逐个计算dp数组的值。
对于每个区间i,我们需要考虑两种情况:
最后,我们将dp[i]更新为这两种情况中的较大值,即dp[i] = max(dp[i-1], dp[j]+区间i的权重)。
通过这样的状态转移,我们可以逐个计算dp数组的值。最终,dp数组的最后一个元素即为最大权重和。
总结一下动态规划解决加权区间调度的思路:
希望这样的解题思路能够帮助你更好地理解动态规划解决加权区间调度问题的思路。
python代码实现:
class Job:
def __init__(self, start, end, weight):
self.start = start
self.end = end
self.weight = weight
def weighted_interval_scheduling(jobs):
jobs.sort(key=lambda x: x.end) # 按照结束时间从小到大排序
n = len(jobs)
dp = [0] * n
dp[0] = jobs[0].weight # 初始化第一个作业的dp值为其权重
for i in range(1, n):
dp[i] = jobs[i].weight # 初始化dp[i]为作业i的权重
for j in range(i-1, -1, -1):
if jobs[j].end <= jobs[i].start:
dp[i] = max(dp[i], dp[j] + jobs[i].weight) # 更新dp[i]的值
return max(dp) # 返回最大权重和
# 示例输入
jobs = [
Job(1, 4, 3),
Job(3, 5, 2),
Job(0, 6, 4),
Job(4, 7, 1),
Job(3, 8, 2),
Job(5, 9, 6),
Job(6, 10, 4),
Job(8, 11, 2)
]
# 输出最大权重和
print(weighted_interval_scheduling(jobs))
问题描述:
最大子数组问题是指在一个数组中,找到一个连续子数组,使得该子数组的和最大。
思路分析:
下面是使用动态规划解决最大子数组问题的一般思路:
python代码实现:
def max_subarray(nums):
n = len(nums)
if n == 0:
return 0
max_sum = nums[0] # 初始化最大子数组和为第一个元素
curr_sum = nums[0] # 当前位置的最大子数组和
for i in range(1, n):
if curr_sum > 0:
curr_sum += nums[i]
else:
curr_sum = nums[i]
max_sum = max(max_sum, curr_sum)
return max_sum
# 用户输入和数据验证
while True:
try:
nums = [int(x) for x in input("请输入一个整数数组,数字之间用空格分隔:").split()]
break
except ValueError:
print("输入无效,请重新输入整数数组!")
# 输出最大子数组和
print(max_subarray(nums))
在这个题的基础上,引申出一个新的题目:
goal:given an n-by-n matrix,find a rectangle whose sum is maxinum
要使用动态规划求解给定的 n × n 矩阵中和最大的矩形,可以按照以下步骤进行:
python代码实现:
def max_rectangle_sum(matrix):
n = len(matrix)
if n == 0:
return 0
dp = [[0] * n for _ in range(n)] # 定义 dp 数组
# 初始化第一行和第一列
dp[0][0] = matrix[0][0]
for i in range(1, n):
dp[i][0] = dp[i-1][0] + matrix[i][0]
dp[0][i] = dp[0][i-1] + matrix[0][i]
# 计算 dp 数组
for i in range(1, n):
for j in range(1, n):
dp[i][j] = dp[i-1][j] + dp[i][j-1] - dp[i-1][j-1] + matrix[i][j]
max_sum = float('-inf') # 初始化最大矩形和为负无穷大
# 遍历 dp 数组,找到最大矩形和
for i in range(n):
for j in range(n):
for k in range(i, n):
for l in range(j, n):
curr_sum = dp[k][l]
if i > 0:
curr_sum -= dp[i-1][l]
if j > 0:
curr_sum -= dp[k][j-1]
if i > 0 and j > 0:
curr_sum += dp[i-1][j-1]
max_sum = max(max_sum, curr_sum)
return max_sum
# 示例输入
matrix = [
[1, 2, -1],
[-3, 0, 6],
[7, 8, -9]
]
# 输出最大矩形和
print(max_rectangle_sum(matrix))
问题描述:
背包问题是一个经典的组合优化问题,旨在从一组物品中选择适合放入背包以最大化总价值,同时要求背包的容量不超过给定的限制。
实现思路:
python代码实现:
def knapsack_dp(weight, value, capacity):
n = len(weight)
if n == 0 or capacity == 0:
return 0, []
# 将浮点数转换为整数,乘以一个大的倍数,以保留小数点后的精度
multiplier = 100
weight = [int(w * multiplier) for w in weight]
capacity = int(capacity * multiplier)
dp = [0] * (capacity + 1)
picks = []
for i in range(n):
if weight[i] <= capacity:
for j in range(capacity, weight[i] - 1, -1):
if dp[j] < dp[j - weight[i]] + value[i]:
dp[j] = dp[j - weight[i]] + value[i]
max_value = dp[capacity]
# 回溯找到放入背包的物品
j = capacity
for i in range(n - 1, -1, -1):
if j >= weight[i] and dp[j] == dp[j - weight[i]] + value[i]:
picks.append(i)
j -= weight[i]
picks.reverse()
return max_value, picks
# 输入数据
n = int(input("请输入物品数量:"))
weight = []
value = []
for i in range(n):
w = float(input(f"请输入第 {i+1} 个物品的重量:"))
v = float(input(f"请输入第 {i+1} 个物品的价值:"))
weight.append(w)
value.append(v)
capacity = float(input("请输入背包的容量:"))
# 数据合法性验证
if n <= 0 or capacity < 0:
print("输入的物品数量或背包容量不合法,请重新输入!")
else:
max_value, picks = knapsack_dp(weight, value, capacity)
print("最大价值为:", max_value)
print("放入背包的物品索引:", picks)
问题描述:
给定两个字符串,要求找到它们的最长公共子序列的长度。
解题思路:
s1
和 s2
。dp
,大小为 (len(s1)+1) × (len(s2)+1)
,用来保存最长公共子序列的长度。
dp[i][j]
表示字符串 s1
的前 i
个字符和字符串 s2
的前 j
个字符的最长公共子序列的长度。i=0
或 j=0
时,dp[i][j] = 0
,表示一个字符串为空时,最长公共子序列的长度为 0。i=1
和 j=1
开始,逐步计算 dp[i][j]
。s1[i-1]
等于 s2[j-1]
,则这两个字符可以加入最长公共子序列中,即 dp[i][j] = dp[i-1][j-1] + 1
。s1[i-1]
不等于 s2[j-1]
,则需要在 s1
的前 i-1
个字符和 s2
的前 j
个字符中找到最长公共子序列,或者在 s1
的前 i
个字符和 s2
的前 j-1
个字符中找到最长公共子序列,取两者中的较大值,即 dp[i][j] = max(dp[i-1][j], dp[i][j-1])
。dp[len(s1)][len(s2)]
即为最长公共子序列的长度。python代码:
def longest_common_subsequence(s1, s2):
m = len(s1)
n = len(s2)
# 创建一个二维数组来保存最长公共子序列的长度
dp = [[0] * (n + 1) for _ in range(m + 1)]
# 动态规划计算最长公共子序列的长度
for i in range(1, m + 1):
for j in range(1, n + 1):
if s1[i - 1] == s2[j - 1]:
dp[i][j] = dp[i - 1][j - 1] + 1
else:
dp[i][j] = max(dp[i - 1][j], dp[i][j - 1])
# 返回最长公共子序列的长度
return dp[m][n]
# 用户输入和数据验证
s1 = input("请输入第一个字符串:")
s2 = input("请输入第二个字符串:")
if not s1 or not s2:
print("输入字符串不能为空")
else:
lcs_length = longest_common_subsequence(s1, s2)
print("最长公共子序列的长度为:", lcs_length)