python_分治算法贪心算法动态规划算法

Posted hellobigorange

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了python_分治算法贪心算法动态规划算法相关的知识,希望对你有一定的参考价值。

一、分治算法思想

分治算法是一种化繁为简的算法思想。分治算法往往应用于计算步骤比较复杂的问题,通过将问题简化而逐步得到结果。

1、基本算法思想

分治算法的基本思想是将一个计算复杂的问题分为规模较小,计算简单的小问题求解,然后综合各个小问题,得到最终问题的答案。分治算法的执行过程如下:
(1)对于一个规模为N 的问题,若该问题可以容易地解决(比如说规模>^较小),则直接解决,否则执行下面的步骤。
(2)将该问题分解为” 个规模较小的子问题,这些子问题互相独立,并且原问题形式相同。
(3)递归地解子问题。
(4)然后,将各子问题的解合并得到原问题的解。

【注意】使用分治算法需要待求解问题能够化简为若干个小规模的相同问题,通过逐步划分,达到一个易于求解的阶段而直接进行求解。然后,程序中可以使用递归算法来进行求解。

2、寻找假币示例

【寻找假币问题】
一个袋子里有30个硬币,其中一枚是假币,并且假币和真币一模- 样,肉眼很难分辨,目前只知道假币比真币重量轻一点。请问如何区分出假币?


l_weight = [0.5] * 30  # 硬币重量列表
l_weight[2] = 1
l_idx = list(range(len(l_weight)))  # 硬币索引


