贪心算法

问题建模与描述:

  我们先来看一个简单的例子:调度问题,有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),使得其值最小,且其对应的最小排列即为应求解。

贪心算法:

设计策略:加工时间短的先做

算法:根据加工事件从小到大排序,依次加工

算法正确性:对所有输入实例都得到最优解

关于证明我们就可以用推理演绎进行证明。

关于贪心法的经典算法:

  1. 单元最短路径算法:

 单源最短路径是指:给定源顶点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到其他点的最短路径)

 

在上述算法中,通过使用贪局部最优(每一个都求观测域与其补集的最小路径),将整个问题模型分割成小的问题模块,当一步一步的局部最优汇总之后,我们就求得了整体上的最优解。但这种情况不是任何时候都能适应,他实现的前提是求解过程可以转换成多步判断过程。

 

  1. Prim算法

普里姆算法(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.

上述的例子说明了贪心法并不是适合所有的情况,在某些情况下就失效了,但是,贪心法即使得不到最优解,它仍旧可以获得最优解的近似结果,所以可以通过最优解进行第一轮处理,再通过更细致的方法处理之后的数据,来提高算法的效率。

  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值