簡體   English   中英

Java中的作業調度算法

[英]Job Scheduling Algorithm in Java

我需要為調度問題設計一個有效的算法,我真的沒有線索。

有一台機器以一定的速度生產葯丸。 例如,如果允許連續工作一天,機器可能能夠生產1丸,如果允許連續工作3天,則可以生產4丸,如果它可以工作5天,則可以生產16丸,等等。 如果我停止機器並取出所有葯丸,那么機器將從第1天開始。 我從機器中取出的所有葯丸必須在同一天使用。

有一定數量的患者每天來吃葯。 患者必須在同一天進行治療,未經治療的患者將被忽略。 目標是確定停止機器的天數並在n天內盡可能多地治療患者。

假設示例輸入的天數n = 5

int[] machineRate = {1,2,4,8,16};
int[] patients =    {1,2,3,0,2};

在這種情況下,如果我在第3天停止機器,我會吃4粒葯。 我可以治療3名患者並丟棄1丸。 然后我再次在第5天停止機器,因為它在第3天停止,它已經工作了2天,因此我有2個葯片治療2名患者。 最終3 + 2 = 5,輸出= 5名患者。

如果我在第2天,第3天,第5天停止機器。那么輸出將是(第2天2名患者2丸)+(第3天3名患者1丸)+(2名患者每天2粒) 5)。 這相當於5名患者。

machineRate[]patients[]根據輸入而變化。

找到最大治療患者數的算法是什么?

這是一個很好的動態編程問題。

思考動態編程的方法是問自己兩個問題:

  1. 如果我將一個(或多個)變量減少到零(或類似),是否存在這個問題的簡單版本?
  2. 如果我知道大小為n所有問題的答案,有沒有一種簡單的方法來計算大小為n+1的問題的答案? (這里,“大小”是特定於問題的,您需要找到有助於解決問題的大小的正確概念。)

對於這個問題,什么是一個簡單的版本? 好吧,假設天數為1.那么這很容易:我停下機器,盡可能多地對待病人。 做其他事情毫無意義。

現在,如果我們考慮剩下的天數作為我們的大小概念,我們也會得到第二個問題的答案。 假設我們知道剩下n天的所有問題的所有答案。 讓我們寫maxTreat(days, running)的,如果有,我們可以把最大數量days左天,如果機器最初已運行running天。

現在剩下n+1天; 這台機器到目前為止已運行了k天。 我們有兩個選擇:(1)停止機器; (2)不要停止它。 如果我們停止機器,我們今天可以治療一些患者(我們可以根據k出數字),然后我們可以治療maxTreat(n, 1)患者,因為剩下n天,明天就是機器將會再運行一天。 如果我們不停止機器,我們今天不能治療任何人,但此后我們將能夠治療maxTreat(n,k+1)患者,因為明天機器將運行k+1天。

我將讓您了解精確的細節,但為了有效地解決它,我們根據剩余的天數和機器運行到目前為止的天數創建一個多維數組。 然后我們遍歷數組,解決所有可能的問題,從平凡(左一天)開始,向后工作(兩天,然后三天,等等)。 在每個階段,我們要解決的問題要么是微不足道的(所以我們可以寫出答案),或者我們可以從上一步寫入數組的條目中計算出來的東西。

關於動態編程的一個非常酷的事情是我們正在創建所有結果的緩存。 因此,對於遞歸方法最終需要多次計算子問題的答案的問題,通過動態編程,我們永遠不會不止一次地解決子問題。

現在我已經看到你的實現的其他評論:

首先,當你達到10,000左右時,我開始減速並不會太驚訝。 該算法為O(n^2) ,因為在每次迭代時,您必須用最多n個條目填充數組,然后才能移動到下一個級別。 我很確定O(n^2)是你將要獲得的最佳漸近復雜度。

如果你想進一步加快速度,你可以看一下自上而下的方法。 目前,您正在進行自下而上的動態編程:解決大小為0,然后是大小為1的情況,依此類推。 但你也可以反過來做。 本質上,這是一個相同的算法,就像你編寫一個非常低效的遞歸解決方案一樣,計算動態子問題的解決方案,除了你每次計算它時都緩存一個結果。 所以它看起來像這樣:

  1. 設置二維數組以保存子問題的解決方案。 每種情況下預填充-1。 值-1表示您尚未解決該子問題。
  2. 編寫一個例程,根據下一級子問題的答案解決maxTreat(days, running)問題。 如果需要子問題的答案,請查看數組。 如果那里有一個-1,你還沒有解決那個,所以你遞歸地解決它,然后把答案放到數組中。 如果除了-1以外的任何東西,你可以使用你在那里找到的值,因為你已經計算過了。 (您也可以使用HashMap而不是多維數組。)

這在某種程度上更好,在另一種情況下更糟。 它更糟糕,因為你有與遞歸相關的開銷,並且因為你最終會用遞歸調用耗盡堆棧。 您可能需要使用命令行參數將堆棧大小提升到JVM。

但是在一個關鍵方面它更好:你不計算所有子問題的答案,而只計算你需要知道答案的答案。 對於某些問題,這是一個巨大的差異,對某些人來說,事實並非如此。 獲得正確的直覺很難,但我認為這可能會產生很大的不同。 畢竟,每個答案僅取決於前一行中的兩個子問題。

