python实现贪心算法


一、开发环境

开发工具:jupyter notebook 并使用vscode,cmd命令行工具协助编程测试算法,并使用codeblocks辅助编写C++程序
编程语言:python3.6

二、实验目标

1. 熟悉贪心算法实现的基本方法和步骤;

2. 学会贪心算法的实现方法和分析方法;

三、实验内容

1. Huffman编码:

测试数据:

X={1310773597218806522025}

将字符串构建哈夫曼树,树的节点名称为字符,节点的权重为字符出现的次数,以此来构建哈夫曼树,最终形成编码并输出结果。

Python贪心算法 汽车加油 python贪心算法活动安排_算法

这里用了两种方式实现哈夫曼树的构建:

第一种构建的时候每次循环都采用sort的方式,相对于采用堆的数据结构,时间复杂度明显增加,第一种方式的代码如下所示:

from heapq import *
import numpy as np
import pandas as pd

X="1310773597218806522025"

inp = input("请输入要构建哈夫曼树的字符串")
# 统计每个自字符出现的频率 并生成字典
def generate_dict(s):
    dic = {}
    
    for i in s:
        if i not in dic:
            dic[i] = 1
        else:
            dic[i] += 1
            
    return dic

dic = generate_dict(inp)

#节点类
class Node(object):
    def __init__(self,name=None,weight=None):
        self.name=name
        self.weight=weight
        self.parent=None
        self.left=None
        self.right=None
        self.id=None
        
    # 自定义类的比较
    def __lt__(self,other):
        return int(self.weight) < int(other.weight)
        
#按权值排序
def sort(list):
    return sorted(list,key=lambda Node:Node.weight)

# 用带权值的字典 生成带权值的结点列表1
def generate_node(dic):
    lis = []
    
    for i in dic:
        newNode = Node(i, dic[i])
        lis.append(newNode)
        
return lis

lis = generate_node(dic)

# Huffman编码1 每次循环下来排个序
def HuffmanTree(lis):
    while(len(lis)!=1):
        a,b=lis[0],lis[1]
        
        new=Node()
        new.weight=a.weight+b.weight
        new.left,new.right=a,b
        
        a.parent = new
        b.parent = new
        
        lis.remove(a)
        lis.remove(b)
        lis.append(new)
        lis=sort(lis)
    return lis

lis = HuffmanTree(lis)

node = lis[0] # 获取根结点

# 前序遍历方法 并执行一定的操作
def pre_order(root, code):
    if root is None:
        code = code[:-1]
        return
    
    pre_order(root.left, code+"0")
    
    if root.name is not None:
        print(root.name,"的权重为",root.weight,"编码为",code)
    
    pre_order(root.right, code+"1")
    

code = ""
# print(res)
print("构建的哈夫曼树为:")
pre_order(node, code)

写方式二的时候,采用python自带的heapq库,将列表作为一个堆来进行,无论是代码上还是时间复杂度上,都很有优势,第二种方式代码如下:

from heapq import *
import numpy as np
import pandas as pd

X="1310773597218806522025"

inp = input("请输入要构建哈夫曼树的字符串")
# 统计每个自字符出现的频率 并生成字典
def generate_dict(s):
    dic = {}
    
    for i in s:
        if i not in dic:
            dic[i] = 1
        else:
            dic[i] += 1
            
    return dic

dic = generate_dict(inp)

#节点类
class Node(object):
    def __init__(self,name=None,weight=None):
        self.name=name
        self.weight=weight
        self.parent=None
        self.left=None
        self.right=None
        self.id=None
        
    # 自定义类的比较
    def __lt__(self,other):
        return int(self.weight) < int(other.weight)
        
#按权值排序
def sort(list):
    return sorted(list,key=lambda Node:Node.weight)

def generate_node2(dic):
    lis = []

    for i in dic:
        newNode = Node(i, dic[i])
        heappush(lis, newNode)
        
    return lis
        

# lis = generate_node(dic)  
lis = generate_node2(dic)

# Huffman编码2 使用堆的方式
def HuffmanTree2(lis):
    global id
    while(len(lis)!=1):
        a = heappop(lis)
        b = heappop(lis)
        new=Node()
        new.weight=a.weight+b.weight
        new.left,new.right=a,b
        
        a.parent = new
        b.parent = new
        
        heappush(lis, new)
    return lis

