Finden Sie nicht überlappende geplante Jobs mit maximalen Kosten

8

Bei einer Menge von n Jobs mit [Startzeit, Endzeit, Kosten] finden Sie eine Teilmenge, sodass sich keine 2 Jobs überschneiden und die Kosten maximal sind.

Jetzt bin ich mir nicht sicher, ob ein gieriger Algorithmus den Trick macht. Das heißt, nach Kosten sortieren und immer den nächsten Job annehmen, der sich nicht überschneidet, und mit maximalen Kosten zwischen den beiden.

Entspricht dies einem Rucksackproblem? Wie könnte ich es angehen?

user1531186
quelle

Antworten:

8

Das Diagramm überlappender Jobs ist ein Intervalldiagramm . Intervalldiagramme sind perfekte Diagramme. Sie versuchen also, eine maximale gewichtsunabhängige Menge (dh keine zwei Überlappungen) in einem perfekten Diagramm zu finden . Dies kann in Polynomzeit gelöst werden. Der Algorithmus wird in "Polynomalgorithmen für perfekte Graphen" von M. Grötschel, L. Lovász und A. Schrijver angegeben.

Es gibt eine Reihe von Sonderfällen perfekter Graphen, für die Benutzer einfachere und effizientere Algorithmen für diese Aufgabe entdeckt haben. Ich weiß nicht, ob Intervallgraphen in einen dieser Sonderfälle fallen.

Peter Shor
quelle
6

Gieriger Algorithmus kann in diesem Fall nicht helfen. Und es konnte nicht mit Bruch- oder 0-1-Rucksackproblemen verglichen werden. Der erste könnte durch einen gierigen Algorithmus in O (n) aufgelöst werden und der zweite ist NP.

Das Problem, das Sie haben, könnte in O (2 ^ n) brutal erzwungen werden. Sie können es jedoch mithilfe der dynamischen Programmierung optimieren.

1) Sortieren Sie die Intervalle nach Startzeit.

2) Initialisiere int [] cost = new int [jobs.length] mit Integer.MIN_VALUE (oder einem beliebigen negativen Wert);

3) Definieren Sie die folgende rekursive Routine (hier ist Java):

private int findCost(Job[] jobs, int k, int[] costs) {
   if(k >= jobs.length) {
      return 0;
   }
   if(costs[k] < 0) {
     int x = findNextCompatibleJob(jobs, k);
     int sumK = jobs[k].cost + findCost(jobs, x, costs);
     int sumK1 = findCost(jobs, k + 1, costs);
     costs[k] = Math.max(sumK, sumK1);
   }
   return costs[k];
}

private int findNextCompatibleJob(Job[] jobs, int k) {
   int finish = jobs[k].finish;
   for(int i = k + 1; i < jobs.length; i++) {
     if(jobs[i].start > finish) {
        return i;
     }
   }
   return Integer.MAX_VALUE;
}

4) Rekursion mit k = 0 starten;

Ich habe nur eine Rekursionsroutine implementiert, während andere Teile trivial sind. Ich habe angenommen, dass alle Kosten> = 0 sind. Wenn es Jobs mit negativen Kosten geben könnte, müssen wir dies überprüfen und diese Jobs einfach ohne Berücksichtigung bestehen.

Maxime
quelle
5

Man könnte dies in O (nlogn) implementieren

Schritte:

  1. Sortieren Sie die Intervalle nach der Endzeit
  2. Definieren Sie p (i) für jedes Intervall und geben Sie den größten Endpunkt an, der kleiner als der Startpunkt des i-ten Intervalls ist. Verwenden Sie die binäre Suche, um nlogn zu erhalten
  3. definiere d [i] = max (w (i) + d [p (i)], d [i-1]).

initialisiere d [0] = 0

Das Ergebnis ist in d [n] n - der Anzahl der Intervalle.

Gesamtkomplexität O (nlogn)

import java.util.*;
class Interval {
  public int start;
  public int end;
  public int cost;
  public Interval(int start, int end, int cost){
    this.start = start;
    this.end = end;
    this.cost = cost;
  }
}
public class BestCombinationFinder {
  public int getBestCombination(List<Interval> intervals) {
    if (intervals == null || intervals.size() == 0) {
      return 0;
    }
    Collections.sort(intervals, new Comparator<Interval>() {
      public int compare(Interval i1, Interval i2) {
        if (i1.end < i2.end) {
          return -1;
        }
        else if (i1.end > i2.end) {
          return 1;
        }
        return 0;
      }
    });
    return findBestCombination(intervals);
  }
  private int findBestCombination(List<Interval> intervals) {
    int[] dp = new int[intervals.size() + 1];
    for (int i = 1; i <= intervals.size(); i++) {
      Interval currInt = intervals.get(i - 1);
      int pIndex = find(intervals, currInt.start, 0, intervals.size() - 1);
      dp[i] = Math.max(dp[pIndex+1] + currInt.cost, dp[i - 1]);
    }
    return dp[intervals.size()];
  }
  private int find(List<Interval> intervals, int target, int left, int right) {
    if (left > right) {
      return right;
    }
    else {
      int mid = (left + right) / 2;
      if (intervals.get(mid).end == target) {
        return mid;
      }
      else if (intervals.get(mid).end > target) {
        return find(intervals, target, left, mid - 1);
      }
      else {
        return find(intervals, target, mid + 1, right);
      }
    }
  }
}
Szilard Mandici
quelle
2
Bitte geben Sie einen Pseudocode an, anstatt zu verlangen, dass Benutzer Java (und insbesondere Java-Sammlungen) verstehen.
David Richerby
2
Ich habe den Pseudocode im ersten Teil meiner Antwort hinzugefügt. Ich wollte nur den entsprechenden Java-Code hinzufügen, wenn er jemandem hilft, ihn besser zu verstehen.
Szilard Mandici
0

Ja, es entspricht einem Rucksackproblem. Betrachten Sie die Endzeit des Jobs und bereiten Sie den Tisch wie einen Rucksack vor. Bevor Sie die folgende Lösung lesen, überprüfen Sie bitte das Rucksackproblem und seine Lösung.

// Input:
// Jobs (stored in array jobs)
// Number of jobs (n)

find the maximum end time from given n jobs => max_end

for j from 0 to max_end do
         table[0, j] := 0
end for 

for i from 1 to n do
    for j from 0 to max_end do
        if jobs[i].end <= j then
           table[i, j] := max(table[i-1, j], table[i-1, jobs[i].start] + jobs[i].cost)
       else
           table[i, j] := table[i-1, j]
       end if
    end for
 end for

Sie können die geplanten Jobs auch drucken, indem Sie die Tabelle durchlaufen:

j := max_end;
for i from n to 1 do
    if table[i][j] != table[i-1][j]
        print jobs[i]
        j = jobs[i].start; 
    end if
end for

Komplexität ist dasselbe wie Rucksackproblem.

Sandesh Kobal
quelle