Минимизация суммы специальной функции по списку

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

Например, рассмотрим список { 1, 2, 3, 4 } и просуммируем a^b для последовательных пар (a,b) по всему списку. т.е. 1^2 + 2^3 + 3^4 = 90. При осмотре минимальная сумма достигается, когда список оформлен как { 2, 3, 1, 4 } => (2^3 + 3^1 + 1^4 = 12).

Обратите внимание, что сумма не зацикливается (т.е. я не рассматриваю last^first), и порядок важен (2^3 != 3^2), а также a^b может быть любой функцией, работающей над любым количеством последовательных элементов.

Есть ли название для такого алгоритма и есть ли устоявшиеся способы его реализации?

Обновлено: Я изменил формулировку вопроса, поскольку неправильно назвал это проблемой сортировки. Как уже отмечалось, это скорее проблема оптимизации.

+1 за невероятно непонятный вопрос. Я не могу представить, что есть ответ, но посмотрю еще раз, чтобы увидеть.

Binary Worrier 06.01.2009 18:24

Я что-то не понимаю? 1 ^ 2 + 2 ^ 3 + 3 ^ 4 = 90, а не 91 ...

BenAlabaster 06.01.2009 18:35

нет, ты прав. Сейчас 90, но это не такая уж большая проблема;)

Matthew Brubaker 06.01.2009 18:37

@Binary Worrier: Я (к несчастью) пытаюсь спросить: как вы сортируете список, когда вам нужно рассматривать весь список, а не сравнивать элементы один за другим? @balabaster: Верно. Спасибо.

Ozgur Ozcitak 06.01.2009 18:38

Я не думаю, что это вещь сортировки - это вещь оптимизации. Вы оптимизируете функцию для всех перестановок в списке.

user3458 06.01.2009 18:41

Для произвольных функций это как раз задача коммивояжера; см. также stackoverflow.com/questions/310787/… [я думаю, что отправил этот ответ, но, по-видимому, нет ...]

ShreevatsaR 06.01.2009 20:20
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
7
6
436
10
Перейти к ответу Данный вопрос помечен как решенный

Ответы 10

Это то, что у меня есть до сих пор. Я создал класс Calc, в который я могу передать каждую из моих комбинаций, затем он вычисляет сумму и имеет метод ToString (), поэтому вам не нужно беспокоиться об итерации для вывода строки суммы и значения. Вы можете получить общее количество и список, переданный в конструктор. Затем вы можете просто добавить каждый из своих наборов комбинаций в список, который вы можете отсортировать по LINQ в inst.Total ... как я продемонстрировал. Все еще работаю над средствами для создания каждой комбинации ...

class Calc
{
    private int[] items;
    private double total;
    public double Total 
    { 
        get
        { 
            return total; 
        } 
    }
    public int[] Items
    {
        get { return items;  }
        set { total = Calculate(value); }
    }
    public static double Calculate(int[] n)
    {
        double t = 0;
        for (int i = 0; i < n.Length - 1; i++)
        {
            int a = n[i]; int b = n[i + 1];
            t += a^b;
        }
        return t;
    }
    public Calc(int[] n)
    {
        this.items = n;
        this.total = Calculate(n);
    }
    public override string ToString()
    {
        var s = String.Empty;
        for (int i = 0; i < items.Length - 1; i++)
        {
            int a = items[i]; int b = items[i + 1];
            s += String.Format("{0}^{1}", a, b);
            s += i < items.Length - 2 ? "+" : " = ";
        }
        s += total;
        return s;
    }
}

А затем мы используем класс в наших вычислениях и очень быстро сортируем по сумме каждой перестановки:

class Program
{
    static void Main(string[] args)
    {
        var Calculations = new List<Calc>();

        ////Add a new item to totals for every combination of...working on this
        Calculations.Add(new Calc(new int[] { 1, 2, 3, 4 }));
        //...

        //Grab the item with the lowest total... if we wanted the highest, we'd
        //just change .First() to .Last()
        var item = Calculations.OrderBy(i=>i.Total).First();
        Console.WriteLine(item);
        //Or if we wanted all of them:
        //Calculations.OrderBy(i=>i.Total).ForEach(Console.WriteLine);
    }
}

Вы можете сохранить лучшую на данный момент сумму, а затем выйти из внутреннего цикла Calculate, если t станет больше этого.

The Archetypal Paul 06.01.2009 19:40

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

BenAlabaster 06.01.2009 20:20

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

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

Я должен попробовать и посмотреть, что я могу придумать.

Поскольку нет ограничений на используемую функцию

