by Richard Johnsonbaugh Marcus Schaefer
贪心是指在所有可选的部分解中,被选中的是在当下看来最有效的那一个。其结果不一定是最优解。
7.1 硬币兑换
本算法使用面额为denom[1]>denom[2]>...>denom[n]=1的硬币来构成总金额为A的兑换。
Input Parameters: denom,A
Output Parameter: None
greedy_coin_change(denom,A){
i=1
while(A>0){
c=A/denom[i]
println("使用"+c+"个面额"+denom[i]+"的硬币")
A=A-c*denom[i]
i=i+1
}
}
7.2 Kruskal算法
生成树:是G的一个子图,包含了G的全部顶点。
最小生成树:有最小权重的一棵生成树。
为了实现Kruskal算法:
1.必须表示这个图:把图表示成边及其权重的一个列表。
2.根据权重以非降序把边排序,然后以排序次序对它们检查。
3.能确定添加一条边是否会产生环路。
Krustal算法:有顶点集{1,...,n}的连通、加权图中寻找一颗最小生成树。算法的输入是一个边的数组edgelist和n。边的成员有:
* 边所关联的顶点v和w
* 边的权重weight。
输出列出最小生成树中的边。函数sort根据权重weight将数组edgelist按非降序排序。【edgelist:|v|w|weight】
makeset可以初始化每个顶点为它的自身部件。findset(v)==findset(w)可以用来测试顶点v和w是否属于同一个部件。union(v,w)可以用来合并顶点v和w所属的部件。
Input Parameters: edgelist,n
Output Parameter: None
Krustal(edgelist,n){
sort(edgelist)
for i=1 to n
makeset(i)
count=0
i=1
while(count<n-1){
if(findset(edgelist[i].v)!=findset(edgelist[i]).w){
println(edgelist[i].v+" "+edgelist[i].w)
count=count+1
union(edgelist[i].v,edgelist[i].w)
}
i=i+1
}
7.3 Prim算法该算法是在连通、加权图中寻找最小生成树的另一种贪心算法。与Kurstal区别:Krustal中的部分解不一定是连通的,Prim的部分解是一棵树。 从一个起始顶点开始,没有边,使用贪心规则:添加一条最小权重边,它的一个顶点在当前树中,而另一个顶点不在。 用列表h记录不在树中的顶点v和从v到书中一点顶点边的最小权重。再用一个数组parent,它记录那些变给予最小权重。如果(v,w)是一条最小权重的边,其中v不在树中,而w在树中,那么parent[v]=w.
h | 顶点(v) 从v到树的最小权重 |parent[v] 1 3 | 5 2 无穷 | - 3 6 | 5 4 无穷 | - 6 2 | 5 本算法在一个n顶点的连通加权图中寻找一颗最小生成树。图用邻接表表示;adj[i]是对表示与顶点i邻接的顶点的节点邻接表中第一个节点的引用。每个节点有下列成员: 与i相邻的顶点ver、边(i,ver)的权重weight,以及对邻接表下一个节点的引用next,对邻接表最后一个节点而言,该值为null。起始顶点是start。在最小生成树中,i≠start的顶点的父母是parent[i],且parent[start]=0。 h.init(key,n) key是一个规模为n的数组,该表达式将h初始化为key中的值 h.del()删除h中有最小权重的项,并回送相应的顶点 h.isin(w)若顶点w在h中,则回送真,否则回送假 h.keyval(w)回送相应于顶点w的权重 h.decrease(w,wgt)将相应于顶点w的权重改变成wgt(一个更小的值)
Input Parameters: adj,start
Output Parameter: parent
prim(adj,start,parent){
n=adj.last
//key是一个本地数组
for i= 1 to n
key[i]=无穷
key[start]=0
parent[start]=0
//下面这条语句将容器h初始化为数组key中的值
h.init(key,n)
for i=1to n
v=h.del()
ref=adj[v]
while(ref!=null){
w=ref.ver
if(h.isin(w)&&ref.weight<h.keyval(w)){
parent[w]=v
h.decrease(w,ref.weight)
}
ref=ref.next
}
}
7.4 Dijkstra算法
求图中某一点到一个指定点的最短路径。
它与Prim算法的运作很像,五一的不同在于与顶点关联的值。在Prim算法中,与顶点v关联的值是从v到当前树的一条边的最小权重。而在Dijkstra算法中,与顶点v关联的值是有形式为length(w)+权重(v,w)的和中的最小值。其中w标示已经找到的一条最短路径的结尾.[在这个情况中,length(w)是从指定顶点到w的一条最短路径长度。]
图用邻接表标示。adj[i]是对表示与顶点i邻接的顶点的节点邻接表中第一个节点的引用。每个节点的成员包括与i相邻的顶点ver、边(i,ver)的权重weight,以及对链接表中下一个节点的引用next,最后一个节点其值为null。在一条最短路径中,顶点i≠start的紧前顶点是predecessor[i],而predecessor[start]=0.
Input Parameters: adj,start
Output Parameter: predecessor
dijkstra(adj,start,predecessor){
//key是一个本地数组
n = adj.last
for i = 1 to n
key[i]=无穷
key[start]=0
predecessort[start]=0
//下面的语句将容器h初始化为数组key中的值
h.init(key,n)
for i= 1 to n{
v=h.min_weight_index()
min_cost=h.keyval(v)
v=h.del
ref=adj[v]
while(ref!=null){
w=ref.ver
if(h.isin(w)&&min_cost+ref.weight<h.keyval(w)){
predecessor[w]=v
h.decrease(w,min_cost+ref.weight)
}//if 结束
ref=ref.next
}//while结束
}//for结束
}
7.5 霍夫曼编码
用可变长度的位串来表示字符。常使用的为短位串,不常使用的使用长位串。
前置代码:表示一个字符的位串不会是另一个字符的位串中的起首段。
霍夫曼编码树节点数据结构:left|character|key|right
如果a是一个数组,h.init(a)将容器h初始化为a中的数据。h.del()删除h中有最小键值的node,并回送node。h.insert(ref)将由ref引用的node插入h。
Input Parameters: a
Output Parameter: None
huffman(a){
h,init(a)
for i=1 to a.last-1{
ref= new node
ref.left=h.del()
ref.right=h.del()
ref.key=ref.left.key+ref.right.key
h.insert(ref)
}
return h.del()
}
7.6 连续背包问题
背包问题:给定n个物件和一个容重量为C的背包,其中物件i的重量wi,受益价值pi,要在满足条件下,寻找xi的值,使得到的总收益价值Σi=1~n xiwi<=C。保证了撞见的物件不会超过背包的容量。
背包问题可分为两种,(1)0/1背包问题,认为xi不是0就是1;(2)连续背包问题,物件被认为是任意可分的。
贪心规则以 价值/重量 的值来非增序选择物件。只要不超过背包的容量,就选取物件的全部。如果背包的容量不够放,就只限选取物件的某个部分将背包充满,然后停止。