Минимизация взвешенной суммы

Я столкнулся с этой проблемой совсем недавно. Предположим, что на оси x есть n точек, x[0], x[1].. x[n-1]. Пусть вес, связанный с каждой из этих точек, равен w[0], w[1].. w[n-1]. Начиная с любой точки от 0 до n-1, цель состоит в том, чтобы охватить все точки так, чтобы сумма w[i]*d[i] была сведена к минимуму, где d[i] - расстояние, пройденное для достижения I-й точки от начальной точки.

пример:
Предположим баллы: 1 5 10 20 40
Предположим вес: 1 2 10 50 13
Если я начну с точки 10 и перейду к точке 20, затем к 5, затем к 40 и, наконец, к 1, то взвешенная сумма станет 10*0+50*(10)+2*(10+15)+13*(10+15+35)+1*(10+15+35+39).

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

3 ответов


есть очень важный факт, который приводит к алгоритму полиномиального времени:

поскольку точки расположены на некоторой оси, они генерируют граф пути, что означает, что для каждых 3 вершин v1,v2,v3, Если v2 между v1 и v3, расстояние между v1 и v3 равно расстоянию между v1 и v2 плюс расстояние между v2 и v3. поэтому, если, например, мы начнем с v1, obj. значение функции пути это прежде всего v2 а потом v3 всегда будет меньше, чем значение пути, который сначала идет к v3 и обратно в v2 потому что:

value of the first path = w[2]*D(v1,v2)+W[3]*(D(v1,v2)+D(v2,v3))

value of the second path = w[3]*D(v1,v3)+W[2]*((v1,v3)+D(v3,v2)) = w[3]*D(v1,v2)+w[3]*D(v2,v3)+w[2]*(D(v1,v2)+2*D(v3,v2))

если мы вычитаем первое значение пути из второго, мы остаемся с w[2]*2*D(v3,v2) который равен или больше 0, если вы не считаете отрицательные веса.

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

это очень важно, так как это оставляет нас с 2^n возможные пути, а не n! возможные пути (как в проблеме коммивояжера).

решение гамильтонова пути TSP / minimum weight на графах путей может быть сделано в полиномиальное время с использованием динамического программирования, вы должны применить тот же метод, но изменить способ расчета целевая функция.

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


Возможно, вам следует уточнить, что вы имеете в виду, что алгоритм "не работает". Основная идея жадного подхода, который вы описали, кажется мне осуществимой. Вы имеете в виду, что жадный подход не обязательно найдет оптимальное решение? Как было указано в комментариях, это может быть NP-полной проблемой-хотя, конечно, нужно было бы проанализировать ее дальше: некоторое динамическое программирование и, возможно, некоторые суммы префиксов для расстояния вычисления также могут привести к решению полиномиального времени.

я быстро реализовал жадное решение на Java (надеюсь, я все правильно понял...)

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

public class MinWeightSum
{
    public static void main(String[] args)
    {
        double x[] = { 1, 5, 10, 20, 40 };
        double w[] = { 1, 2, 10, 50, 13 };

        List<Integer> givenIndices = Arrays.asList(2, 3, 1, 4, 0);
        Path path = createPath(x, w, givenIndices);
        System.out.println("Initial result "+path.sum);

        List<Integer> sortedWeightIndices =
            computeSortedWeightIndices(w);
        Path greedyPath = createPath(x, w, sortedWeightIndices);
        System.out.println("Greedy result  "+greedyPath.sum);

        System.out.println("For "+sortedWeightIndices+" sum "+greedyPath.sum);
   }

    private static Path createPath(
        double x[], double w[], List<Integer> indices)
    {
        Path path = new Path(x, w);
        for (Integer i : indices)
        {
            path.append(i);
        }
        return path;
    }


