基础算法-贪心&动态规划
基本概念
贪心算法:
贪心算法又叫做贪婪算法,它在求解问题时,总是做出眼前最大利益,也就是说只顾眼前不顾大局,所以它是局部最优解。核心点:通过局部最优解推出全局最优
怎么去贪就是贪心策略怎么选择。
思考
公司安排会议室,现在给你N个会议的开始和结束时间,你怎样安排会议才能使会议室得到最大利用?即安排最多场次的会议
选时间最短:1-3.5 2-4 3.6-6 6-7如果选择2-4那么1-3和3-5都不可以选择所以选择时间最短不是最佳策略
按结束时间从小到大排序:首先把第一个加入可以开会的列表,之后只要开始时间大于上一个结束时间的会议就可以开
1-3.5 可以开
2-4
3.6-6 可以开
6-7 可以开
代码实现
package Tanxin;
import java.awt.List;
import java.util.ArrayList;
import java.util.Scanner;
import java.util.concurrent.ArrayBlockingQueue;
class Meeting implements Comparable<Meeting>{
int meetNum; //会议编号
int startTime; //开始时间
int endTime; //结束时间
public Meeting(int meetNum, int startTime, int endTime) {
super();
this.meetNum = meetNum;
this.startTime = startTime;
this.endTime = endTime;
}
@Override
public String toString() {
return "Meeting [meetNum=" + meetNum + ", startTime=" + startTime + ", endTime=" + endTime + "]";
}
public int compareTo(Meeting o) {
if(this.endTime>o.endTime)return 1;
return -1;
}
}
public class MeetingTest {
public static void main(String[] args) {
Scanner cin=new Scanner(System.in);
ArrayList<Meeting> meetings =new ArrayList<Meeting>();
int n =cin.nextInt(); //n个会议
for (int i=0;i<n;i++){
int start =cin.nextInt();
int end=cin.nextInt();
Meeting meeting = new Meeting(i+1, start, end);
meetings.add(meeting);
}
meetings.sort(null);
int curTime = 0;//当前时间,从零点开始
for (int i=0;i<n;i++){
Meeting meeting=meetings.get(i);
if (meeting.startTime>=curTime){ //会议的开始时间大于等于当前时间,就能开
System.out.println(meeting.toString());
curTime=meeting.endTime;
}
}
}
}
贪心算法的套路:一定会有一个排序,哈夫曼编码
贪心算法的关键是贪心策略的选择,选择贪心策略必须具备无后效性,即某个状态以前的过程不会影响以后的状态,只与当前状态有关。不是对所有问题都能得到整体最优解。
贪心算法的最重要的两个点是:
贪心策略和通过局部最优解能够得到全局最优解
以下问题可以由贪心算法解决:
1.针对某个问题有限制值,以及有一个期望的最好结果,通常是从某些数据中选出其中一些,达到最好的结果
2.一般会有一个排序,找出贡献最大的
3.举例看贪心是否可以解决(测试用例)
一般用在任务调度,教室排课等系统。
实际上用贪心算法解决问题的思路,并不总能给出最优解
思考
双十一马上就要来了,购物车中加了N个东西,5000元(不找零),每个东西只能买一件,应该怎么选择物品能使5000元额度能最大利用,如果存在多种最优组合只需要给出一种即可
代码实现
package Tanxin;
public class CarDp {
public static void main(String[] args) {
int weight[]={1,2,3,4,5,8,9} ; //购物车问题,值需要一个价值就可以了,重量都没有
int w=8; //中奖的钱
int n=weight.length;
int dp[][]=new int [n+1][w+1]; //n表示物品,w表示重量,初始化全是0;
for(int i=1;i<=n;i++){
for (int cw=0;cw<=w;cw++){ //分割的背包
if(weight[i-1]<=cw){ //如果能装 状态转移方程
dp[i][cw]=Math.max(
weight[i-1]+dp[i-1][cw-weight[i-1]],
dp[i-1][cw]
);
}else{ //如果不能装 状态转移方程
dp[i][cw]=dp[i-1][cw];
}
}
}
System.out.println(dp[n][w]);
}
}
动态规划
经典题:编辑距离,两个字符串的最长公共子串
背包问题
背包容量50kg 现有如下物品(物品不能切分并且只有一个),请问怎么装才能拿到最大价值
重量 价值 性价比
物品1 10 60 6
物品2 20 100 5
物品3 40 120 3
性价比最高:贪心策略,按性价比排序,得到最大价值60+100=160背包装了30kg。很显然40+10为120+60=180, 因此用贪心解决不了
解决办法:
遍历(枚举)排列组合
重量 价值 性价比
物品1 1 6 6
物品2 2 10 5
物品3 4 12 3
把5kg的背包拆分成5份
状态转移方程:
能装的时候,每次和上边的比较,大就装否则就不装
Max{money[i]+res[i-1] [w-weight[i]] , res[i-1][w]}
money[i]+res[i-1] [w-weight[i]];装这个物品
res[i-1][w] ;不装这个物品
要确定哪个物品加入背包了就找表格最后一列上下两行不等就是下一行的物品加进来了。
18!=16说明物品3加进来了,
5-4=1,找1kg的列。
类推
代码实现
package Tanxin;
public class Dp {
public static void main(String[] args) {
int value[]={60,100,120} ;
int weight[]={10 ,20,40} ; //购物车问题,只需要一个价值就可以了,重量都没有
int w=50;
int n=3;
int dp[][]=new int [n+1][w+1]; //n表示物品,w表示重量,初始化全是0;
//时间复杂度O(nw)
for(int i=1;i<=n;i++){ //每次加的物品
for (int cw=1;cw<=w;cw++){ //分割的背包
if(weight[i-1]<=cw){ //如果能装 状态转移方程
dp[i][cw]=Math.max(
value[i-1]+dp[i-1][cw-weight[i-1]],
dp[i-1][cw]
);
}else{ //如果不能装 状态转移方程
dp[i][cw]=dp[i-1][cw];
}
}
}
System.out.println(dp[n][w]);
}
}
考虑:生成找出物品的路径怎么找
package Tanxin;
public class CarDp {
public static void main(String[] args) {
int weight[]={1,2,3,4,5,6,3} ; //购物车问题,值需要一个价值就可以了,重量都没有
int w=10; //中奖的钱
int n=weight.length;
int dp[][]=new int [n+1][w+1]; //n表示物品,w表示重量,初始化全是0;
for(int i=1;i<=n;i++){
for (int cw=1;cw<=w;cw++){ //分割的背包
if(weight[i-1]<=cw){ //如果能装 状态转移方程
dp[i][cw]=Math.max(
weight[i-1]+dp[i-1][cw-weight[i-1]],
dp[i-1][cw]
);
}else{ //如果不能装 状态转移方程
dp[i][cw]=dp[i-1][cw];
}
}
}
//算完后只需输出矩阵最后一个即可
System.out.println("能装的最大价值为:"+dp[n][w]);
for(int i=1;i<=n;i++){
for (int cw=1;cw<=w;cw++){
System.out.print(dp[i][cw]+" ");
}
System.out.println();
}
w=dp[n][w];
System.out.println("具体物品:");
for(int i=n;i>1;i--){
if (dp[i][w]==dp[i-1][w]){
//不用加
}else{
System.out.println(i+":"+weight[i-1]);
w=w-weight[i-1];
}
}
if (w!=0)System.out.println(1+":"+weight[0]);//表示最后一个物品是要加进来的
}
}
和遍历的比较及优化
遍历每次在物品加进来的时候会保存选择或者不选择两种状态,这样下去后边的状态保存的越多是2^n个,动态规划是每次吧当前的情况的最优解计算出来,层层递推,下一层的最优解是基于上一次结果存下来的,,所以最后结果就是最优解,其实就是把问题分成一个子问题,通过子问题求解全局最优解。
动态规划和贪心比较
贪心是只管眼前不会管以后的情况,动态规划不一样,动态规划的每一次递推都是基于上一次的最优解进行。往往动态规划能找出问题的最优解,而贪心不一定。但是动态规划的时间复杂度更高,相对来说贪心算法是比较高效的,动态规划如果子问题很多容易计算不出结果,并且动态规划的问题往往能用贪心解决一部分甚至很大一部分。因此如果在实际项目中可以使用贪心算法进行问题求解。很多问题是保证尽可能准确,贪心恰恰是符合这个规则的。