Оптимизация рекурсивного алгоритма в Java

Фон

У меня есть упорядоченный набор точек данных, сохраненных как TreeSet<DataPoint>. Каждая точка данных имеет position и Set объектов Event (HashSet<Event>).

Есть 4 возможных объекта Event A, B, C и D. Каждый DataPoint имеет 2 из них, например. A и C, кроме первого и последнего DataPoint объектов в наборе, которые имеют T размера 1.

Мой алгоритм должен найти вероятность нового DataPoint Q в позиции x с Event Q в этом наборе.

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

Алгоритм

Формула для вычисления S:

http://mathbin.net/equations/105225_0.png

где

http://mathbin.net/equations/105225_1.png

http://mathbin.net/equations/105225_2.png

для http://mathbin.net/equations/105225_3.png

и

http://mathbin.net/equations/105225_4.png

http://mathbin.net/equations/105225_5.png - это дорогостоящая функция вероятности, которая зависит только от ее аргументов и ничего другого (и http://mathbin.net/equations/105225_6.png), http://mathbin.net/equations/105225_7.png - это последний DataPoint в наборе ( right node), http://mathbin.net/equations/105225_8.png - это первый DataPoint (lefthand node), http://mathbin.net/equations/105225_9.png - самый правый DataPoint, который не является node, http://mathbin.net/equations/105225_10.png является DataPoint, http://mathbin.net/equations/105225_12.png является Set событий для этого DataPoint.

Таким образом, вероятность для Q с Event Q равна:

http://mathbin.net/equations/105225_11.png

Реализация

Я реализовал этот алгоритм в Java так:

public class ProbabilityCalculator {
    private Double p(DataPoint right, Event rightEvent, DataPoint left, Event leftEvent) {
        // do some stuff
    }

    private Double f(DataPoint right, Event rightEvent, NavigableSet<DataPoint> points) {
        DataPoint left = points.lower(right);

        Double result = 0.0;

        if(left.isLefthandNode()) {
            result = 0.25 * p(right, rightEvent, left, null);
        } else if(left.isQ()) {
            result = p(right, rightEvent, left, left.getQEvent()) * f(left, left.getQEvent(), points);
        } else { // if M_k
            for(Event leftEvent : left.getEvents())
                result += p(right, rightEvent, left, leftEvent) * f(left, leftEvent, points);
        }

        return result;
    }

    public Double S(NavigableSet<DataPoint> points) {
        return f(points.last(), points.last().getRightNodeEvent(), points)
    }
}

Итак, чтобы найти вероятность Q при x с Q:

Double S1 = S(points);
points.add(Q);
Double S2 = S(points);
Double probability = S2/S1;

Проблема

Поскольку реализация на данный момент соответствует математическому алгоритму. Однако на практике это оказывается не очень хорошей идеей, так как f вызывает себя дважды для каждого DataPoint. Итак, для http://mathbin.net/equations/105225_9.png, f вызывается дважды, затем для n-1 f вызывается дважды дважды для каждого из предыдущие вызовы и т.д. и т.д. Это приводит к сложности O(2^n), что довольно ужасно, учитывая, что в каждом Set может быть более 1000 DataPoints. Поскольку p() не зависит от всего, кроме его параметров, я включил функцию кеширования, где, если p() уже был рассчитан для этих параметров, он просто возвращает предыдущий результат, но это не решает проблему сложности с присущей сложностью. Я что-то пропустил здесь в отношении повторных вычислений, или это сложность, неизбежная в этом алгоритме?

Ответ 1

Спасибо за все ваши предложения. Я реализовал свое решение, создав новые вложенные классы для уже рассчитанных значений P и F, а затем использовал HashMap для хранения результатов. Затем HashMap запрашивается для результата, прежде чем вычисление произойдет; если он присутствует, он просто возвращает результат, если он не вычисляет результат и добавляет его в HashMap.

Конечный продукт выглядит примерно так:

public class ProbabilityCalculator {

    private NavigableSet<DataPoint> points;

    private ProbabilityCalculator(NavigableSet<DataPoint> points) {
        this.points = points;
    }

    private static class P {
        public final DataPoint left;
        public final Event leftEvent;
        public final DataPoint right;
        public final Event rightEvent;