lis = HuffmanTree2(lis)

node = lis[0] # 获取根结点

# 前序遍历方法 并执行一定的操作
def pre_order(root, code):
    if root is None:
        code = code[:-1]
        return
    
    pre_order(root.left, code+"0")
    
    if root.name is not None:
        print(root.name,"的权重为",root.weight,"编码为",code)
    
    pre_order(root.right, code+"1")
    

code = ""
# print(res)
print("构建的哈夫曼树为:")
pre_order(node, code)

经过测试,两种方式的结果是一模一样的

输入字符串:1310773597218806522025

结果截图如下:

Python贪心算法 汽车加油 python贪心算法活动安排_结点_02

观察这张图,发现权重高的编码短,权重小的编码长,从逻辑上符合哈夫曼树的特点

2.

最小生成树:

最小生成树所用的测试数据为:

Python贪心算法 汽车加油 python贪心算法活动安排_Python贪心算法 汽车加油_03

Prim算法

Prim算是一种求最小生成树的方法,下面用自己的语言来描述一下prim算法的过程,首先随机选取一个节点,然后取节点权重最小的一条边,把边连接的结点也考虑进去,再次选择所有边中权重最小的边,直到选取了所有的结点。

代码如下:

# prim算法
from heapq import heappush, heappop
import math

data = [[0, 2, 8, 1, 0, 0, 0, 0], 
  [2, 0, 6, 0, 1, 0, 0, 0], 
  [8, 6, 0, 7, 5, 1, 2, 0], 
  [1, 0, 7, 0, 0, 0, 9, 0], 
  [0, 1, 5, 0, 0, 3, 0, 8], 
  [0, 0, 1, 0, 3, 0, 4, 6], 
  [0, 0, 2, 9, 0, 4, 0, 3], 
  [0, 0, 0, 0, 8, 6, 3, 0]]

# 转换数据格式
def build_graph(data):
    G = {}
    for i in range(len(data)):
        G[str(i)] = {}
        for j in range(len(data[i])):
            if data[i][j]!=0:
                G[str(i)][str(j)] =  data[i][j]
    return G

G = build_graph(data)
# print(G)

def prim(G, source):
    P = {}    # parent字典
    Q = [(0, None, source)]    # 优先队列
    
    while Q:
        _, p, u = heappop(Q)    # 将权值最小的元素弹出
        if u in P:    # 如果节点u在P里面的话
            continue
        P[u] = p   # 节点u链接节点p
        for v,w in G[u].items():    # 将所有现有邻接线的权重添加入优先队列中
            heappush(Q, (w, u, v)) # 权重, 先节点, 节点
    return P

subtree = prim(G, "0")

print(subtree)

 

 

将最终生成的字典打印出来:

Python贪心算法 汽车加油 python贪心算法活动安排_结点_04

Kruskal算法:

Kruskal算法也是一种求最小生成树的算法, 就是计算方式与prim算法不同,同样用自己的语言描述一下:

遍历全图,找到权重最小的边,加入最小生成树,再次遍历,如果出现了环,则取下一小权重的边加入,重复上述步骤,直到所有的节点都加入,则完成。

Kruskal算法的代码为:

# kruskal算法

# 返回U中包含定点v的连通分支的名字。这个运算用来确定某条边的两个端点所属的连通分支
def find(C, u):
    if C[u] != u:
        C[u] = find(C, C[u])
    return C[u]

# 将C,R两个连通分支连接起来
def union(C, R, u ,v):
    u, v = find(C, u), find(C, v)
    if R[u] > R[v]:
        C[v] = u
    else:
        C[u] = v
    if R[u] == R[v]:
        R[v] += 1
        

def kruskal(G):
    E = [(G[u][v], u, v) for u in G for v in G[u]]
    print(E)
    T = set()
    C, R = {u: u for u in G}, {u: 0 for u in G}
    print(C, R)
    for _, u, v in sorted(E):
        print(_, u, v)
        if find(C, u) != find(C, v):
            T.add((u, v))
            union(C, R, u, v)
    return T

print(list(kruskal(G)))

最终得到的结果与用prim算法求得解是一模一样的。