Python_算法实现_(7)贪婪算法

1.NP完全问题

NP完全问题(Non-deterministic Polynomial complete problem)是没有快速算法的问题,其时间复杂度为O(n!)。通常没有完全判定问题是不是NP完全问题,但有一些经验能够帮助判断

  • 元素较少时算法的运行速度非常快,随着元素的增加速度会变得非常慢
  • 涉及“所有组合”问题通常是NP完全问题
  • 不能将问题分为小问题,需要考虑各种情况,这类问题可能是NP完全问题
  • 如果涉及序列问题(旅行商问题),可能是NP完全问题
  • 如果设计集合问题(集合覆盖问题),可能是NP完全问题
  • 如果问题可以转化为集合覆盖问题和旅行商问题,则一定是NP完全问题

2.贪婪算法

贪婪算法是一种不追求最优解,只希望得到较为满意解的方法。贪婪法一般可以快速得到满意的解,因为它省去了为找最优解要穷尽所有可能而必须耗费的大量时间。贪婪法常以当前情况为基础作最优选择,而不考虑各种可能的整体情况,所以贪婪法不要回溯。

贪婪算法是每一步都寻找最优解的做法,并非在任何情况下都可行,但易于实现且时间复杂度可以降为O(n^2)

3.集合覆盖问题代码实现

实例:现有集合A、B、C、D、E、F,每个集合中包含一定的数字,具体内容如下表,先找出最少集合组合,能够覆盖1-10全部的数字。

  • A = {1, 2, 3}
  • B = {7, 8, 9, 10}
  • C = {4, 5}
  • D = {2, 4, 5, 7}
  • E = {3, 6, 8, 10}
  • F = {3, 6}

根据IPO思想,实现该代码的主要分为一下三个部分

  • 生成集合
  • 遍历查找,选择去重后能够加入数字最多的集合,直至包含所有需要的数字
  • 输出选中的集合

代码如下:

number = {}
number['A'] = {1, 2, 3}
number['B'] = {7, 8, 9, 10}
number['C'] = {4, 5}
number['D'] = {2, 4, 5, 7}
number['E'] = {3, 6, 8, 10}
number['F'] = {3, 6}

number_needed = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
number_covered = set()
final_set = set()

while number_needed:
    best_set = None     # 临时存储当前最优集合及加入最优集合后的长度
    best_covered = set()
    
    for set_name,set_number in number.items():
        covered = number_covered.union(set(set_number)) # 覆盖的集合为已覆盖和临时选择的并集
        if len(covered) > len(best_covered): # 当此时集合长度更大时,更换当前最优集合
            best_set = set_name
            best_covered = covered

    # 经处理后best_set中保存这当前情况下的最优决策
    # 将最优决策加入最终集合中,
    number_needed = number_needed.difference(best_covered)
    number_covered = best_covered
    final_set.add(best_set)

print(final_set)

输出结果为:{'A', 'E', 'B', 'C'}

4.旅行商问题代码实现

旅行商问题是寻找遍历所有点且路径权值和最短的路径的算法,基于传统的循环遍历查找起来其运行时间为O(n!)。使用贪心算法处理旅行商问题的原理是:

  • 寻找所有两点连线路径中权值最小的路径
  • 找到两点的邻居
  • 寻找两点和邻居路径中权值最小的路径,重复

以如下例子来实现旅行商问题的贪心算法
题目:有五个城市,其距离有以下矩阵

[ [0, 14, 10, 27, 31,], 
  [14, 0, 11, 17, 28]
  [10, 11, 0, 19, "inf"]
  [27, 17, 19, 0, 7]
  [31, 28, "inf", 7, 0]]

实现代码如下:

# 输入路径权值矩阵,为无向图因此对称
graphs = [[0, 14, 10, 27, 31,], 
          [14, 0, 11, 17, 28], 
          [10, 11, 0, 19, "inf"], 
          [27, 17, 19, 0, 7], 
          [31, 28, "inf", 7, 0]]

INF = float("inf")

# 遍历进行路径修改,将0和"inf"都修改为INF
for start in range(5):
    for end in range(5):
        if graphs[start][end] == 0 or graphs[start][end] == "inf":
            graphs[start][end] = INF

path = []       # 建立存储路径的列表,当列表中存储少于4对路径时,继续执行
start = 0    # 无向图寻找最短路径为一条直线,用statr和end记录直线的两端方便后面将邻居存储为need_reserach
end = 0
need_research = graphs
path_value = 0
while len(path) < 4:
    short_path_value = INF      # 建立最短路径权值用来存储
    short_path = []

    print(need_research)
    
    for i in range(len(need_research)):
        for j in range(5):
            if need_research[i][j] < short_path_value:
                short_path_value = need_research[i][j]
                real_line = i + start
                short_path = [real_line, j]

    graphs[short_path[0]][short_path[1]] = INF   # 将走过的路径权值标记为无穷大
    graphs[short_path[1]][short_path[0]] = INF

    path.append(short_path)   # 将生成的最短路径加入path中
    path_value += short_path_value

    print(short_path)
    
    if len(path) == 1:    # 如果时第一次生成路径则直接作为路径的起点与重点
        start = short_path[0]
        end = short_path[1]
    else:                 # 否则分别检查起点或终点哪个在新生成的路径中
        if start in short_path:  # 如果起点在新生成的路径中,则将起点替换为其指向的邻居作为新的起点
            index = short_path.index(start)
            start = short_path[1-index]
        else:
            index = short_path.index(end)
            end = short_path[1-index]
            
    need_research = [graphs[start], graphs[end]]   # 以start和end的邻居作为新的research对象

print(path)

输出结果为:[[3, 4], [3, 1], [1, 2], [2, 0]]
因此运动路径为:A -> C -> B -> D -> E

你可能感兴趣的:(Python算法)