        public P(DataPoint left, Event leftEvent, DataPoint right, Event rightEvent) {
            this.left = left;
            this.leftEvent = leftEvent;
            this.right = right;
            this.rightEvent = rightEvent;
        }

        public boolean equals(Object o) {
            if(!(o instanceof P)) return false;
            P p = (P) o;

            if(!(this.leftEvent == null ? p.leftEvent == null : this.leftEvent.equals(p.leftEvent)))
                return false;
            if(!(this.rightEvent == null ? p.rightEvent == null : this.rightEvent.equals(p.rightEvent)))
                return false;

            return this.left.equals(p.left) && this.right.equals(p.right);
        }

        public int hashCode() {
            int result = 93;

            result = 31 * result + this.left.hashCode();
            result = 31 * result + this.right.hashCode();
            result = this.leftEvent != null ? 31 * result + this.leftEvent.hashCode() : 31 * result;
            result = this.rightEvent != null ? 31 * result + this.rightEvent.hashCode() : 31 * result;

            return result;
        }
    }

    private Map<P, Double> usedPs = new HashMap<P, Double>();

    private static class F {
        public final DataPoint left;
        public final Event leftEvent;
        public final NavigableSet<DataPoint> dataPointsToLeft;

        public F(DataPoint dataPoint, Event dataPointEvent, NavigableSet<DataPoint> dataPointsToLeft) {
            this.dataPoint = dataPoint;
            this.dataPointEvent = dataPointEvent;
            this.dataPointsToLeft = dataPointsToLeft;
        }

        public boolean equals(Object o) {
            if(!(o instanceof F)) return false;
            F f = (F) o;
            return this.dataPoint.equals(f.dataPoint) && this.dataPointEvent.equals(f.dataPointEvent) && this.dataPointsToLeft.equals(f.dataPointsToLeft);
        }

        public int hashCode() {
            int result = 7;

            result = 31 * result + this.dataPoint.hashCode();
            result = 31 * result + this.dataPointEvent.hashCode();
            result = 31 * result + this.dataPointsToLeft.hashCode();

            return result;
        }

    }

    private Map<F, Double> usedFs = new HashMap<F, Double>();

    private Double p(DataPoint right, Event rightEvent, DataPoint left, Event leftEvent) {
        P newP = new P(right, rightEvent, left, leftEvent);

        if(this.usedPs.containsKey(newP)) return usedPs.get(newP);


        // do some stuff

        usedPs.put(newP, result);
        return result;

    }

    private Double f(DataPoint right, Event rightEvent) {

        NavigableSet<DataPoint> dataPointsToLeft = dataPoints.headSet(right, false);

        F newF = new F(right, rightEvent, dataPointsToLeft);

        if(usedFs.containsKey(newF)) return usedFs.get(newF);

        DataPoint left = points.lower(right);

        Double result = 0.0;

        if(left.isLefthandNode()) {
            result = 0.25 * p(right, rightEvent, left, null);
        } else if(left.isQ()) {
            result = p(right, rightEvent, left, left.getQEvent()) * f(left, left.getQEvent(), points);
        } else { // if M_k
            for(Event leftEvent : left.getEvents())
                result += p(right, rightEvent, left, leftEvent) * f(left, leftEvent, points);
        }

        usedFs.put(newF, result)

        return result;
    }

    public Double S() {
        return f(points.last(), points.last().getRightNodeEvent(), points)
    }

    public static probabilityOfQ(DataPoint q, NavigableSet<DataPoint> points) {
        ProbabilityCalculator pc = new ProbabilityCalculator(points);

        Double S1 = S();

        points.add(q);

        Double S2 = S();

        return S2/S1;

    }
}

Ответ 2

Вам также нужно memoize f для первых двух аргументов (третий всегда передается, поэтому вам не нужно об этом беспокоиться). Это уменьшит сложность времени вашего кода от O (2 ^ n) до O (n).

Ответ 3

ОБНОВЛЕНО:

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

....
private Map<String, Double> previousResultMap = new ....


private Double p(DataPoint right, Event rightEvent, DataPoint left, Event leftEvent) {
   String key = // calculate unique key from inputs
   Double previousResult = previousResultMap.get(key);
   if (previousResult != null) {
      return previousResult;
   } 

   // do some stuff
   previousResultMap.put(key, result);
   return result;
}

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