最終的解決方案(不要嘗試這個,直到你自上而下的遞歸遞歸!)是自上而下但沒有遞歸。 這樣可以避免堆棧空間問題。 為此,您需要創建一堆需要求解的子問題(使用ArrayDeque ),然后將它們從隊列的前面移開,直到沒有剩余。 第一件事就是將需要解決方案的大問題推到堆棧上。 現在,您迭代地從堆棧中彈出問題,直到它為空。 彈出一個,稱之為P 然后:

  1. 查看數組或HashMap以查看P是否已解決。 如果是,請返回答案。
  2. 如果沒有,請查看P的子問題是否已經解決。 如果有,則可以解決P ,並緩存答案。 如果堆棧現在為空,那么你已經解決了最后的問題,並輸出了P的答案。
  3. 如果子問題尚未全部解決,則將P推回堆棧。 然后將P尚未解決的任何子問題也推送到堆棧中。

當您將主要問題及其子問題及其子問題推送到堆棧時,您的堆棧將最初增長。 然后,您將開始解決較小的實例並將結果放入緩存中,直到最終您擁有解決主要問題所需的一切。

它不會使用比遞歸自頂向下方法少得多的內存,但它確實使用堆空間而不是JVM堆棧空間,這意味着它可以更好地擴展,因為JVM堆棧比堆小得多。

但這很困難。 至少,在開始編寫更難的版本之前,請保留您的工作解決方案!

另一種方法是預測第二天或幾天。 假設我們在最后幾天看過1,2.patients,我們今天可以服用兩片葯,治愈兩個病人或預測第二天三個或更多,並讓機器運行。 如果我們沒有像1,1那樣的加薪,我們預測明天會有一名病人,今天就服用一粒。 如果第二天變得像1,4,8那樣不同,我們只是將第二天的預測調整為1/2,即2.這個解決方案的好處是你可以處理不確定性,即你不知道是什么明天會來。 這允許我們流式傳輸數據。 不利的是,第一位患者將永遠死亡。

我已經實現了chiastic-security的設計,但是當n大於10000左右時性能不是很好。 如果有人有任何其他想法,請告訴我,因為我認為這是一個非常有趣的問題。 我一開始嘗試使用遞歸但是內存不足,所以我不得不在循環中完成它。 到目前為止,我正在存儲一個帶有所有結果的大二維數組,但后來我意識到我只需要訪問前面的“行”結果,所以我只使用了2個數組:“current”和“previous”:

static int calculateMax() {
    int[] previous = new int[n];
    for (int daysMachineRunning=0; daysMachineRunning<n; daysMachineRunning++) {
        previous[daysMachineRunning] = treatPatients(0, daysMachineRunning);
    }

    int[] current = null;
    for (int daysRemaining=1; daysRemaining<n; daysRemaining++) {
        current = new int[n-daysRemaining];
        for (int daysMachineRunning=0; daysMachineRunning<n-daysRemaining; daysMachineRunning++) {
            current[daysMachineRunning] = Math.max(
                    treatPatients(daysRemaining, daysMachineRunning) + previous[0],
                    previous[daysMachineRunning+1]
                );
        }
        previous = current;
    }
    return current[0];
}

static int treatPatients(int daysRemaining, int daysMachineRunning) {
    return Math.min(patients[n-1-daysRemaining], machineRate[daysMachineRunning]);
}

編輯:我現在已經實現了第二種方法,但仍然遇到n> = 10000左右的問題: Exception in thread "main" java.lang.OutOfMemoryError: Java heap space 這是我的代碼,如果有人有興趣進一步追求:

static final int[][] results = new int[n][n];
static final SortedSet<Target> queue = new TreeSet<>(new Comparator<Target>() {
    @Override
    public int compare(Target o1, Target o2) {
        if (o1.daysRemaining < o2.daysRemaining)
            return -1;
        else if (o1.daysRemaining > o2.daysRemaining)
            return 1;
        else if (o1.daysMachineRunning < o2.daysMachineRunning)
            return 1;
        else if (o1.daysMachineRunning > o2.daysMachineRunning)
            return -1;
        else return 0;
    }
});

public static void main(String[] args) {
    for (int i=0; i<n; i++) {
        Arrays.fill(results[i], -1);
    }

    if (n <= 10) {
        System.out.println(Arrays.toString(machineRate));
        System.out.println(Arrays.toString(patients));
    } else
        System.out.println(n);

    System.out.println(calculateMax());
}

static class Target {
    int daysRemaining, daysMachineRunning;
    Target(int daysRemaining, int daysMachineRunning) {
        this.daysRemaining = daysRemaining;
        this.daysMachineRunning = daysMachineRunning;
    }
}

static int calculateMax() {
    addTarget(n-1, 0);
    while (results[n-1][0]==-1) {
        Target t = queue.first();
        queue.remove(t);
        calculateMax(t);
    }
    return results[n-1][0];
}

static void calculateMax(Target t) {
    int daysRemaining = t.daysRemaining;
    int daysMachineRunning = t.daysMachineRunning;
    int treatedPatients = Math.min(patients[n-1-daysRemaining], machineRate[daysMachineRunning]);
    if (daysRemaining==0)
        results[0][daysMachineRunning] = treatedPatients;
    else {
        int resultA = results[daysRemaining-1][0];
        int resultB = results[daysRemaining-1][daysMachineRunning+1];
        if (resultA>=0 && resultB>=0) {
            results[daysRemaining][daysMachineRunning] = Math.max(treatedPatients + resultA, resultB);
        }
        else {
            if (resultA==-1)
                addTarget(daysRemaining-1, 0);
            if (resultB==-1)
                addTarget(daysRemaining-1, daysMachineRunning+1);
            addTarget(daysRemaining, daysMachineRunning);
        }
    }
}

static void addTarget(int a, int b) {
    queue.add(new Target(a,b));
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM