Динамическое программирование: алгоритм для решения следующего? - PullRequest
6 голосов
/ 12 марта 2012

Я недавно выполнил следующее упражнение для интервью:

'Робот может быть запрограммирован на пробег "a", "b", "c" ... "n" километров, и это займет t a , t b , t c ... t n минут соответственно.Как только он достигает запрограммированных километров, он должен быть выключен на «м» минут.

Через «m» минут он снова может быть запрограммирован на дальнейшие «a», «b», «c» ... «n» километры.

Как бы вы запрограммировали этого робота, чтобы он прошел точное количество километров за минимальное количество времени? '

Я думал, что это вариация неограниченной проблемы рюкзак , в котором размер будет число километров и значение, время, необходимое для завершения каждого отрезка.Основное отличие состоит в том, что нам нужно минимизировать, а не максимизировать стоимость.Поэтому я использовал эквивалент следующего решения: http://en.wikipedia.org/wiki/Knapsack_problem#Unbounded_knapsack_problem, в котором я выбираю минимум.

Наконец, потому что нам нужно точное решение (если оно есть) по карте, построенной с помощьюАлгоритм для всех различных расстояний, я перебрал каждое и прошел запрограммированное расстояние каждого робота, чтобы найти точное расстояние и минимальное время среди них.

Я думаю, что пауза, которую робот делает между запусками, является чем-то вроде красной сельдии вам просто нужно включить его в свои расчеты, но это не влияет на выбранный подход.

Я, вероятно, ошибаюсь, потому что я не прошел тест.У меня нет других отзывов об ожидаемом решении.

Редактировать: возможно, я все-таки не ошибся и потерпел неудачу по разным причинам.Я просто хотел проверить свой подход к этой проблеме.

import static com.google.common.collect.Sets.*;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.log4j.Logger;
import com.google.common.base.Objects;
import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;

public final class Robot {

    static final Logger logger = Logger.getLogger (Robot.class);

    private Set<ProgrammedRun> programmedRuns;
    private int pause;
    private int totalDistance;

    private Robot () {
        //don't expose default constructor & prevent subclassing 
    }


    private Robot (int[] programmedDistances, int[] timesPerDistance, int pause, int totalDistance) {

        this.programmedRuns = newHashSet ();
        for (int i = 0; i < programmedDistances.length; i++) {
            this.programmedRuns.add (new ProgrammedRun (programmedDistances [i], timesPerDistance [i] ) );
        }
        this.pause = pause;
        this.totalDistance = totalDistance;
    }


    public static Robot create (int[] programmedDistances, int[] timesPerDistance, int pause, int totalDistance) {
        Preconditions.checkArgument (programmedDistances.length == timesPerDistance.length);
        Preconditions.checkArgument (pause >= 0);
        Preconditions.checkArgument (totalDistance >= 0);
        return new Robot (programmedDistances, timesPerDistance, pause, totalDistance);
    }

    /**
     * @returns null if no strategy was found. An empty map if distance is zero. A
     * map with the programmed runs as keys and number of time they need to be run
     * as value.  
     * 
     */
    Map<ProgrammedRun, Integer> calculateOptimalStrategy () {

        //for efficiency, consider this case first
        if (this.totalDistance == 0) {
            return Maps.newHashMap ();
        }

        //list of solutions for different distances. Element "i" of the list is the best set of runs that cover at least "i" kilometers
        List <Map<ProgrammedRun, Integer>> runsForDistances = Lists.newArrayList();

        //special case i = 0 -> empty map (no runs needed)
        runsForDistances.add (new HashMap<ProgrammedRun, Integer> () );

        for (int i = 1; i <= totalDistance; i++) {
            Map<ProgrammedRun, Integer> map = new HashMap<ProgrammedRun, Integer> ();
            int minimumTime = -1;
            for (ProgrammedRun pr : programmedRuns) {
                int distance = Math.max (0, i - pr.getDistance ());
                int time = getTotalTime (runsForDistances.get (distance) ) + pause + pr.getTime();
                if (minimumTime < 0 || time < minimumTime) {
                    minimumTime = time;
                    //new minimum found
                    map = new HashMap<ProgrammedRun, Integer> ();
                    map.putAll(runsForDistances.get (distance) );

                    //increase count
                    Integer num = map.get (pr);
                    if (num == null) num = Integer.valueOf (1);
                    else num++;

                    //update map
                    map.put (pr, num);
                }
            }
            runsForDistances.add (map );
        }

        //last step: calculate the combination with exact distance

        int minimumTime2 = -1;
        int bestIndex = -1;
        for (int i = 0; i <= totalDistance; i++) {
            if (getTotalDistance (runsForDistances.get (i) ) == this.totalDistance ) {
                int time = getTotalTime (runsForDistances.get (i) );
                if (time > 0) time -= pause;
                if (minimumTime2 < 0 || time < minimumTime2 ) {
                    minimumTime2 = time;
                    bestIndex = i;
                }
            }
        }

        //if solution found

        if (bestIndex != -1) {
            return runsForDistances.get (bestIndex);
        }

        //try all combinations, since none of the existing maps run for the exact distance
        List <Map<ProgrammedRun, Integer>> exactRuns = Lists.newArrayList();

        for (int i = 0; i <= totalDistance; i++) {
            int distance = getTotalDistance (runsForDistances.get (i) );
            for (ProgrammedRun pr : programmedRuns) {
                //solution found
                if (distance + pr.getDistance() == this.totalDistance ) {
                    Map<ProgrammedRun, Integer> map = new HashMap<ProgrammedRun, Integer> ();
                    map.putAll (runsForDistances.get (i));

                    //increase count
                    Integer num = map.get (pr);
                    if (num == null) num = Integer.valueOf (1);
                    else num++;

                    //update map
                    map.put (pr, num);

                    exactRuns.add (map);
                }
            }
        }

        if (exactRuns.isEmpty()) return null;

        //finally return the map with the best time
        minimumTime2 = -1;
        Map<ProgrammedRun, Integer> bestMap = null;

        for (Map<ProgrammedRun, Integer> m : exactRuns) {
            int time = getTotalTime (m);
            if (time > 0) time -= pause; //remove last pause
            if (minimumTime2 < 0 || time < minimumTime2 ) {
                minimumTime2 = time;
                bestMap = m;
            }
        }

        return bestMap;
    }

