贪心算法 x 优先级队列
1. 预备知识
1.1 优先级队列
- 优先队列PriorityQueue是Queue接口的实现,可以对其中元素进行排序
Queue<Module> q = new PriorityQueue<>(compare);
- 常用的方法:
peek() // 返回队首元素 poll() // 返回队首元素,且队首元素出列 offer() // 向队列中添加元素 size() // 返回队列元素的个数 isEmpty() // 判断队列是否为空
- 队列可以保存基本数据类型
- 如下面这段代码,代码的输出为1 2 3。可见进入队列的元素已经按照升序的方式排好了顺序(默认的排序方式是升序)
Queue<Integer> q = new PriorityQueue<>(); q.offer(1); q.offer(3); q.offer(2); while(!q.isEmpty()) { System.out.print(q.poll()+" "); }
- 当然也可以自定义比较器,进行降序排列
此时调用如下代码输出的就是3 2 1,变成了降序排列。//自定义比较器,降序排列 static Comparator<Integer> cmp = new Comparator<Integer>() { public int compare(Integer e1, Integer e2) { return e2 - e1; } };
Queue<Integer> p= new PriorityQueue<>(cmp); p.offer(1); p.offer(3); p.offer(2); while(!p.isEmpty()) { System.out.print(p.poll()+" "); }
- 比较器升降序说明
Comparator<Object> cmp = new Comparator<Object>() { public int compare(Object o1, Object o2) { //升序 return o1-o2; //降序 return o2-o1; } };
- 如下面这段代码,代码的输出为1 2 3。可见进入队列的元素已经按照升序的方式排好了顺序(默认的排序方式是升序)
- 队列也可以保存自定义的类
- 先自定义一个类User,包含两个属性:id和age
class User{ int id; int age; public User(int id,int age){ this.id=id; this.age=age; } }
- 自定义比较类,按照自己想要的顺序比较类的属性
这里按照id从小到大升序排列//自定义比较类,这里按照id从小到大升序排列 static Comparator<User> user=new Comparator<User>() { public int compare(User o1, User o2) { return o1.id-o2.id; } };
- 最后通过代码
代码的输出为:Queue<User> q=new PriorityQueue<>(user); User n1=new User(1, 2); User n2=new User(3, 5); User n3=new User(2, 3); q.offer(n1); q.offer(n2); q.offer(n3); User n; while(!q.isEmpty()) { n=q.poll(); System.out.println("id: "+n.id+" age:" +n.age); }
效果显而易见id: 1 age:2 id: 2 age:3 id: 3 age:5
- 先自定义一个类User,包含两个属性:id和age
1.2 贪心算法
- 所谓贪心算法是指,在对问题求解时,总是做出在当前看来是最好的选择。也就是说不从整体最优上加以考虑,他所做出的仅是在某种意义上的局部最优解。
- 贪心算法没有固定的算法框架,算法设计的关键是贪心策略的选择。必须注意的是,
贪心算法不是对所有问题都能得到整体最优解,选择的贪心策略必须具备无后效性,即某个状态以后的过程不会影响以前的状态,只与当前状态有关。所以对所采用的贪心策略一定要仔细分析其是否满足无后效性。 - 贪心策略适用的前提是:局部最优策略能导致产生全局最优解。
2. 实例
Leetcode第502. IPO题就是一道典型的贪心算法x优先级队列的题目
- 题目如下
假设 力扣(LeetCode)即将开始 IPO 。为了以更高的价格将股票卖给风险投资公司,力扣 希望在 IPO 之前开展一些项目以增加其资本。由于资源有限,它只能在 IPO 之前完成最多 k 个不同的项目。帮助 力扣 设计完成最多 k 个不同项目后得到最大总资本的方式。 给你 n 个项目。对于每个项目 i ,它都有一个纯利润 profits[i] ,和启动该项目需要的最小资本 capital[i] 。 最初,你的资本为 w 。当你完成一个项目时,你将获得纯利润,且利润将被添加到你的总资本中。 总而言之,从给定项目中选择 最多 k 个不同项目的列表,以 最大化最终资本 ,并输出最终可获得的最多资本。 答案保证在 32 位有符号整数范围内。 示例 1: 输入:k = 2, w = 0, profits = [1,2,3], capital = [0,1,1] 输出:4 解释: 由于你的初始资本为 0,你仅可以从 0 号项目开始。 在完成后,你将获得 1 的利润,你的总资本将变为 1。 此时你可以选择开始 1 号或 2 号项目。 由于你最多可以选择两个项目,所以你需要完成 2 号项目以获得最大的资本。 因此,输出最后最大化的资本,为 0 + 1 + 3 = 4。 示例 2: 输入:k = 3, w = 0, profits = [1,2,3], capital = [0,1,2] 输出:6
- 题目的思路:
我们每次都去找在当前已有的资本下能够完成项目中的利益最大的那个项目,是典型的贪心算法的思想 - 解题步骤:
- 定义一个Module类用来存放每一个项目的profits和capital,方便提供给优先级队列和数组使用
class Module { public Module(int profits, int capital) { this.profits = profits; this.capital = capital; } int profits; int capital; }
- 将每一个项目的profits和capital存到一个Module类型的数组中,建立起一一对应的关系。并且按照资本captial从小到大升序排列
排列的函数如下:Module[] profitsCaptial=new Module[profits.length]; for(int i=0;i<profits.length;i++){ profitsCaptial[i]=new Module(profits[i],capital[i]); } Arrays.sort(profitsCaptial,new SortByCaptial());
public static class SortByCaptial implements Comparator<Module> { @Override public int compare(Module o1, Module o2) { return o1.capital-o2.capital; } }
- 建立一个优先级队列q,q使用排列函数compare按照每一个项目的profits降序排列。每次循环将当前已有资本可以开展的项目加入队列,并且按照profits从大到小降序排列。这时排在队首的元素就是当前已有资本可以开展的利益最大的项目(体现贪心思想),将该利益加到w上,进行下一次循环。所有循环结束后,返回w即可。
int count = 1; Queue<Module> q = new PriorityQueue<>(compare); int i = 0; while (count <= k) { while ( i < profits.length && w >= profitsCaptial[i].capital) { q.offer(new Module(profitsCaptial[i].profits,profitsCaptial[i].capital)); i++; } if (q.isEmpty()) return w; Module n = q.poll(); w += n.profits; count++; }
- 定义一个Module类用来存放每一个项目的profits和capital,方便提供给优先级队列和数组使用