算法基礎(Java)--貪心算法

前言

前面簡單的介紹了八大經典排序算法,此文將要介紹貪心算法,並介紹一些常見貪心算法題目。html

1. 貪心算法的概念

所謂貪心算法是指,在對問題求解時,老是作出在當前看來是最好的選擇。也就是說,不從總體最優上加以考慮,他所作出的僅是在某種意義上的局部最優解。 貪心算法沒有固定的算法框架,算法設計的關鍵是貪心策略的選擇。必須注意的是,貪心算法不是對全部問題都能獲得總體最優解,選擇的貪心策略必須具有無後效性,即某個狀態之後的過程不會影響之前的狀態,只與當前狀態有關。 因此對所採用的貪心策略必定要仔細分析其是否知足無後效性java

2. 基本思路

  1. 創建數學模型來描述問題。
  2. 把求解的問題分紅若干個子問題。
  3. 對每一子問題求解,獲得子問題的局部最優解。
  4. 把子問題的解局部最優解合成原來解問題的一個解。

3. 適用的問題

貪心策略適用的前提是:局部最優策略能致使產生全局最優解。也就是當算法終止的時候,局部最優等於全局最優。算法

由於用貪心算法只能經過解局部最優解的策略來達到全局最優解,所以,必定要注意判斷問題是否適合採用貪心算法策略,找到的解是否必定是問題的最優解。 若是肯定可使用貪心算法,那必定要選擇合適的貪心策略;數組

4. 實例講解

4.1 揹包問題

問題: 有一個揹包,揹包容量是M=150。有7個物品,物品能夠分割成任意大小。要求儘量讓裝入揹包中的物品總價值最大,但不能超過總容量。bash

物品 A B C D E F G
重量 35 30 60 50 40 10 25
價值 10 40 30 50 35 40 30

分析:數據結構

目標函數: ∑pi最大

約束條件是裝入的物品總重量不超過揹包容量:∑wi<=M( M=150)

(1)根據貪心的策略,每次挑選價值最大的物品裝入揹包,獲得的結果是否最優?

(2)每次挑選所佔重量最小的物品裝入是否能獲得最優解?

(3)每次選取單位重量價值最大的物品,成爲解本題的策略。
複製代碼

通常來講,貪心算法的證實圍繞着:整個問題的最優解必定由在貪心策略中存在的子問題的最優解得來的。框架

對於例題中的3種貪心策略,都是沒法成立(沒法被證實)的,解釋以下ide

(1)貪心策略:選取價值最大者。

反例:

W=30

物品:A B C

重量:28 12 12

價值:30 20 20

根據策略,首先選取物品A,接下來就沒法再選取了,但是,選取B、C則更好。

(2)貪心策略:選取重量最小。它的反例與第一種策略的反例差很少。

(3)貪心策略:選取單位重量價值最大的物品。反例:

W=30

物品:A B C

重量:28 20 10

價值:28 20 10
複製代碼

動態規劃中將會學習三種最基本的揹包問題:零一揹包,部分揹包,徹底揹包。上面已經證實,揹包問題不能使用貪心算法。函數

不能解決爲何還要引用揹包問題來說解貪心算法呢?學習

爲了加深對貪心算法的理解: 整個問題的最優解必定由在貪心策略中存在的子問題的最優解得來的。

4.2 錢幣找零問題

這個問題在咱們的平常生活中就更加廣泛了。 用貪心算法的思想,很顯然,每一步儘量用面值大的紙幣便可。

假設紙幣金額爲1元、5元、10元、20元、50元、100元,123元應該儘量兌換少的紙幣。 按嘗試應該兌換1張100、1張20元和3張1元的。

算法思路很簡單,只須要儘量從最大的面值往下一直減便可。

static void splitChange(int money) {
    int[] prices = {100, 50, 20, 10, 5, 1};
    int[] notes = new int[prices.length];
    int change = money;
    if (money > 0) {
        while (change > 0) {
            for (int i = 0; i < prices.length; i++) {
                int count = 0;
                for (int k = 0; change - prices[i] >= 0; k++) {
                    if (change - prices[i] >= 0) {
                        change = change - prices[i];
                        count++;
                    } else break;
                }
                notes[i] = count;
            }
        }
    }
    System.out.println("找零:");
    for (int num = 0; num < prices.length; num++) {
        System.out.print(notes[num] + "張" + prices[num] + "元 ");
    }
}
複製代碼

4.3 多機調度問題

問題描述: 設有n個獨立的做業{1, 2, …, n}, 由m臺相同的機器進行加工處理. 做業i所需時間爲ti。約定:任何做業能夠在任何一臺機器上加工處理, 但未完工前不容許中斷處理,任何做業不能拆分紅更小的子做業。要求給出一種做業調度方案,使所給的 n 個做業在儘量短的時間內由 m 臺機器加工處理完成。 多機調度問題是一個 NP 徹底問題,到目前爲止尚未徹底有效的解法。對於這類問題,用貪心選擇策略有時能夠設計出一個比較好的近似算法。

貪心算法求解思路

採用最長處理時間做業優先的貪心策略: 當n≤m時, 只要將機器i的[0, ti]時間區間分配給做業i便可。 當n>m時, 將n個做業依其所需的處理時間從大到小排序,而後依次將做業分配給空閒的處理機。

