贪心算法


1 什么是贪心算法

   所谓贪心算法是指,在对问题求解时,总是做出在当前看来是最好的选择。也就是说,不从整体最优上加以考虑,他所做出的仅是在某种意义上的局部最优解。

2 贪心算法的特点:
   贪心算法不是对所有问题都能得到整体最优解,但对范围相当广泛的许多问题它能产生整体最优解或者是整体最优解的近似解。
3 贪心算法的基本思路如下:

 1.建立数学模型来描述问题。
   2.把求解的问题分成若干个子问题。
   3.对每一子问题求解,得到子问题的局部最优解。
   4.把子问题的解局部最优解合成原来解问题的一个解。

4 实现该算法的过程:
   从问题的某一初始解出发;
   while 能朝给定总目标前进一步 do
   求出可行解的一个解元素;
   由所有解元素组合成问题的一个可行解;

5 贪心算法解决活动安排问题:

   5.1问题描述:

    假设要在足够多的会场里安排一批数目为N活动,E={e1, e2, …, en},其中每个活动都需要使用某一会场,而在同一时间内该会场只能由一个活动使用,每个活动都有开始时间si和结束时间fi(si<fi),并希望使用尽可能少的会场。设计一个有效的贪心算法进行安排。

   5.2问题分析:

   根据会场安排问题的定义,首先将问题简化为:找出两个活动,若ei和ej满足si≥fj或sj≥fi,则称这两个活动相容,即问题转化为:要求找出最多相容会场集合A。

    问题简化为对相容会场A的寻找,下面用贪心方法分析过程,根据题意,选取一种量度标准,然后按量度标准对n个输入排序,按顺序一次输入一个量。如果这个输入和当前已构成在这种量度意义下的部分最优解加在一起不能产生一个可行解,则不把此输入加到这部分解中,这种能够得到某种量度意义下的最优解的分级处理方法就是贪心方法。那么问题转化为对度量标准的寻找,判断各个数据是否可以包含在解向量中去,然后根据目标函数来选择最优解!

   5.3问题求解:

      1.将所有活动按结束时间排序,得到活动集合E={e1,e2…en};

      2.先将e1选入结果集合A中,即A={e1};

      3.依次扫描每一个活动ei:

         如果ei的开始时间晚于最后一个选入A的活动ej的结束时间,则将ei选入A中,否则放弃ei。

   5.4图示

        下面表格有12个活动,并给出各个活动的开始时间与结束时间,那么请用上述贪心解法分析并求解最优会场数

         活动i           1  2  3  4  5  6  7   8    9  10  11 
         开始时间si   1  3  0  5  3  5  6   8    8   2   12 
         结束时间fi    4 5   6  7  8  9 10 11 12 13  14
 

         根据贪心策略:现将1~12个活动的结束时间排序(为解说方便上表格已经排好)排序可用快速排序。

         毋庸置疑将E1先分配入会场集合A1,然后按照顺序找出下个活动,使得其开始时间小于E1的结束时间(即满足时间不冲突),如图易知为E4,再将E4分配给A1,以后每一步骤都重复如E4的选择。经过第一轮的筛选可知会场集合A1中包含:A1={E1,E4,E8,E11}

        此时已经没有活动在相容于会场A1中,那么再继续对A2进行同样的选取,同理:

A2={E2,E6}

A3={E3,E7}

A4={E5,E9}

A5={E10}

        那么得出总的会场数目:S=5

    5.5代码实现:

 