    private int getTotalTime (Map<ProgrammedRun, Integer> runs) {
        int time = 0;
        for (Map.Entry<ProgrammedRun, Integer> runEntry : runs.entrySet()) {
            time += runEntry.getValue () * runEntry.getKey().getTime ();
            //add pauses
            time += this.pause * runEntry.getValue ();
        }
        return time;
    }

    private int getTotalDistance (Map<ProgrammedRun, Integer> runs) {
        int distance = 0;
        for (Map.Entry<ProgrammedRun, Integer> runEntry : runs.entrySet()) {
            distance += runEntry.getValue() * runEntry.getKey().getDistance ();
        }
        return distance;
    }

    class ProgrammedRun {
        private int distance;
        private int time;
        private transient float speed;

        ProgrammedRun (int distance, int time) {
            this.distance = distance;
            this.time = time;
            this.speed = (float) distance / time;
        }

        @Override public String toString () {
            return "(distance =" + distance + "; time=" + time + ")";
        }

        @Override public boolean equals (Object other) {
            return other instanceof ProgrammedRun 
                && this.distance == ((ProgrammedRun)other).distance 
                && this.time == ((ProgrammedRun)other).time;
        }

        @Override public int hashCode () {
            return Objects.hashCode (Integer.valueOf (this.distance), Integer.valueOf (this.time));
        }

        int getDistance() {
            return distance;
        }

        int getTime() {
            return time;
        }

        float getSpeed() {
            return speed;
        }
    }

}


public class Main {

    /* Input variables for the robot */

    private static int [] programmedDistances = {1, 2, 3, 5, 10}; //in kilometers
    private static int [] timesPerDistance = {10, 5, 3, 2, 1}; //in minutes

    private static int pause = 2; //in minutes

    private static int totalDistance = 41; //in kilometers

    /**
     * @param args
     */
    public static void main(String[] args) {

        Robot r = Robot.create (programmedDistances, timesPerDistance, pause, totalDistance);

        Map<ProgrammedRun, Integer> strategy = r.calculateOptimalStrategy ();

        if (strategy == null) {
            System.out.println ("No strategy that matches the conditions was found");
        } else if (strategy.isEmpty ()) {
            System.out.println ("No need to run; distance is zero");
        } else {
            System.out.println ("Strategy found:");
            System.out.println (strategy);
        }
    }

}

Ответы [ 4 ]

4 голосов
/ 12 марта 2012

Немного упрощаясь, пусть t i будет временем (включая время простоя), которое требуется роботу, чтобы пробежать расстояние d i . Предположим, что t 1 / d 1 ≤… ≤ t n / d n . Если t 1 / d 1 значительно меньше, чем t 2 / d 2 и d 1 и общее расстояние D, которое должно быть пройдено, велико, тогда ветвление и граница, вероятно, превосходят динамическое программирование. Разветвление и связывание решает формулировку целочисленного программирования

свести к минимуму ∑ i t i x i
с учетом
10 i d i x i = D
& xxx; i x i & in; N