    private static List<Integer> computeSortedWeightIndices(final double w[])
    {
        List<Integer> indices = new ArrayList<Integer>();
        for (int i=0; i<w.length; i++)
        {
            indices.add(i);
        }
        Collections.sort(indices, new Comparator<Integer>()
        {
            @Override
            public int compare(Integer i0, Integer i1)
            {
                return Double.compare(w[i1], w[i0]);
            }
        });
        return indices;
    }

    static class Path
    {
        double x[];
        double w[];

        int prevIndex = -1;
        double distance;
        double sum;

        Path(double x[], double w[])
        {
            this.x = x;
            this.w = w;
        }

        void append(int index)
        {
            if (prevIndex != -1)
            {
                distance += Math.abs(x[prevIndex]-x[index]);
            }
            sum += w[index] * distance;
            prevIndex = index;
        }
    }
}

последовательность индексов, которую вы описали в Примере, дает решение

For [2, 3, 1, 4, 0] sum 1429.0

жадный подход, который вы описали дает

For [3, 4, 2, 1, 0] sum 929.0

лучшим решением

For [3, 2, 4, 1, 0] sum 849.0 

который я нашел, проверив все перестановки индексов (это невозможно для больших n конечно)


предположим, что вы частично прошли через решение и прошли расстояние D до сих пор. Если идти дальше расстояние X и увидеть точку С Вт вес стоит (Д + х)Вт. Если идти дальше расстояние Y и видим смысл с V вес стоит (Д + х + г)в.. Если вы суммируете все это, есть компонент, который зависит от пути, который вы берете после расстояния D: xw + xv + yv+..., и есть компонент, который зависит от расстояния D и суммы Весов точек, которые вам нужно перенос: D (v + w + ...). Но компонент, который зависит от расстояния D, не зависит ни от чего другого, кроме суммы Весов точек, которые вам нужно посетить, поэтому он фиксирован в том смысле, что он одинаков независимо от пути, который вы берете после прохождения расстояния D.

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

таким образом, дорогостоящий, но полиномиальный подход к динамическому программированию имеет в качестве состояния текущее положение (которое должно быть одной из точек) положение первой, если таковая имеется, неявленной точки слева от текущее положение и положение, если таковое имеется, первой незаявленной точки справа от текущей точки. Есть не более двух точек, которые мы должны рассмотреть посещение далее-точка справа от текущей точки и точка слева от текущей точки. Мы можем определить стоимость этих двух альтернатив, рассмотрев предварительно рассчитанные затраты для государств с меньшим количеством оставшихся точек, и сохранить наилучший результат как наилучшую возможную стоимость с этой точки. Мы могли бы подсчитать эти затраты по фикции что D=0 в момент достижения текущей точки. Когда мы ищем сохраненные затраты, они также хранятся в этом предположении (но С D=0 в их текущей точке, а не в нашей текущей точке), но мы знаем сумму весов точек, оставшихся на этом этапе, поэтому мы можем добавить к сохраненной стоимости эту сумму весов, умноженную на расстояние между нашей текущей точкой и точкой, которую мы ищем, чтобы компенсировать это.

Это дает стоимость O (n^3), потому что вы строите таблицу с O (n^3) клетки, причем каждая клетка является продуктом относительно простого процесса. Однако, поскольку никогда не имеет смысла проходить ячейки, не посещая их, текущая точка должна быть рядом с одной из двух точек на любом конце интервала, поэтому нам нужно рассмотреть только возможности O(n^2), что снижает стоимость до O(n^2). Зигзагообразно, таких как (0, 1, -1, 2, -2, 3, -3, 4, -4...) может быть лучшим решением для подходящих причудливых весов, но это все еще так, даже, например, при переходе от -2 до 3, это -2 to-ближайшая точка, еще не взятая между двумя точками 3 и -3.

Я поставил попытку реализации java в http://www.mcdowella.demon.co.uk/Plumber.java. Проводка теста проверяет эту версию ДП против (медленной) почти исчерпывающей версии для нескольких случайно произведенных тестовых случаев длины до и включая 12. Он по-прежнему не может быть полностью без ошибок, но, надеюсь, он заполнит детали.