Java代码   收藏代码
  1. package chapter02;  
  2.   
  3. import java.util.Arrays;  
  4. import java.util.LinkedList;  
  5.   
  6. public class ActivitySelection {  
  7.     private int[] start;    // 开始时间  
  8.     private int[] finish;   // 结束时间  
  9.       
  10.     public ActivitySelection(int[] s, int[] f) {  
  11.         assert s.length == f.length;  
  12.           
  13.         start = s;  
  14.         finish = f;  
  15.     }  
  16.       
  17.     public ActivitySelection(int[] s, int[] f, boolean sort) {  
  18.         assert s.length == f.length;  
  19.         if (!sort) {  
  20.             // 按结束时间进行插入排序  
  21.             for (int i = 1; i < s.length; i++) {  
  22.                 for (int j = i; j > 0 && f[j] < f[j-1]; j--) {  
  23.                     swap(s, j, j-1);  
  24.                     swap(f, j, j-1);  
  25.                 }  
  26.             }  
  27.         }  
  28.           
  29.         start = s;  
  30.         finish = f;  
  31.     }  
  32.       
  33.     private void swap(int[] a, int i, int j) {  
  34.         int temp = a[i]; a[i] = a[j]; a[j] = temp;  
  35.     }  
  36.       
  37.     public int[] recurisveSelect() {  
  38.         LinkedList<Integer> activities = new LinkedList<Integer>();  
  39.         recursiveSelect(activities, -1, start.length);  
  40.           
  41.         return toIntArray(activities);  
  42.     }  
  43.   
  44.     private int[] toIntArray(LinkedList<Integer> activities) {  
  45.         int[] result = new int[activities.size()];  
  46.         int i = 0;  
  47.         for (int activity : activities) {  
  48.             result[i++] = activity;  
  49.         }  
  50.         return result;  
  51.     }  
  52.       
  53.     private void recursiveSelect(LinkedList<Integer> activities, int i, int j) {  
  54.         int m = i + 1;  
  55.         while ((i >= 0 && m < j && start[m] < finish[i]) || i >= start.length) {  
  56.             m++;  
  57.         }  
  58.         if (m < j) {  
  59.             activities.add(m);  
  60.             recursiveSelect(activities, m, j);  
  61.         }  
  62.     }  
  63.       
  64.     public int[] select() {  
  65.         LinkedList<Integer> activities = new LinkedList<Integer>();  
  66.         activities.add(0); // add first activity  
  67.   
  68.         int i = 0;  
  69.         for (int m = 1; m < start.length; m++) {  
  70.             if (start[m] >= finish[i]) {  
  71.                 activities.add(m);  
  72.                 i = m;  
  73.             }  
  74.         }  
  75.         return toIntArray(activities);  
  76.     }  
  77.       
  78.     public static void main(String[] args) {  
  79.         int[] s = new int[] {130535688212};  
  80.         int[] f = new int[] {4567891011121314};  
  81.           
  82.         ActivitySelection as = new ActivitySelection(s, f);  
  83.         int[] acts = as.recurisveSelect();  
  84.         System.out.println("recursively select activites: " + Arrays.toString(acts));  
  85.           
  86.         acts = as.select();  
  87.         System.out.println("\nselect activites: " + Arrays.toString(acts));  
  88.           
  89.         s = new int[] {513,  8035,  6,  8,  212};  
  90.         f = new int[] {7451168910121314};  
  91.         as = new ActivitySelection(s, f, false);  
  92.         acts = as.recurisveSelect();  
  93.         System.out.println("\nrecursively select activites: " + Arrays.toString(acts));  
  94.           
  95.         acts = as.select();  
  96.         System.out.println("\n");  
  97.         System.out.println("select activites: " + Arrays.toString(acts));  
  98.     }  
  99. }  

 

 6 贪心算法解决背包问题:

    6.1问题描述:

      已知有一个可容纳重量为C的背包以及n件物品,其中第i件物品的重量为wi,每件物品的价值为pi(pi>0)。怎样向背包装如物品,才能使装入背包的物品的价值最大?

    6.2贪心算法求解背包问题基本思想:

     将n件物品的价值/重量比求出(价值/重量比,也就是单位重量物品的价值),然后按照非递增排序。装入过程,从价值/重量比最大的物品开始装入,直到某一件物品不能全部装入背包的时候,停止装入,而要对最后一件不能全部装入的物品进行部分装入,将背包装满,总价值达到最大。如果全部的物品都能装入背包,那得到的肯定也是最优解,这是一种特殊的情况。

    6.3代码实现:

