Скажем, у меня есть список, и я хочу, чтобы он был организован так, чтобы сумма определенной функции, работающей над ее последовательными элементами, была минимальной.
Например, рассмотрим список { 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 ^ 2 + 2 ^ 3 + 3 ^ 4 = 90, а не 91 ...
нет, ты прав. Сейчас 90, но это не такая уж большая проблема;)
@Binary Worrier: Я (к несчастью) пытаюсь спросить: как вы сортируете список, когда вам нужно рассматривать весь список, а не сравнивать элементы один за другим? @balabaster: Верно. Спасибо.
Я не думаю, что это вещь сортировки - это вещь оптимизации. Вы оптимизируете функцию для всех перестановок в списке.
Для произвольных функций это как раз задача коммивояжера; см. также stackoverflow.com/questions/310787/… [я думаю, что отправил этот ответ, но, по-видимому, нет ...]





Это то, что у меня есть до сих пор. Я создал класс 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 станет больше этого.
Вы могли бы, и он лучше всего подходит для точного расчета, но возможность выбирать, что вы берете, вместо того, чтобы отказываться от всех остальных, добавляет большую гибкость.
Хм, это интересная проблема. Использование существующих конструкций сортировки (IComparable) не сработает, потому что вам нужно больше информации, чем доступно для метода compareTo.
Решение этой проблемы будет во многом зависеть от того, какой метод вы хотите использовать для сортировки. Однако на первый взгляд может показаться, что вам, вероятно, придется перебирать все возможные заказы, чтобы найти минимальный заказ. Вы можете потенциально замкнуть его, если текущая сумма больше, чем предыдущая общая сумма.
Я должен попробовать и посмотреть, что я могу придумать.
Поскольку нет ограничений на используемую функцию
also a^b could be any function operating over any number of consecutive elements.
если используется постоянная функция (скажем, та, которая всегда возвращает 1), сумма будет одинаковой для всех порядков, но вы не обязательно узнаете это, пока не посмотрите на все порядки.
Так что я не вижу ничего быстрее, чем вычисление функции и суммы для всех перестановок.
(Вы можете запоминать результаты для каждого кортежа, чтобы ускорить оценку, но я думаю, вам все равно нужно просмотреть их все)
Обновлено: Кроме того, поскольку это может быть функция, действующая на все элементы, у вас может быть функция, которая возвращала бы 0 для всех перестановок, кроме одной, для которой она возвращает 1.
Итак, в общем случае вам определенно нужно будет оценить функцию для всех перестановок.
Хорошее объяснение, спасибо. Я просто подумал, что, поскольку моя функция расстояния работает с последовательными элементами, может быть умный способ свести это к более простой проблеме, а не грубо форсировать все перестановки.
Похоже, это Проблема оптимизации, а не проблема сортировки.
Бьюсь об заклад, немного (а может быть, много) работы кто-то может показать, что это функционально эквивалентно одной из известных проблем NP Complete. Однако для некоторых конкретных функций (например, a ^ b в вашем примере) проблема может быть проще.
Это определенно не отсортированный список. Если у вас есть отсортированный список [x ~ 0 ~ ... x ~ n ~], список [x ~ 0 ~ ..x ~ i-1 ~, x ~ i + 1 ~ ..x ~ n ~] (т.е. x ~ i ~ удалено) по определению также будет отсортировано. В вашем примере удаление 0 из подпоследовательности 100,0,100, скорее всего, приведет к отмене сортировки списка.
непроверенные и недоказанные:
Я предполагаю, что это не может быть так просто ... но это работает для вашего примера, и разве это не главное?
Нет, на самом деле речь идет об общем случае. Также часть пункта 3 должна гласить «перевернуть этот список», а не «перевернуть этот список». Кроме того, вам не нужна первая половина пункта 3 и совсем не нужна точка 4.
«Сортировка» обычно определяется с помощью оператора двоичного сравнения («меньше или равно»). То, что вы ищете, - это «лучшая» перестановка списка, где «лучший» определяется как критерий, который определяется для всего списка (в то время как «определенная функция» определяется для соседних элементов, сумма по всему списку делает его глобальным свойством).
Если я правильно понимаю, "коммивояжер" - это пример вашей проблемы, так что ваша проблема в любом случае является NP-полной ;-)
Если function = "расстояние между городом a и городом b", то да, это проблема коммивояжера!
Более правильно, учитывая любой экземпляр TSP, вы можете сделать его экземпляром этой проблемы, определив здесь «функцию» как соответствующее расстояние в экземпляре TSP. Следовательно, TSP сводится к этой проблеме.
Это полная проблема NP, потому что алгоритм неизвестен (NB для данной функции a ^ b это не NP Complete, это можно сделать за один проход после сортировки, см. Пример ниже для решения)
Невозможно заранее написать «алгоритм сортировки» без вычисления результатов данной функции для всех возможных перестановок списка.
Однако, получив функцию, вы можете (возможно) разработать для нее метод сортировки, например. для a ^ b выше, упорядочьте список таким образом (без применения функции к каким-либо элементам) «Макс, мин, следующий максимум, следующий минимум. . . »И поменяйте этот порядок в обратном порядке.
В зависимости от сложности данной функции будет все труднее предоставлять оптимизированные процедуры сортировки.
Спасибо,
В то время я был уверен, что это не полная проблема NP. Поскольку я снял голос "против". Надеюсь, ваша репутация изменилась соответствующим образом. Простите...
Кроме того, эта проблема может быть неполной NP в зависимости от конкретной применяемой функции (общие вопросы OP).
@Gregg: Я согласен, это не было четко указано в моем исходном ответе, поэтому я немного отредактировал ответ. Спасибо
@Pierre: Спасибо, я всегда оставляю комментарий, в котором говорится, почему я голосую против (если там нет достаточного комментария), и проверяю его позже, чтобы узнать, могу ли я его удалить. Еще раз спасибо дружище
Это домашнее задание?
В противном случае это проблема динамического программирования. Чтобы увидеть это, вы должны преобразовать вашу проблему в следующую, взяв за основу ваш пример. Вы в самом начале. Вы можете выбрать один из {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).
Еще одно слово помимо «динамического программирования», которое вы, возможно, захотите изучить, - это понятие «ограничение».
Это просто стандартный (n ^ 2) (2 ^ n) DP. Кстати, вы можете сократить коэффициент n из своего кода.
Это работает для (1 2 3 4). Но когда я пробую (1 2 3 4 5), он возвращает (4 3 2 1 5) со стоимостью 76. Однако существует лучшее решение: (4 2 3 1 5) со стоимостью 28.
Я вижу только одно решение:
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);
}
}
Я украл код перестановки из Блог Иэна Гриффитса.
+1 за невероятно непонятный вопрос. Я не могу представить, что есть ответ, но посмотрю еще раз, чтобы увидеть.