貪欲なアルゴリズム(従来例のいくつか)
贪心算法,指在对问题进行求解的时候,总是做出当前看来是最好的选择。也就是说不从整体上最优上考虑,算法得到的结果是某种意义上的局部最优解
記事ディレクトリ
導入
貪欲アルゴリズムで解決できる問題には次のような特徴があります。
-
1.贪心选择的性质:一个问题的整体最优解可以通过一系列局部的最优解的选择达到。并且每一次的选择可以依赖于之前做出的选择,但是不依赖后面做出的选择。这就是贪心选择性质。对于一个具体的问题,要确定他是否具有贪心选择的性质,必须证明每一步所作的贪心选择最终导致问题的整体的最优解
-
2.最优子结构性质:当一个恩问题的最优解包含其子问题的最优解的时候,此问题具有最优子结构性质。问题的最优子结构性质是该问题可用贪心法的求解所在。
使用手順
貪欲アルゴリズムを使用する基本的な手順は次のとおりです。
1> 問題を説明するための数学的モデルを確立します。
2> 解決すべき問題をいくつかの下位問題に分割します。
3> 各部分問題を解決して、部分問題の局所最適解を取得します。
4> 部分問題の局所最適解を元の問題の解に結合します。
实际上在使用贪心算法的时候,待选
テンプレート
while(约束条件成立)
{
选择当前最优解,记录并累计到最后的解中
if(当前最优解使用完毕)
当前最优解=当前次优解;
}
貪欲なアルゴリズムの欠点
以下の不具合
1.不能保证解是最佳的。因为贪心算法得到的是局部最优解,不是从整体开了整体最优解。
2.贪心算法只能确定某些问题的可行性范围
欠点1と欠点2は、貪欲なアルゴリズムには限界があることを意味しており、例えば、硬貨を両替する場合、8元なら2元と5元があり、1枚5元、1枚2元、1元となる。左です、それでは計算できません、実際には2元が4枚使えます、これは一例です
3.贪心算法一般用来解决最大或者最小解
欠点は、貪欲アルゴリズムがすべての解を走査するのではなく、特定の戦略を使用して値を選択することです。特定の問題のすべての解を走査するには、バックトラッキング手法がよく使用されます。
古典的な例
1. アクティビティ選択問題
2.コイン両替問題
3. ナップザック問題の再考
4. 川を渡る船の問題
5. インターバルカバレッジの問題
よくある例
アクティビティ選択の質問
この問題は通常、貪欲アルゴリズムと動的計画法で解決できますが、以下では貪欲アルゴリズムを使用して解決します。
質問の形式は、特定の場所で複数のアクティビティを開催し、各アクティビティにかかる時間が異なる (開始時間と終了時間が異なる) 場合、できるだけ多くのアクティビティをどのように手配するかというものです。この問題が貪欲アルゴリズムで解決できる理由は、次のアクティビティの選択は前のアクティビティの期限にのみ依存し、全体の状況を考慮する必要がないためです。
活動 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |
---|---|---|---|---|---|---|---|---|---|---|---|
開始時間 | 1 | 3 | 0 | 5 | 3 | 5 | 6 | 8 | 8 | 2 | 12 |
終了時間 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
この問題を解決するときは、終了時刻を並べ替える必要があります。前のアクティビティの終了時刻は次のアクティビティの選択に影響しますが、現在のアクティビティの開始時刻は次のアクティビティの選択に影響しないためです。終了時刻を見てください。前のアクティビティの終了時刻を使用して、次のアクティビティの開始時刻と比較します。前者が後者より小さい場合、後者が次のアクティビティになります。そうでない場合は、比較を続けます。次のアクティビティの開始時間
問題解決のアイデア
将所有活动按照结束时间进行排序
然后默认选取第一个活动,用变量endTime标注当前活动的结束时间,然后与后面活动的开始时间进行比较
如果前者结束时间小于等于后者活动开始时间,那么选择后者这个活动,反之,继续比较下下一个活动,再次进行判断
重复进行第三步,直到所有活动比较完成
import java.util.*;
/**
* 活动选择:
* 大概形式为:在某一个地方,有多个活动要进行,活动有开始时间和结束时间,我们该如何选择才能在这一天中这一个地点,举办最多的活动
* 求解最多的活动个数为?
*/
class Play{
int preTime;
int endTime;//表示活动的开始时间和结束时间
}
public class 活动选择 {
public static void main(String[] args) {
Scanner scanner=new Scanner(System.in);
//我们可以用容器来存储各个活动,然后进行贪心算法,最后得到结果
//我们可以用链表来进行存储,排序
LinkedList<Play> linkedList=new LinkedList();
int n=scanner.nextInt();//表示活动的个数
for (int i = 0; i <n ; i++) {
//提案写每一个活动的开始时间和结束时间
Play play=new Play();
play.preTime=scanner.nextInt();
play.endTime=scanner.nextInt();
linkedList.add(play);//存储链表中,然后进行对于结束时间排序
}
Collections.sort(linkedList, new Comparator<Play>() {
@Override
public int compare(Play o1, Play o2) {
return o1.endTime-o2.endTime;
}
});
//排序完成
//然后进行判别,从第一个活动开始
int num=0;//计数
Play Time=linkedList.get(0);
for (int i = 0; i <linkedList.size() ; i++) {
if(Time.endTime<=linkedList.get(i).preTime){
Time=linkedList.get(i);
num++;
}
}
System.out.println(num);//得到
}
}
コイン両替問題
問題の一般的な説明は次のとおりです。それぞれ、1 元、2 元、5 元、10 元、20 元、50 元、100 元の紙幣 a、b、c、d、e、f があるとします。さて、これらのお金を K 元の支払いに使用するには、少なくとも何枚の紙幣を使用する必要がありますか? この問題は前の問題と似ています。この問題を解決するには、通常 2 つの配列があり、1 つは金種のサイズを表し、もう 1 つは異なる金種のコインの数を表し、金種を表す配列は順序付けされています。
最小数のコインを求めているため、貪欲なアルゴリズムを使用できます。
1.理所当然先使用大额纸币,当大额纸币张数不够的时候,再使用后面较小面额的纸币,依次递减,直到表示完所有纸币
2.做题方法可以递归也可以迭代
import java.util.*;
public class 钱币找零问题 {
public static void main(String[] args) {
//题目:指定币值和相应的数量,用最少的数量去凑齐某金额
//思路:利用贪心算法,我们优先选择面值大的纸币,依次类推,直到凑齐总金额
Scanner scanner=new Scanner(System.in);
int num=scanner.nextInt();//输入总金额
greedy(num);
}
public static void greedy(int num){
int[] values = {
1, 2, 5, 10, 20, 50, 100 };
//数量
int[] counts = {
3, 3, 2, 1, 1, 3, 3 };
//获取需要各种面值多少张
int[] result = getNumber(num, values, counts);
System.out.println("各币值的数量:"+Arrays.toString(result));
}
public static int[] getNumber(int sum,int []values,int[]counts){
int []result=new int[7];//表示有七种钱币
//我们要找的是每一种钱币所需的票数
int add=0;//表示需要的总钱数
for (int i = values.length-1; i >=0 ; i--) {
int num=(sum-add)/values[i];
if(num>counts[i]){
num=counts[i];
}
//add加上数值
add=add+num*values[i];
result[i]=num;
}
return result;
}
//或者这样迭代
public static int[] get(int sum,int []values,int[]counts){
int []result=new int[values.length];
for (int i = values.length-1; i >=0 ; i++) {
int num=Math.min(counts[i],sum/values[i]);
result[i]=num;
sum=sum-num*values[i];
}
return result;
}
}
ナップサック問題
Not 01 バックパック
常见题型为给定n种物品,一个背包,背包的容量是c,二米一个物品i的价值为vi,重量为wi,如何选择装入的物品使得背包的总价值最大?
ここでいうナップザック問題とはナップザックの一部を指しており、01のナップザック問題のように分解できないものではなく、分解して一部をナップザックに入れることは可能です。貪欲なアルゴリズムの観点から、私たちが保証するのは、バックパックの合計価値が最大になること、つまり、入れられた各アイテムの個別の価値が可能な限り大きくなるようにすることです。(分解可能)
次のように進めます
1.需要将物品按照单位重量价值进行排序。
2.将尽可能多的单位i重量价值最高的物品装入被阿波,若最大单位重量价值的物品全部装入背包后,背包华友多余容量,则选择单位重量价值次高的尽可能多的装入背包中。
3.如果最后一件物品无法装入,那就计算可以装入的比例,然后按照比例装入
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedList;
import java.util.Scanner;
/**
* 贪心的背包 ,有n个物品,一个背包,然后想使得这个背包的价值最大
* 不是01背包不可拆分物品,这个是可以拆分的
*/
class beiBao{
int values;
int weight;//价值和重量
double wValues;//单个物品的单位重量的价值
}
public class 背包 {
public static void main(String[] args) {
Scanner scanner=new Scanner(System.in);
int n=scanner.nextInt();
int weight=scanner.nextInt();//背包能承受的重量
LinkedList<beiBao> linkedList=new LinkedList();
for (int i = 0; i <n ; i++) {
beiBao b=new beiBao();
b.values=scanner.nextInt();
b.weight=scanner.nextInt();
b.wValues=b.values/b.weight;
linkedList.add(b);
}
Collections.sort(linkedList, new Comparator<beiBao>() {
@Override
public int compare(beiBao o1, beiBao o2) {
return (int)(o2.wValues-o1.wValues);
}
});
for (int i = 0; i <n ; i++) {
System.out.println(linkedList.get(i).wValues);
}
double allvalues=0;
boolean[] flag=new boolean[n];
for (int i = 0; i < flag.length; i++) {
flag[i]=false;//表示没有放入背包中
}
for (int i = 0; i <n ; i++) {
if(linkedList.get(i).weight<=weight){
flag[i]=true;
weight=weight-linkedList.get(i).weight;
allvalues+=linkedList.get(i).values;
System.out.println("重量为:"+linkedList.get(i).weight+"价格为:"+linkedList.get(i).values+"可以完全装入");
}
}
for (int i = 0; i <n ; i++) {
if(!flag[i]){
double rate=(double)weight/linkedList.get(i).weight;
allvalues=allvalues+rate*linkedList.get(i).values;
weight-=linkedList.get(i).weight;
System.out.println("重量为:"+linkedList.get(i).weight+"价值为:"+linkedList.get(i).values+"装入比例为:"+rate);
}
}
System.out.println("总价值为:"+allvalues);
}
}
ボート横断問題
この問題の一般的な説明は次のとおりです: 川を渡る必要がある人は n 人ですが、最大 2 人を乗せることができるボートは 1 隻しかありません。糸の速度は 2 人のうち遅い方の速度です。 n 人を向こう側に運ぶには、少なくともどれくらい時間がかかりますか。
これらの人々が費やした時間が昇順、つまり早い人から遅い人の順に並べられた配列に格納されていると仮定すると、各人が費やした時間は time[0]、time[1]...time[n] となります。
人数が 4 人以上の場合、この問題には 2 つの選択肢があります:
1. 最も速い人と 2 番目に速い人が最初に川を渡り、次に最も速い人がボートを漕いで戻ります; 2 番目に遅い人と最も遅い人が川を渡り、次にボートを漕ぎます。 2 番目に速い 戻る、この時点で次のプロセスにかかる時間を計算します
アイコン
2 番目の方法:
1. 川を渡るのが最も速い者と最も遅い者、次にボートを漕いで戻るのが最も速い者、川を渡るのが最も速い者と二番目に遅い者、そして戻ってくるのが最も速い者
アイコン
上記の川を渡る方法は2通りありますので、利用する際はどちらの方が時間が短いかを比較してから利用すると良いでしょう。
人数が4名未満の場合
1. 人数が3人の場合、このときの固定時間はtime[0]+time[1]+time[2]となります。
2. 人数が2人の場合、このときの固定時間は時間[1]です。
3. 人数が1人の場合、固定時間はtime[0]
/*
* 有n个人需要过河,只有一艘船,最多能乘2人,船的运行速度为2人中较慢一人的速度,
* 过去后还需一个人把船划回来,把n个人运到对岸,最少需要多久。
*/
public class River {
public static void main(String[] args) {
int[] times={
1,2,4,5,8};
int result=crossRiver(times);
System.out.println("所花时间为:"+result);
}
private static int crossRiver(int[] times){
/*n表示还未过河的人数,初始化为所有人*/
int n=times.length;
int result=0;
while(n>0){
if(n==1){
result=result+times[0];
break;
}else if(n==2){
result=result+times[0]+times[1];
break;
}else if(n==3){
result=result+times[0]+times[1]+times[2];
break;
}else{
/*在每次过河时,在两种方式上进行比较,选择耗时更少的那个*/
result=result+Math.min(times[1]+times[0]+times[n-1]+times[1],times[n-1]+times[0]+times[n-2]+times[0]);
/*无论采取哪种方式,最后的结果都是讲最慢的和次慢的运送过河,也就是数组的最后两位,所以此处可简单地将数组长度-2*/
n=n-2;
}
}
return result;
}
}
エリアカバー範囲
この問題は、長さが m の区間、n 個の線分の始点と終点 (閉じた区間) が与えられたとき、区間全体を完全にカバーできる最小の線分の数を見つけます。手順は次のとおりです。 :
- 選択されるすべての区間のうち、始点と終点が必要な範囲外にある区間は除外されます。
- すべての間隔を原点ごとに並べ替えます
- 最初のポイントがデフォルトで選択されます。その後、ポイントを選択するプロセスでは、次の原則に従う必要があります。新しい間隔の開始点は、現在の間隔の終了点よりも小さい必要があり、現在の間隔の終了点は、新しい間隔は、現在の間隔の開始点より大きくなければなりません
- 現在の間隔の終了点値 >= 予想される終了点値になるまでステップ 3 をループで繰り返し、間隔を見つけるプロセスを終了します。
import java.util.*;
class quJian{
int pre;
int end;//表示区间的起点和终点
}
public class 区间覆盖 {
public static void main(String[] args) {
Scanner scanner=new Scanner(System.in);
//加入区间 10个
int n=scanner.nextInt();
int m=scanner.nextInt();
//假设所求区间范围为[n,m];
LinkedList <quJian>linkedList=new LinkedList();
for (int i = 0; i <7 ; i++) {
quJian qj=new quJian();
qj.pre=scanner.nextInt();
qj.end=scanner.nextInt(); //1.删除起点和终点在所要求范围外的区间
if(!(qj.pre<n&&qj.end<n||qj.pre>m&&qj.end>m)){
linkedList.add(qj);
}
}
//先进行排序
Collections.sort(linkedList, new Comparator<quJian>() {
@Override
public int compare(quJian o1, quJian o2) {
return o1.pre-o2.pre;//按照小区间的起点排下序,升序
}
});
//然后进行选取
boolean flag=false;
int count=1;//计数
System.out.println("起点:"+linkedList.get(0).pre+","+linkedList.get(0).end);
int end=linkedList.get(0).end;
for (int i = 1; i <linkedList.size() ; i++) {
if(linkedList.get(i).pre<=end&&linkedList.get(i).end>end){
end=linkedList.get(i).end;//更新end点
for (int j = i+1; j <linkedList.size() ; j++) {
//然后从这后面找到最长的end
if(linkedList.get(j).end>end){
end=linkedList.get(j).end;
count++;//找到一个区间
System.out.println(linkedList.get(j).pre+" "+linkedList.get(j).end);
if(end>=m){
flag=true;
}
}
}
}
if(flag){
break;
}
}
System.out.println(count);
}
}