python实现贪心算法
一、开发环境
开发工具:jupyter notebook 并使用vscode,cmd命令行工具协助编程测试算法,并使用codeblocks辅助编写C++程序
编程语言:python3.6
二、实验目标
1. 熟悉贪心算法实现的基本方法和步骤;
2. 学会贪心算法的实现方法和分析方法;
三、实验内容
1. Huffman编码:
测试数据:
X={1310773597218806522025}
将字符串构建哈夫曼树,树的节点名称为字符,节点的权重为字符出现的次数,以此来构建哈夫曼树,最终形成编码并输出结果。
这里用了两种方式实现哈夫曼树的构建:
第一种构建的时候每次循环都采用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
结果截图如下:
观察这张图,发现权重高的编码短,权重小的编码长,从逻辑上符合哈夫曼树的特点
2.
最小生成树:
最小生成树所用的测试数据为:
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)
将最终生成的字典打印出来:
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算法求得解是一模一样的。