/** * @Description: 多機調度問題 * @Date: 15:49 2019/8/10 * @Param: [a, m] * @return: int */
public static int greedy(int[] a, int m) {
    //int n = a.length - 1;//a的下標從1開始,因此n(做業的數目)=a.length-1
    int n = a.length;
    int sum = 0;
    if (n <= m) {
        for (int i = 0; i < n; i++)
            sum += a[i + 1];
        System.out.println("爲每一個做業分別分配一臺機器");
        return sum;
    }
    List<JobNode> d = new ArrayList<>();//d保存全部的做業
    for (int i = 0; i < n; i++) {//將全部的做業存入List中,每一項包含標號和時間
        JobNode jb = new JobNode(i + 1, a[i]);
        d.add(jb);
    }
    Collections.sort(d);//對做業的List進行排序
    LinkedList<MachineNode> h = new LinkedList<>();//h保存全部的機器
    for (int i = 0; i <m; i++) {//將全部的機器存入LinkedList中
        MachineNode x = new MachineNode(i+1, 0);//初始時,每臺機器的空閒時間(完成上一個做業的時間)都爲0
        h.add(x);
    }

    for (int i = 0; i < n; i++) {
        Collections.sort(h);
        MachineNode x = h.peek();
        System.out.println("將機器" + x.id + "從" + x.avail + "到" + (x.avail + d.get(i).time) + "的時間段分配給做業" + d.get(i).id);
        x.avail += d.get(i).time;
        sum = x.avail;
    }
    return sum;
}


public static class JobNode implements Comparable {
    int id;//做業的標號
    int time;//做業時間

    public JobNode(int id, int time) {
        this.id = id;
        this.time = time;
    }

    @Override
    public int compareTo(Object x) {//按時間從大到小排列
        int times = ((JobNode) x).time;
        return Integer.compare(times, time);
    }
}

public static class MachineNode implements Comparable {
    int id;//機器的標號
    int avail;//機器空閒的時間(即機器作完某一項工做的時間)

    public MachineNode(int id, int avail) {
        this.id = id;
        this.avail = avail;
    }

    @Override
    public int compareTo(Object o) {//升序排序,LinkedList的first爲最小的
        int xs = ((MachineNode) o).avail;
        return Integer.compare(avail, xs);
    }
}
複製代碼

4.4 【leetcode】630-課程安排III

問題描述: 有n門課,號碼從1到n。每門課有對應的時長t以及截至日期d。選擇了一門課,就得持續t天而且在d以前完成這門課。從第一天開始。 給定n門課程,以(t, d)對錶示,你須要找出你能夠參加的最多的課程數。 須要注意,你不能同時上兩門課。

問題分析:

貪心法 先對數組以d排序,在遍歷數組的過程當中維護當前日期,若課程可選,那麼增長當前日期,若不可選,從已經選過的課程中找出一個耗時最長的課程,若該課程耗時比當前課程長,那麼替換。重複此過程

class Solution {
    public int scheduleCourse(int[][] courses) {
        Arrays.sort(courses, new Comparator<int[]>(){
            @Override
            public int compare(int[] a, int[] b){
                return a[1] - b[1];
            }
        });

        int count = 0, curtime = 0;
        for(int i = 0;i < courses.length;i++){
            //若可選,增長當前時間,而且將當前課程放入courses中
            //不然,從courses中選一個耗時最長的課程,若這個耗時最長的課程比當前課程還長,則替換
            if(curtime + courses[i][0] <= courses[i][1]){
                courses[count++] = courses[i];
                curtime += courses[i][0];
            }else{
                int max_i = i;
                for(int j = count - 1;j >= 0;j--){
                    if(courses[j][0] > courses[max_i][0])   max_i = j;
                }
                if(courses[max_i][0] > courses[i][0]){
                    curtime += courses[i][0] - courses[max_i][0];
                    courses[max_i] = courses[i];
                }
            }
        }

        return count;
    }
}
複製代碼

4.5 測試代碼

public class greedyProgramTest {
    public static void main(String[] args) {
        //找零問題
        int money = 123;
        greedyProgram.splitChange(money);

        System.out.println();
        System.out.println("-------");

        //多機調度問題
        int[] a = {5,4,2,14,16,6,5,3};
        int m = 3;
        System.out.println("總時間爲:"+greedyProgram.greedy(a,m));

        System.out.println("-------");

        //課程表
        int[][] course = {{2,5},{2,19},{1,8},{1,3}};
        System.out.println(Solution.scheduleCourse(course));
    }
}
複製代碼

運行結果:

找零:
1張100元  0張50元  1張20元  0張10元  0張5元  3張1元  
-------
將機器1從0到16的時間段分配給做業5
將機器2從0到14的時間段分配給做業4
將機器3從0到6的時間段分配給做業6
將機器3從6到11的時間段分配給做業1
將機器3從11到16的時間段分配給做業7
將機器2從14到18的時間段分配給做業2
將機器3從16到19的時間段分配給做業8
將機器1從16到18的時間段分配給做業3
總時間爲:18
-------
課程數:4
複製代碼

5. 小結&參考資料

小結

關於貪心算法的實例不少,這就不一一列舉了,主要就是理解貪心算法的主要思想,經過局部最優解獲得整體最優解的問題。貪心算法是很高效的算法之一,只要能簡化出模型就能利用貪心算法來解決問題。

參考資料

相關文章
相關標籤/搜索