def find_money(l_weight, l_idx):
    n = len(l_idx)
    if n == 1:
        return l_idx[0] + 1
    if n % 2 == 0:  # 钱币数为偶数
        idx_before = l_idx[0]
        idx_medium = l_idx[n // 2]
        idx_last = l_idx[-1] + 1
        if sum(l_weight[idx_before:idx_medium]) > sum(l_weight[idx_medium:idx_last]):  # ,分成两堆后,前一堆重量更沉
            return find_money(l_weight, list(range(idx_before, idx_medium)))
        else:  # 分成两堆后,后一堆重量更沉
            return find_money(l_weight, list(range(idx_medium, idx_last)))


    else:  # 钱币数为奇数
        idx_before = l_idx[0]
        idx_medium = l_idx[n // 2]
        idx_last = l_idx[-1]
        if sum(l_weight[idx_before:idx_medium]) == sum(l_weight[idx_medium:idx_last]):  # ,分成两堆后,前一堆重量更沉
            return idx_last + 1
        elif sum(l_weight[idx_before:idx_medium]) > sum(l_weight[idx_medium:idx_last]):
            return find_money(l_weight, list(range(idx_before, idx_medium)))
        else:  # ,分成两堆后,后一堆重量更沉
            return find_money(l_weight, list(range(idx_medium, idx_last)))


r = find_money(l_weight, l_idx)
print(r)

二、贪心算法

寻找最优解,最优选择。
贪心算法

1、思想

(1) 原理:在对问题求解时,总是做出在当前看来是最好的选择。也就是说,不从整体最优上加以考虑,他所做出的仅是在某种意义上的局部最优解。贪心算法不是对所有问题都能得到整体最优解,但对范围相当广泛的许多问题他能产生整体最优解或者是整体最优解的近似解。
(2)贪心算法则通常以自顶向下的方式进行,以迭代的方式作出相继的贪心选择,每作一次贪心选择就将所求问题简化为规模更小的子问题。

2、最大不相容活动安排示例

i = list(range(11))  # 活动名称
s = [1, 3, 0, 5, 3, 5, 6, 8, 8, 2, 12]  # 开始时间
f = [4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]  # 结束时间

# act = list(zip(i, s, f))
import pandas as pd
import numpy as np

act = pd.DataFrame(np.array([s, f, i]).T, columns=["startTime", "endTime", "name"])
act.sort_values(by=['endTime', 'startTime'], inplace=True, ignore_index=True)
t_end = act["endTime"][0]  # 结束世间最早的一场活动
l = [1]  # 最大不相容活动名称列表
while t_end <= act["endTime"].values[-1]:
    flag = 0  # 是否有下一场开始时间>=本场结束时间标志位。存在;flag=0
    for idx, t_start in enumerate(act["startTime"]):
        if t_start >= t_end:
            flag = 1
            l.append(act["name"][idx] + 1)
            t_end = act["endTime"][idx]
            act = act[idx:].reset_index(drop=True) # 缩小规模
            break
    if not flag:
        break
print(l)

三、动态规划

1、思想

动态规划思想(写的超级棒!)
那么遇到问题如何用动态规划去解决呢?根据上面的分析我们可以按照下面的步骤去考虑:

   1、构造问题所对应的过程。
   2、思考过程的最后一个步骤,看看有哪些选择情况。
   3、找到最后一步的子问题,确保符合“子问题重叠”,把子问题中不相同的地方设置为参数。
   4、使得子问题符合“最优子结构”。
   5、找到边界,考虑边界的各种处理方式。
   6、确保满足“子问题独立”,一般而言,如果我们是在多个子问题中选择一个作为实施方案,而不会同时实施多个方案,那么子问题就是独立的。
   7、考虑如何做备忘录。
   8、分析所需时间是否满足要求。
   9、写出转移方程式。

思考:感觉像是从一个总问题,进入二叉树分类,知道不满足约束条件,或者到达根节点,每次分叉的目的都是为了寻找收益最大的点

2、最大和路径

a = [[7], [3, 8],[9,1,1]]
'''
约束:
a[i][j]-->a[i+1][j],a[i+1,j+1]
i,j<=len(a)-1;

子结构:
f(i,j)   # f(i,j)表示当在i行,j列的时,可获取的最大总收益
if i == len(a)-1  return f(i,j) = a[i][j]
else: f(i,j)=max(f(i+1,j+1),f(i+1,j))+a[i][j]
'''


def f(i, j):
    while i <= len(a) - 1:
        if i == len(a) - 1:
            return a[i][j] #
        else:
            return max(f(i + 1, j + 1), f(i + 1, j))+a[i][j] # 之后的最大收益+当前位置的收益


result = f(0,0)

3、背包问题

m = [2, 2, 6, 5, 4]
price = [3, 6, 5, 4, 6]
weight = 10
"""
背包问题:
子问题,每个物品都有两种选择,装背包或不装背包,装背包后对总结构的影响是收益和背包空间
限制条件:
Sigma(m_i)<=weight,i<=len(m)-1
优化目标:
Max(Sigma(price_i)),i<=len(m)-1
子结构:
f(i,left_weight)在剩余物品)0,1,2,...i和剩余质量为left_weight时的最大总收益
if i==0: # 剩最后一个

else:    if left_weight<m[i]: f(i,left_weight) = price[i]
    else: f(i,left_weight) = 0
    # 把第i个装包收益为f(i-1,left_weight-m[i])+price[i],或者不装包收益为f(i-1,left_weight)
    f(i,left_weight) = Max(f(i-1,left_weight),f(i-1,left_weight-m[i])+price[i])

"""


# 先考虑最后一个是否装包,最后考虑第一个是否装包
def f(i, left_weight):
    while i <= len(price) - 1:
        if i == 0:  # 剩最后一个
            if left_weight >= m[i]:  # 背包剩余重量是否允许将i(最后一个)装包
                return price[i]
            else:
                return 0
        # 考虑背包剩余重量是否允许将i装包
        # 把第i个装包收益为f(i-1,left_weight-m[i])+price[i],或者不装包收益为f(i-1,left_weight)
        else:
            if left_weight < m[i]:
                return 0
            else:
                return max(f(i - 1, left_weight), f(i - 1, left_weight - m[i]) + price[i])


result = f(len(m) - 1, weight)

4、动态规划和贪心算法的区别

详细区别

  • 动态规划自底向上,父问题的解依赖于子问题的最优解,解出子问题再逐层向上递归。

  • 贪心算法自顶向下,每一次都考虑当前最优解。

  • 贪心:每一步的最优解一定包含上一步的最优解,上一步之前的最优解则不作保留。动态规划:全局最优解中一定包含某个局部最优解,但不一定包含前一个局部最优解,因此需要记录之前的所有的局部最优解

  • 贪心:如果把所有的子问题看成一棵树的话,贪心从根出发,每次向下遍历最优子树即可(通常这个“最优”都是基于当前情况下显而易见的“最优”);这样的话,就不需要知道一个节点的所有子树情况,于是构不成一棵完整的树。动态规划:动态规划则自底向上,从叶子向根,构造子问题的解,对每一个子树的根,求出下面每一个叶子的值,最后得到一棵完整的树,并且最终选择其中的最优值作为自身的值,得到答案

  • 根据以上两条可以知道,贪心不能保证求得的最后解是最佳的,一般复杂度低;而动态规划本质是穷举法,可以保证结果是最佳的,复杂度高。

  • 针对0-1背包问题:这个问题应比较选择该物品和不选择该物品所导致的最终方案,然后再作出最好选择,由此就导出许多互相重叠的子问题,所以用动态规划。

以上是关于python_分治算法贪心算法动态规划算法的主要内容,如果未能解决你的问题,请参考以下文章

算法分治思想动态规划回溯贪心算法

算法导论—分治法思想动态规划思想贪心思想

算法复习分治算法动态规划贪心算法

五大常用算法:分治动态规划贪心回溯和分支界定

分治、贪心五大算法

五大算法:分治,贪心,动态规划,回溯,分支界定