Java代码   收藏代码
  1. package chapter02;  
  2.   
  3. import java.util.Arrays;  
  4. import java.util.Comparator;  
  5. import java.util.HashMap;  
  6. import java.util.Iterator;  
  7. import java.util.Map;  
  8. import java.util.Set;  
  9.   
  10. /** 
  11.  * 贪心算法求解背包问题 
  12.  *  
  13.  * @author shirdrn 
  14.  */  
  15. public class KnapsackProblem {  
  16.   
  17.     private Map<String, Double> weight; // 物品重量Map  
  18.     private Map<String, Double> price; // 物品价值Map  
  19.   
  20.     private Map.Entry<String, Double>[] rateEntries; // 每件物品的价值重量比  
  21.     private Double capacity; // 背包容量  
  22.     private Double optimized = 0.00// 最优解(背包能够装载物品的最大价值)  
  23.     private Map<String, Double> solutions; // 装入背包的每种物品,及其对应的该种物品的百分比  
  24.   
  25.     public KnapsackProblem(Map<String, Double> weight,  
  26.             Map<String, Double> price, Double capacity) {  
  27.         this.weight = weight;  
  28.         this.price = price;  
  29.         this.capacity = capacity;  
  30.         this.solutions = new HashMap<String, Double>();  
  31.         this.sort(); // 初始化,对物品按照重量/价值比排序  
  32.     }  
  33.       
  34.     public static void main(String []args){  
  35.           
  36.     }  
  37.   
  38.     /** 
  39.      * 对输入的Map map根据其Value执行降序排序 
  40.      *  
  41.      * @param map 
  42.      * @return 排好序的Map.Entry<String, Double>[]数组 
  43.      */  
  44.     @SuppressWarnings("unchecked")  
  45.     public void sort() {  
  46.         Map<String, Double> rateMap = new HashMap<String, Double>();  
  47.         Iterator<Map.Entry<String, Double>> wit = this.weight.entrySet()  
  48.                 .iterator();  
  49.         Iterator<Map.Entry<String, Double>> pit = this.price.entrySet()  
  50.                 .iterator();  
  51.         while (wit.hasNext() && pit.hasNext()) {  
  52.             Map.Entry<String, Double> w = wit.next();  
  53.             Map.Entry<String, Double> p = pit.next();  
  54.             rateMap.put(w.getKey().trim(), p.getValue() / w.getValue());  
  55.         }  
  56.         Set<Map.Entry<String, Double>> entrySet = rateMap.entrySet();  
  57.         rateEntries = (Map.Entry[]) entrySet.toArray(new Map.Entry[entrySet  
  58.                 .size()]);  
  59.         // 对rateEntries进行降序排序  
  60.         Arrays.sort(rateEntries, new Comparator() {  
  61.   
  62.             public int compare(Object o1, Object o2) {  
  63.                 Double v1 = ((Map.Entry<String, Double>) o1).getValue();  
  64.                 Double v2 = ((Map.Entry<String, Double>) o2).getValue();  
  65.                 return v2.compareTo(v1);  
  66.             }  
  67.   
  68.         });  
  69.     }  
  70.   
  71.     /** 
  72.      * 背包问题的装载求解方法 
  73.      */  
  74.     public void solve() {  
  75.         Double c = this.capacity;  
  76.         String lastGoods = null;  
  77.         for (Map.Entry<String, Double> entry : rateEntries) {  
  78.             String goods = entry.getKey().trim();  
  79.             if (this.weight.get(goods) < c) { // 如果当前物品重量小于背包所能装载的物品的重量,则直接全部放入背包  
  80.                 this.solutions.put(goods, 1.00); // 对于物品goods全部放入背包,可行解为1.00  
  81.                 c = c - this.weight.get(goods); // 背包剩余可装载重量  
  82.                 this.optimized += this.price.get(goods); // 将物品goods的全部总价值加入到最优值变量上  
  83.             } else { // 不能将某件物品全部装载到背包里,应该将最后一件的一部分装入背包  
  84.                 lastGoods = entry.getKey(); // 记下最后一件物品  
  85.                 break;  
  86.             }  
  87.         }  
  88.         if (lastGoods != null) { // 如果不能将全部物品装载到背包中  
  89.             Double lastRate = c / this.weight.get(lastGoods);  
  90.             this.solutions.put(lastGoods, lastRate);  
  91.             this.optimized += lastRate * this.price.get(lastGoods); // 装入背包的部分价值计入最优解(最大价值变量)  
  92.         }  
  93.     }  
  94.   
  95.     /** 
  96.      * 获取物品重量/价值比非递增排序数组 
  97.      *  
  98.      * @return 
  99.      */  
  100.     public Map.Entry<String, Double>[] getRateEntries() {  
  101.         return rateEntries;  
  102.     }  
  103.   
  104.     /** 
  105.      * 获取最优解(最大价值) 
  106.      *  
  107.      * @return 
  108.      */  
  109.     public Double getOptimized() {  
  110.         return optimized;  
  111.     }  
  112.   
  113.     /** 
  114.      * 获取装入背包的物品的重量百分比解向量 
  115.      *  
  116.      * @return 
  117.      */  
  118.     public Map<String, Double> getSolutions() {  
  119.         return solutions;  
  120.     }  
  121. }  

 

  6.4 关于0/1背包问题:

      对于0—1背包问题,贪心选择之所以不能得到最优解是因为它无法保证最终能将背包装满,部分背包空间的闲置使每千克背包空间所具有的价值降低了。事实上,在考虑0—1背包问题的物品选择时,应比较选择该物品和不选择该物品所导致的最终结果,然后再作出最好选择。由此就导出许多互相重叠的于问题。这正是该问题可用动态规划算法求解的另一重要特征。动态规划算法的确可以有效地解0/1背包问题。