also a^b could be any function operating over any number of consecutive elements.

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

Так что я не вижу ничего быстрее, чем вычисление функции и суммы для всех перестановок.

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

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

Итак, в общем случае вам определенно нужно будет оценить функцию для всех перестановок.

Хорошее объяснение, спасибо. Я просто подумал, что, поскольку моя функция расстояния работает с последовательными элементами, может быть умный способ свести это к более простой проблеме, а не грубо форсировать все перестановки.

Ozgur Ozcitak 07.01.2009 17:35

Похоже, это Проблема оптимизации, а не проблема сортировки.

Бьюсь об заклад, немного (а может быть, много) работы кто-то может показать, что это функционально эквивалентно одной из известных проблем NP Complete. Однако для некоторых конкретных функций (например, a ^ b в вашем примере) проблема может быть проще.

Это определенно не отсортированный список. Если у вас есть отсортированный список [x ~ 0 ~ ... x ~ n ~], список [x ~ 0 ~ ..x ~ i-1 ~, x ~ i + 1 ~ ..x ~ n ~] (т.е. x ~ i ~ удалено) по определению также будет отсортировано. В вашем примере удаление 0 из подпоследовательности 100,0,100, скорее всего, приведет к отмене сортировки списка.

непроверенные и недоказанные:

  1. Сортировать список
  2. Создайте пары из отсортированного списка, захватывая первое число и последнее, затем второе и второе до последнего и т. д. (Т.е. 1,2,3,4 становятся 1,4 и 2,3)
  3. Для каждой пары сохраните значение ^ b. Обратный отсортировать этот список
  4. Замените значения a ^ b в новом списке на значения a и b.

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

Нет, на самом деле речь идет об общем случае. Также часть пункта 3 должна гласить «перевернуть этот список», а не «перевернуть этот список». Кроме того, вам не нужна первая половина пункта 3 и совсем не нужна точка 4.

Binary Worrier 07.01.2009 12:31
Ответ принят как подходящий

«Сортировка» обычно определяется с помощью оператора двоичного сравнения («меньше или равно»). То, что вы ищете, - это «лучшая» перестановка списка, где «лучший» определяется как критерий, который определяется для всего списка (в то время как «определенная функция» определяется для соседних элементов, сумма по всему списку делает его глобальным свойством).

Если я правильно понимаю, "коммивояжер" - это пример вашей проблемы, так что ваша проблема в любом случае является NP-полной ;-)

Если function = "расстояние между городом a и городом b", то да, это проблема коммивояжера!

The Archetypal Paul 06.01.2009 19:30

Более правильно, учитывая любой экземпляр TSP, вы можете сделать его экземпляром этой проблемы, определив здесь «функцию» как соответствующее расстояние в экземпляре TSP. Следовательно, TSP сводится к этой проблеме.

ShreevatsaR 06.01.2009 20:10

Это полная проблема NP, потому что алгоритм неизвестен (NB для данной функции a ^ b это не NP Complete, это можно сделать за один проход после сортировки, см. Пример ниже для решения)

Невозможно заранее написать «алгоритм сортировки» без вычисления результатов данной функции для всех возможных перестановок списка.

Однако, получив функцию, вы можете (возможно) разработать для нее метод сортировки, например. для a ^ b выше, упорядочьте список таким образом (без применения функции к каким-либо элементам) «Макс, мин, следующий максимум, следующий минимум. . . »И поменяйте этот порядок в обратном порядке.

В зависимости от сложности данной функции будет все труднее предоставлять оптимизированные процедуры сортировки.

Спасибо,

В то время я был уверен, что это не полная проблема NP. Поскольку я снял голос "против". Надеюсь, ваша репутация изменилась соответствующим образом. Простите...

Pierre 06.01.2009 21:22

Кроме того, эта проблема может быть неполной NP в зависимости от конкретной применяемой функции (общие вопросы OP).

Gregg Lind 06.01.2009 21:25

@Gregg: Я согласен, это не было четко указано в моем исходном ответе, поэтому я немного отредактировал ответ. Спасибо

Binary Worrier 07.01.2009 12:25

@Pierre: Спасибо, я всегда оставляю комментарий, в котором говорится, почему я голосую против (если там нет достаточного комментария), и проверяю его позже, чтобы узнать, могу ли я его удалить. Еще раз спасибо дружище

Binary Worrier 07.01.2009 12:27

Это домашнее задание?

В противном случае это проблема динамического программирования. Чтобы увидеть это, вы должны преобразовать вашу проблему в следующую, взяв за основу ваш пример. Вы в самом начале. Вы можете выбрать один из {1,2,3,4}. Оттуда вы можете перейти к {1,2,3,4}. Сделайте это 4 раза, и у вас будет все расположение длины 4 из списка {1,2,3,4}.

