贪心方法 greedy method:
逐步构造最有解(其实是近似最优的解)。每一步,我们都在一定的标准(这个标准叫贪婪准则 greedy criterion)下作出一个最优决策。
最优化问题 optimization problem:包含一组限制条件(constraint)和一个优化函数(optimization function)。符合限制条件的问题求解方案称为可行解(feasible solution)。使优化函数可能取得最佳值的可行解称为最优解(optimal solution)。
1.货箱装载问题
有一艘大船要装载货物。货物要装载货箱中,所有货箱的大小都一样,但货箱的重量各不相同。设第i个货箱的重量为Wi,(1 <= i <= n), 货船的最大载重量为c。我们的目的是在货船上装入最多的货物。
note:这个问题可以精确地描述为最优化问题:设存在一组变量xi (表示 i 号箱装船与否),xi的值是0或1。如 xi 为 0,则货箱 i 不装船; 如 xi 为 1 ,则货箱 i 装船。我们要找到一组 xi,使它满足限制条件ΣWixi<=c. 优化函数是 Σxi。每一组满足限制条件的xi都是一个可行解。使得Σxi最大的可行解就是最优解。
贪婪求解:把货箱分步装载到货船上,一步装载一个货箱。每一步决定装载哪一个货箱。做决定所依据的贪婪准则是:从剩下的货箱中,选择重量最小的货箱。这样可以保证所选的货箱总重量最小,从而使得货船改用最大的容量来装载更多的货箱。根据这种贪婪策略,首先选择最轻的货箱,然后选择次轻的货箱,如此下去,直到所有货箱均装上床,或船上不能再容纳一个货箱的空间。
我们最终的程序流程是:重量从小到大排序,然后挨个往船上装。——好朴素啊。
void continerLoading(container* c, int capacity, int numberOfContainers, int* x)
{//货箱装载的贪婪方法
//令 x[i] = 1, 当且仅当货箱 i (i >= 1)已装载
//按照重量递增排列 O(nlogn)
heapSort(c, numberOfContainers);
int n = numberOfContainers;
//初始化x
for(int i = 1; i <= n; i++)
x[i] = 0;
//按照重量顺序选择货箱
for(int i = 1; i <= n && c[i].weight <= capacity; i++)
{//对货箱c[i].id有足够的容量 id表示货箱的范围,从1到货箱数量。
x[c[i].id] = 1;
capacity -= c[i].weight; //剩余容量
}
}
程序首先使用堆排序法按照重量对货箱排序。然后按照重量递增顺序,把货箱装上船。排序O(nlogn), 算法其他部分用时O(n).所以程序总的复杂性是O(nlogn)。
2.单源最短路径
下面这个blog有实例讲解。
http://blog.csdn.net/mu399/article/details/50903876
贪婪求解——Dijkstra贪婪算法
这是目前公认的求解最短路径最好的算法。但Dijkstra算法主要计算了到其他所有点的最短路径(很早如果就找到自己需要的,那么提前终止),效率较低。
分步生成最短路径。每一步产生一个到达新的目的顶点的最短路径。每一条最短路径的目的顶点的选择方法如下greedy criterion:从一条最短路径还没有到达的顶点中,选择一个可以产生最短路径的目的顶点。也就是说,Dijkstra方法按照路径长度的递增顺序产生最短路径。
在算法的每一步,产生下一条最短路径,这条路径是在一条已经产生了的最短路径上加一条可行的边而得到的。
一种简便的方法来存储最短路径:使用数组p[ ]. 令p[i] (即predesessor) 是从源顶点到达顶点i的路径中顶点i前面的那个顶点。
为了便于按照长度递增顺序产生最短路径,我们定义d[i] 是在已生成的最短路径上扩展一条最短边从而到达顶点i时这条最短边的长度(即distanceFromSource)。
简单说,p[i]存的是前一个顶点,d[i]存的是前一条边权。
Dijkstra最短路径算法的简化:
step1:初始化 d[i] = a[s][i],1 <= i <= n;
对每一个邻接于s的顶点i,令p[i] = s;
对所有顶点,初始化 p[i] = 0;
创建一个表 L 存储所有 p[i] != 0的顶点 i;
step2:如果 L 为空,结束。否则,转到step3;
step3:从 L 中删除 d 值最小的顶点 i;
step4:对于所有邻接于 i 的顶点 j,更新 d[j]=min{ d[j], d[i]+a[i][j] }.
如果d[j]改变,令p[j] = i 并且,如果j不在 L 中,将j加进去。
转到step2;
求解最短路径的程序:
template
void AdjacencyWDigraph::ShortestaPaths(int s, T d[], int p[])
{//Shortest pats from vertex s, return shortest distances in d and predecessor info in p.
if (s < 1 || s > n) throw OutOfBounds();
Chain L; //List of reachable vertices for which paths have yet to be found
ChainIterator I;
//initialize d, p, and L
for (int i = 1; i <= n; i++){
d[i] = a[s][i];
if (d[i] == NoEdge) p[i] = 0;
else {p[i] = s;
L.Insert(0,1);}
}
//update d and p
while (!L.IsEmpty()){// more paths exist
//find vertex *v in L with least d
int *v = I.Initialize(L);
int *w = I.Next();
while(w){
if(d[*w] < d[*v]) v = w;
w = I.Next();
}
//next shortest path is to vertex *v
//delete from L and update d
int i =*v;
L.Delete(*v);
for (int j = 1; j <= n; j++){
if(a[i][j] != NoEdge && (!p[j] || d[j] > d[i] + a[i][j]))
{//d[j] decreases
d[j] = d[i] + a[i][j];
//add j to L if not already in L
if (!p[j]) L.Insert(0,j);
p[j] = i;
}
}
}
}
3.最小成本生成树
二种不同的贪婪方法:Kruskal,Prim
1)Kruskal
算法思想:
分步骤选择n-1条边,每一步选择一条边,一句的 greedy criterion是:从剩下的边中选择一条成本最小且不会产生环路的边加入已选择的边集。
Kruskal算法分e步,其中e是网络中边的数目。它按成本递增顺序考察e条边,每步考察一条边时,如果这条边加入已选的边集中会产生环路,则将其抛弃,否则,将它加入已选入的边集中。
伪代码:
//Find a minimum-cost spanning tree in an n-vertex network
Let T be the set of selected edges. Initialize T=空集;
Let E be the set of network edges.
while(E!=空集)&&(|T|!=n-1){
Let (u,v) be a least cost edges in E.
E = E-{(u,v)}.//delete edge from E
if((u,v)does not create a cycle in T)
Add edge(u,v)to T.
}
if(|T|==n-1) T is a minimum-cost spanning tree.
else The network is not connected and has no spanning tree.
代码:
template
bool UNetwork ::Kruskal ( EdgeNode) t[ ] )
{//Find a min cost spanning tree using Kruskal's method.
//Return false if not connected. If connected,return min spanning tree in t[0:n-2].
int n = Vertices();
int e = Edges();
//set up array of network edges
InitializePos(); // graph iterator
EdgeNode *E = new EdgeNode [e+1];
int k = 0; //cursor for E.
for (int i = 1; i <= n; i++){
//get all edges incident to i
int j;
T c;
First(i, j, c);
while(j){ //j is adjacent from i
if(i < j){ //add edge to E
E[++k].weight = c;
E[k].u = i;
E[k].v = j;
}
Next(i,j,c);
}
}
//put edges in min heap
MinHeap >H(1);
H.Initialize(E,e,e);
UnionFind U(n);// union/find structure
//extract edges in cost order and select/reject
k = 0;// use as cursor for t now.
while(e && k < n-1){
//spanning tree not complete & edges remain
EdgeNode x;
H.DeleteMin(x); //min cost edge
e--;
int a = U.Find(x.u);
int b = U.Find(x.v);
if(a != b){//select edge
t[k++] = x;
U.Union(a,b);
}
}
DeactivatePos();
H.Deactivate();
return (k == n - 1);
}
2)Prim 算法
算法思想:也是通过分步选边来创建最小生成树,一步选择一条边。每一步的greedy criterion :从剩余的边中,选择一条成本最小的边,并且把它加入已选的边集中形成一棵树。
note:Prim每一步所得的入选边集都形成一棵树,而Kruskal每一步所得入选边集则是一个森林。
伪代码:
//Assume that the network has at least one vertex.
Let T be the set of selected edges.Initialize T =空集
Let TV be the set of vertices already in the tree. Set TV = {1}
Let E be the set of network edges.
while(E<>空集)&&(|T|<>n-1){
Let (u,v) be a least-cost edge such that u 属于 TV and v 不属于 TV //引入割的概念
if(there is no such edge) break.
E=E-{(u,v)}.//delete edge from E
Add edge(u,v) to T.
}
if (|T| == n-1) T is a minimum-cost spanning tree.
else The network is not connected and has no spanning tree.
4.拓扑排序, topological sorting
i)问题描述
装配一辆汽车,任务之间有先后关系,这组任务和任务的先后顺序可用有向图表示出来,称为AOV(activity on vertex)网络.顶点代表任务,有向边表示先后关系。
对任务有向图的任意一条边(i,j),在这个序列中,任务i一定出现在任务j的前面。具有这种性质的序列称为拓扑序列(topological sequence)。
拓扑排序是指根据有向图建立拓扑序列的过程。
ii)贪婪方法
贪婪方法按照从左到右分步构造拓扑序列。每一步选择一个新顶点加入序列。选择新顶点的依据是贪婪准则:从剩余的顶点中选择一个顶点w,它没有这样的入边(v,w),其中顶点v不在序列中。即如果w有入边,那么这个边一定是从序列里连出去的。
如果选择了一个顶点w,不满足上述贪婪准则,即边(v,w)中的顶点v不在序列中,那么就构造不出拓扑序列。
伪代码:
Let n be the number of vertices in the digraph.
Let V be an empty sequence
while(true){
Let w be any vertex that has no incoming edge (v,w) such that v is not in V.
if there is no such w,break.
Add w to the end of V.
}
if(V has fewer than n vertices) the algorithm fails.
else V is a topological sequence.