Encontre trabalhos agendados sem sobreposição com custo máximo

8

Dado um conjunto de n trabalhos com [horário de início, horário de término, custo], localize um subconjunto para que nenhum trabalho se sobreponha e o custo seja máximo.

Agora não tenho certeza se um algoritmo ganancioso fará o truque. Ou seja, classifique por custo e sempre faça o próximo trabalho que não se cruze e com o custo máximo entre os dois.

Isso é equivalente a um problema de mochila? Como eu poderia abordar isso?

user1531186
fonte

Respostas:

8

O gráfico de tarefas sobrepostas é um gráfico de intervalo . Os gráficos de intervalo são gráficos perfeitos. Então, o que você está tentando fazer é encontrar um conjunto independente de peso máximo (ou seja, não há duas sobreposições) em um gráfico perfeito . Isso pode ser resolvido em tempo polinomial. O algoritmo é dado em "Algoritmos polinomiais para gráficos perfeitos" , de M. Grötschel, L. Lovász e A. Schrijver.

Existem vários casos especiais de gráficos perfeitos para os quais as pessoas descobriram algoritmos mais simples e eficientes para esta tarefa. Não sei se os gráficos de intervalo se enquadram em um desses casos especiais.

Peter Shor
fonte
6

O algoritmo ganancioso não pode ajudar nesse caso. E não pôde ser comparado com problemas fracionários ou de mochila 0-1. O primeiro pode ser resolvido pelo algoritmo guloso em O (n) e o segundo é NP.

O problema que você tem pode ser forçado com força bruta em O (2 ^ n). Mas você pode otimizá-lo usando programação dinâmica.

1) Classifique os intervalos por hora de início.

2) Inicialize int [] costs = new int [jobs.length] com Integer.MIN_VALUE (ou qualquer valor negativo);

3) Defina a seguir a rotina recursiva (aqui é 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) Inicie a recursão com k = 0;

Eu implementei apenas rotina de recursão enquanto outras partes são triviais. Eu considerei que qualquer custo é> = 0. Se houver trabalhos com custos negativos, precisamos adicionar uma verificação e passar esses trabalhos sem consideração.

Máxima
fonte
5

Pode-se implementar isso em O (nlogn)

Passos:

  1. Classifique os intervalos com base no horário final
  2. defina p (i) para cada intervalo, fornecendo o maior ponto final menor que o ponto inicial do i-ésimo intervalo. Use a pesquisa binária para obter o nlogn
  3. defina d [i] = max (w (i) + d [p (i)], d [i-1]).

inicialize d [0] = 0

O resultado será em d [n] n - o número de intervalos.

Complexidade geral 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
fonte
2
Forneça pseudocódigo, em vez de exigir que as pessoas entendam Java (e, em particular, coleções Java).
David Richerby
2
Eu adicionei o pseudo-código na primeira parte da minha resposta. Eu só queria adicionar o código Java correspondente também, se isso ajudar alguém a entendê-lo melhor.
Szilard Mandici
0

Sim, é equivalente a um problema de mochila. Considere o horário de término do trabalho e prepare a mesa como uma mochila. Antes de ler a solução a seguir, verifique o problema da mochila e sua solução.

// 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

Você também pode imprimir os trabalhos agendados percorrendo a tabela:

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

Complexidade é o mesmo que problema de mochila.

Sandesh Kobal
fonte