Теперь вам нужна функция стоимости, которая определяется как:

f(prev, next) = prev ^ next
              = 0 if the solution is not valid for your original problem 
              = 0 if prev is the start

Общая стоимость выражается как

cost(i|a|X) = min(i in {1,2,3,4}, f(i, a) + cost(X))

обратите внимание, что i|a|X представляет собой список, начинающийся с элемента a, а затем i, а остальная часть списка - X.

Глядя на функцию cost, вы должны распознать динамическое программирование.

Оттуда вы можете вывести алгоритм. Посмотрите википедию, чтобы найти введение в динамическое программирование.

Моя реализация схемы, которую вы можете протестировать с помощью схемы PLT:

(define (cost lst f)
  (if (null? lst)
      0
      (let ((h (car lst))
            (t (cdr lst)))
        (if (null? t)
            0
            (+ (f h (car t))
               (cost t f))))))

(define (solve lst f)
  (let loop ((s '()))
    (if (= (length s) (length lst))
        s
        (loop
         (let choose ((candidate lst)
                      (optimal #f)
                      (optimal-cost #f))
           (if (null? candidate)
               optimal
               (let ((c (car candidate)))
                 (if (memq c s)
                     (choose (cdr candidate) optimal optimal-cost)
                     (if (not optimal) 
                         (choose (cdr candidate) (cons c s) (cost (cons c s) f))
                         (if (<= (cost (cons c s) f)
                                 (cost optimal f))
                             (choose (cdr candidate) (cons c s) (cost (cons c s) f))
                             (choose (cdr candidate) optimal optimal-cost)))))))))))

Затем вызов (solve '(1 2 3 4) expt) дает другое минимальное решение »(3 2 1 4).

Еще одно слово помимо «динамического программирования», которое вы, возможно, захотите изучить, - это понятие «ограничение».

Gregg Lind 06.01.2009 21:26

Это просто стандартный (n ^ 2) (2 ^ n) DP. Кстати, вы можете сократить коэффициент n из своего кода.

ShreevatsaR 07.01.2009 01:46

Это работает для (1 2 3 4). Но когда я пробую (1 2 3 4 5), он возвращает (4 3 2 1 5) со стоимостью 76. Однако существует лучшее решение: (4 2 3 1 5) со стоимостью 28.

Ozgur Ozcitak 07.01.2009 12:00

Я вижу только одно решение:

грубая сила

    public static int Calculate(Func<int, int, int> f, IList<int> l)
    {
        int sum = 0;
        for (int i = 0; i < l.Count-1; i++)
        {
            sum += f(l[i], l[i + 1]);
        }
        return sum;
    }

    public static IEnumerable<IEnumerable<T>> Permute<T>(IEnumerable<T> list, int count)
    {
        if (count == 0)
        {
            yield return new T[0];
        }
        else
        {
            int startingElementIndex = 0;
            foreach (T startingElement in list)
            {
                IEnumerable<T> remainingItems = AllExcept(list, startingElementIndex);

                foreach (IEnumerable<T> permutationOfRemainder in Permute(remainingItems, count - 1))
                {
                    yield return Concat<T>(
                        new T[] { startingElement },
                        permutationOfRemainder);
                }
                startingElementIndex += 1;
            }
        }
    }

    // Enumerates over contents of both lists.
    public static IEnumerable<T> Concat<T>(IEnumerable<T> a, IEnumerable<T> b)
    {
        foreach (T item in a) { yield return item; }
        foreach (T item in b) { yield return item; }
    }

    // Enumerates over all items in the input, skipping over the item
    // with the specified offset.
    public static IEnumerable<T> AllExcept<T>(IEnumerable<T> input, int indexToSkip)
    {
        int index = 0;
        foreach (T item in input)
        {
            if (index != indexToSkip) yield return item;
            index += 1;
        }
    }

    public static void Main(string[] args)
    {
        List<int> result = null;
        int min = Int32.MaxValue;
        foreach (var p in Permute<int>(new List<int>() { 1, 2, 3, 4 }, 4))
        {
            int sum = Calculate((a, b) => (int)Math.Pow(a, b), new List<int>(p));
            if (sum < min)
            {
                min = sum;
                result = new List<int>(p);
            }
        }
        // print list
        foreach (var item in result)
        {
            Console.Write(item);
        }
    }

Я украл код перестановки из Блог Иэна Гриффитса.

Другие вопросы по теме