, используя значение релаксации, где x i может быть любым неотрицательным вещественным веществом в качестве ориентира. Последний легко проверяется как максимум (t 1 / d 1 ) D, установив x 1 в D / d 1 и & forall; i ≠ 1 x i = 0 и не менее (t 1 / d 1 ) D путем установки единственной переменной двойного запрограммируйте на t 1 / d 1 . Решение проблемы релаксации - это шаг связанный ; каждое целочисленное решение является дробным, поэтому наилучшее целочисленное решение требует как минимум времени (t 1 / d 1 ) D.

Шаг ветвления берет одну целочисленную программу и разбивает ее на две части, решения которых, вместе взятые, охватывают все пространство решений оригинала. В этом случае один фрагмент может иметь дополнительное ограничение x 1 = 0, а другой может иметь дополнительное ограничение x 1 ≥ 1. Может показаться, что это создаст подзадачи с боковые ограничения, но на самом деле мы можем просто удалить первый ход или уменьшить D на d 1 и добавить к цели постоянную t 1 . Другой вариант ветвления - добавить ограничение x i = & lfloor; D / d i & rfloor; или x i ≤ & lfloor; D / d i & rfloor; - 1, что требует обобщения на верхние границы числа повторений каждого хода.

Основной цикл ветвления и привязки выбирает одну из коллекции подзадач, ветвей, вычисляет границы для двух подзадач и возвращает их обратно в коллекцию. Эффективность по грубой силе проистекает из того факта, что, когда у нас есть решение с конкретным значением, каждая подзадача, смягченное значение которой по крайней мере настолько велико, может быть выброшена. Как только коллекция будет очищена таким образом, у нас будет оптимальное решение.

Возможны гибриды ветвления и связывания, а также динамическое программирование, например, вычисление оптимальных решений для малых D с помощью DP и использование этих значений вместо разветвлений в подзадачах, которые были решены.

1 голос
/ 12 марта 2012

Создать массив размером m и для 0 до m (m - ваше расстояние) сделать:

a [i] = бесконечно;

a [0] = 0;

a [i] = min {min {a [ij] + t j + m для всех j в возможных километрах робота.и j ≠ i }, t i , если i находится в возможных шагах робота}

[м] является наименьшим возможным значением.Также вы можете иметь массив типа b, чтобы сохранить выбор a[i] s.Также, если [m] == бесконечное число означает, что это невозможно.

Редактировать: мы можем решить его другим способом, создав орграф, опять-таки наш график зависит от длины mпути, у графа есть узлы с меткой {0..m}, теперь начинайте с узла 0 и соединяйте его со всеми возможными узлами;означает, что если у вас есть километр i, вы можете подключить 0 и v i с весом t i , за исключением узла 0-> x, для всех остальных узлов вам следует подключить узел i-> j с весом t ji + m для j> i и ji доступно во входных километрах.теперь вы должны найти кратчайший путь от v 0 до v n .но этот алгоритм все еще O (нм).

0 голосов
/ 13 марта 2012

Я большой сторонник показа вместо того, чтобы рассказывать.Вот программа, которая может делать то, что вы ищете.Дайте мне знать, если это удовлетворит ваш вопрос.Просто скопируйте, вставьте и запустите программу.Конечно, вы должны проверить свой собственный набор данных.

import java.util.Arrays;

public class Speed {
/***
 *
 * @param distance
 * @param sprints ={{A,Ta},{B,Tb},{C,Tc}, ..., {N,Tn}}
 */
public static int getFastestTime(int distance, int[][] sprints){
    long[] minTime = new long[distance+1];//distance from 0 to distance
    Arrays.fill(minTime,Integer.MAX_VALUE);
    minTime[0]=0;//key=distance; value=time
    for(int[] speed: sprints)
        for(int d=1; d<minTime.length; d++)
            if(d>=speed[0] && minTime[d] > minTime[d-speed[0]]+speed[1])
                minTime[d]=minTime[d-speed[0]]+speed[1];
    return (int)minTime[distance];
}//

public static void main(String... args){
    //sprints ={{A,Ta},{B,Tb},{C,Tc}, ..., {N,Tn}}
    int[][] sprints={{3,2},{5,3},{7,5}};
    int distance = 21;
    System.out.println(getFastestTime(distance,sprints));
}
}
0 голосов
/ 12 марта 2012

Пусть G будет желаемым пробегом.

Пусть n будет наибольшим возможным пробегом без паузы.

Пусть L = G / n (целочисленная арифметика, часть отбрасывания дроби)

Пусть R = G mod n (т.е. остаток от вышеуказанного деления)

Заставьте робота пробежать самое длинное расстояние (т.е. n) L раз, а затем, какое бы расстояние (a, b, c и т. Д.) Не превышало бы R на наименьшую величину (т. Е. Наименьшее доступное расстояние, равное или больше чем R)

Либо я неправильно понял проблему, либо вы все обдумываете это

...