赞
踩
问题建模与描述:
我们先来看一个简单的例子:调度问题,有n项任务,每项任务的加工时间已知,从0时刻开始陆续安排到一台机器上加工,每个任务的完成时间是从0时刻到任务加工截止的时间。求:总完成时间(所有任务完成时间之和)最短的安排方案。
实例:任务集S={1,2,3,4,5},加工时间t1=3,t2=8,t3=5,t4=10,t5=15
算法:按加工时间将上述任务从小到大排序,
解:1->3->2->4->5
总完成时间=3+(3+5)+ (3+5+8) + (3+5+8+10)+ + (3+5+8+10+15)=94
问题建模:
输入:任务集: S={1,2,...,n },第j项任务加工时间:tj∈Z,j=1,2,...,n;
输出:调度I,S的排列i1,i2 ,...in
目标函数:I的完成时间,t(I)=k=1nn-k+1*Tik
解t(I),使得其值最小,且其对应的最小排列即为应求解。
贪心算法:
设计策略:加工时间短的先做
算法:根据加工事件从小到大排序,依次加工
算法正确性:对所有输入实例都得到最优解
证:
关于贪心法的经典算法:
单源最短路径是指:给定源顶点s∈Vs∈V到分别到其他顶点v∈V−{s}v∈V−{s}的最短路径的问题。
Dijkstra算法采用贪心策略:按路径长度递增的顺序,逐个产生各顶点的最短路径。算法过程中需要维护一个顶点集S,此顶点集保存已经找到最短路径的顶点。还需要维护一个距离数组dist, dist[i]表示第i个顶点与源结点s的距离长度。
Java程序代码:
package Greedy;
import java.util.Scanner;
public class Dijkstra{
/**
* 单元最短路径算法
* 缺陷:算法中将10000视为无穷大
*/
private double[] D;
private double[] S;
private double a[][];
public void setA(double[][] a) {
this.a = a;
}
//获得最短路径
public double[] getShortestPath(int begin){ //邻接矩阵作为参数传递
S=new double[a[0].length]; //存储最终结果
D=a[begin]; //初始化观测域D
int time=0,index=0;
while(time<S.length){
index=getMin(D);
S[index]=D[index]; //获得观测域中的最端路径 并将最短路径压入S
D[index]=10001; //将已经决定的路径设置为10001
time++;
renew(index);//更新观测域D
}
return S;
}
public void renew(int j){ //根据压入的点更新数组D
for(int i=0;i<D.length;i++){
if(D[i]==10001){
continue;
}
if(S[j]+a[j][i]<D[i]){
D[i]=S[j]+a[j][i];
}
}
}
public int getMin(double[] D){
int index=0;
double min=D[0];
for(int i=1;i<D.length;i++){
if(min>D[i]&&min>=0&&D[i]>=0){
min=D[i];
index=i; //获得最小值的数组下标
}
}
return index;
}
public static void main(String[] args) {
Scanner scanner=new Scanner(System.in);
System.out.println("请输入邻接矩阵的长度: ");
int len=scanner.nextInt();
double a[][]=new double[len][len];
for(int i=0;i<len*len;i++){
a[i/len][i%len]=scanner.nextDouble();
}
Dijkstra dijkstra=new Dijkstra();
dijkstra.setA(a);
double answer[]=dijkstra.getShortestPath(0);
for (double d : answer) {
System.out.println(d);
}
}
}
输入:邻接矩阵的大小及数值,例如:
6
0 50 10 10000 45 10000
10000 0 15 10000 10 10000
20 10000 0 15 10000 10000
10000 20 10000 0 35 3
10000 10000 10000 30 0 10000
10000 10000 10000 10000 10000 0
输出:
0.0
45.0
10.0
25.0
45.0
28.0(该结果为从顶点1到其他点的最短路径)
在上述算法中,通过使用贪局部最优(每一个都求观测域与其补集的最小路径),将整个问题模型分割成小的问题模块,当一步一步的局部最优汇总之后,我们就求得了整体上的最优解。但这种情况不是任何时候都能适应,他实现的前提是求解过程可以转换成多步判断过程。
普里姆算法(Prim算法),图论中的一种算法,可在加权连通图里搜索最小生成树。意即由此算法搜索到的边子集所构成的树中,不但包括了连通图里的所有顶点(英语:Vertex (graph theory)),且其所有边的权值之和亦为最小。算法描述
1).输入:一个加权连通图,其中顶点集合为V,边集合为E;
2).初始化:Vnew = {x},其中x为集合V中的任一节点(起始点),Enew = {},为空;
3).重复下列操作,直到Vnew = V:
a.在集合E中选取权值最小的边<u, v>,其中u为集合Vnew中的元素,而v不在Vnew集合当中,并且v∈V(如果存在有多条满足前述条件即具有相同权值的边,则可任意选取其中之一);
b.将v加入集合Vnew中,将<u, v>边加入集合Enew中;
4).输出:使用集合Vnew和Enew来描述所得到的最小生成树。
Java程序代码:
package Greedy;
import java.util.ArrayList;
import java.util.HashSet;
/**
*
*@describe:普利姆算法(最小耗费生成树)
* @author 曾鹏
*2018年9月16日下午8:30:13
*/
public class Prim {
private Graph graph;
private int min; //用于存储new_V与other_V形成的边的最小值
private int[][] ad_matrix;
private HashSet<Integer> new_V=new HashSet<>(); // HashSet用于存储不同的元素
private ArrayList<Integer> other_V=new ArrayList<>(); // HashSet用于存储不同的元素
public static final int N=65535;
public void setGraph(Graph graph) {
this.graph = graph;
}
public void getMintree(){
min=graph.getAd_matrix()[0][0];
ad_matrix=graph.getAd_matrix();
int len=ad_matrix[0].length;
//对other_V的初始化
for(int i=0;i<len;i++){
other_V.add(i);
}
int line=0,columm=0;
int length=0;
// System.out.println(getMin());
while(new_V.size()<graph.getVertix().length){
length=getMin();
line=length/len;
columm=length%len;
System.out.println(graph.getVertix()[line]+"――"+graph.getVertix()[columm]+":"+min);
new_V.add(line); //添加至new_V集合
new_V.add(columm); //添加至new_V集合
ad_matrix[line][columm]=N;
ad_matrix[columm][line]=N;
min=N;
other_V.set(line, -1);
other_V.set(columm, -1);
}
}
public int getMin(){ //用于更新最小值min
int index=0;
int len=ad_matrix[0].length;
if(new_V.isEmpty()){
for (int i = 0; i < graph.getAd_matrix()[0].length*graph.getAd_matrix().length; i++) {
if(min>ad_matrix[i/len][i%len]){
min=ad_matrix[i/len][i%len];
index=i;
}
}
return index;
}
for (Integer j : new_V) {
for(Integer i : other_V){
if(i>0&&min>ad_matrix[j][i]){
min=ad_matrix[j][i];
index=j*ad_matrix[0].length+i;
}
}
}
return index;
}
public static void main(String[] args){
char[] vertex=new char[]{'A','B','C','D','E','F','G'}; //顶点数组
int[][] ad_matrix=new int[7][]; //邻接矩阵
ad_matrix[0]=new int[]{N,5,7,N,N,N,2};
ad_matrix[1]=new int[]{5,N,N,9,N,N,3};
ad_matrix[2]=new int[]{7,N,N,N,8,N,N};
ad_matrix[3]=new int[]{N,9,N,N,N,4,N};
ad_matrix[4]=new int[]{N,N,8,N,N,5,4};
ad_matrix[5]=new int[]{N,N,N,4,5,N,6};
ad_matrix[6]=new int[]{2,3,N,N,4,6,N};
Prim prim=new Prim();
Graph g=new Graph(vertex, ad_matrix);
prim.setGraph(g); //创建图
prim.getMintree();
}
}
//内部类
class Graph{
private char[] vertix; //顶点数组
private int[][] ad_matrix; //临邻接组
private char[][] min_tree; //用于存储最小耗费生成树 双亲表示法:在子节点对应的空间存储父节点坐标
public void setMin_tree(char[][] min_tree) {
this.min_tree = min_tree;
}
public char[] getVertix() {
return vertix;
}
public void setVertix(char[] vertix) {
this.vertix = vertix;
}
public int[][] getAd_matrix() {
return ad_matrix;
}
public void setAd_matrix(int[][] ad_matrix) {
this.ad_matrix = ad_matrix;
}
public Graph(char[] v,int[][] a) {
vertix=v;
ad_matrix=a;
min_tree=new char[vertix.length][2]; //一列存储字符 一列存储父节点下标
}
}
输入:程序内部设置的邻接矩阵
输出:
A――G:2
G――B:3
G――E:4
E――F:5
F――D:4
A――C:7
Prim算法其实也是通过局部最优最后求出整体最优的一个过程,Prim算法每次添加一个点到new_V中,都是在更新一次数据,每次都在更新后的数据中获取最小的路径,再更新,再获取,递归原列表为空时结束递归,并在这个过程中求出最优解,同上述的Dijkstra算法一样,这个算法实现的前提是通过一步一步的局部最优便可获得整体最优。
贪心法对于求解最优化问题十分有效,但是,有时候,局部的最优并不一定能到导致全局的最优,我们可以来看看下面的例子:
有4个物品要装入背包,物品重量和价值如下:
标号 | 1 | 2 | 3 | 4 |
重量wi | 3 | 4 | 5 | 2 |
价值vi | 7 | 9 | 9 | 2 |
背包的重量限制为6,问如何选择物品,使得在不超重的情况下装入背包的物品价值达到最大?
对于这类问题,按照以往的贪心法来做的话,我们先根据物品的单位重量价值进行从大到小排序:7/3>9/4>/5>2/2,即:1,2,3,4.
通过贪心法的求得解{1,4},重量为5,价值为9
更优的解:{2,4},重量为6,价值为11.
上述的例子说明了贪心法并不是适合所有的情况,在某些情况下就失效了,但是,贪心法即使得不到最优解,它仍旧可以获得最优解的近似结果,所以可以通过最优解进行第一轮处理,再通过更细致的方法处理之后的数据,来提高算法的效率。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。