7 贪心算法解决哈夫曼树问题:

   哈夫曼编码是广泛地用于数据文件压缩的十分有效的编码方法。其压缩率通常在20%~90%之间。哈夫曼编码算法用字符在文件中出现的频率表来建立一个用0,1串表示各字符的最优表示方式。

   给出现频率高的字符较短的编码,出现频率较低的字符以较长的编码,可以大大缩短总码长。

   例如一个包含100,000个字符的文件,各字符出现频率不同,如下表所示。定长变码需要300,000位,而按表中变长编码方案,文件的总码长为: (45×1+13×3+12×3+16×3+9×4+5×4)×1000=224,000。

   我们一样的还是用一张图来描述霍夫曼编码的流程:

   这个过程概括的说就是一个根据频率建立二叉树的过程。建完之后对应的编码也就完成了。

   第一步a. 这个a就和之前的活动选择问题一样,把需要的所以字符按照频率排序。

   第二部b. 选取出现频率最小的两个节点 f 和 e。组成一个新的节点,新的节点的频率就是e和f的和。原来的e和f分别成了新节点的左子节点和右子节点。(注意这里一个默认的规则就是频率小的是左子节点,大的是右子节点。)然后把之前的两个节点从原来的组中删除,加新的节点加入排序。

   第三部c. 其实和第二部雷同,就是一个循环的过程。这里再次去除队列中的最小频率的两项(这时是c和b)。组成新的节点加入队列排序。

   如此循环往复,最后就形成了(f)这个二叉树。现在有了二叉树只有,我们把左子树这条边标记为0,右子树标记为1。这样就差生了对应的编码方式 a=0; b=101;....


  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值