Алгоритм оценки монотонности массива (т. е. оценки "сортированности" массива)
редактировать: Вау, много замечательных ответов. Да, я использую это как функцию пригодности для оценки качества сорта, выполняемого генетическим алгоритмом. Таким образом, стоимость оценки важна (т. е. она должна быть быстрой, предпочтительно O(n)
.)
в рамках приложения AI, с которым я играю, я хотел бы иметь возможность оценивать массив кандидатов целых чисел на основе его монотонности, он же его "сортированность". На данный момент я использую эвристику, которая вычисляет самый длинный отсортированный прогон, а затем делит его на длину массива:
public double monotonicity(int[] array) {
if (array.length == 0) return 1d;
int longestRun = longestSortedRun(array);
return (double) longestRun / (double) array.length;
}
public int longestSortedRun(int[] array) {
if (array.length == 0) return 0;
int longestRun = 1;
int currentRun = 1;
for (int i = 1; i < array.length; i++) {
if (array[i] >= array[i - 1]) {
currentRun++;
} else {
currentRun = 1;
}
if (currentRun > longestRun) longestRun = currentRun;
}
return longestRun;
}
Это хорошее начало, но оно не учитывает возможности того, что могут быть "сгустки" отсортированных суб-последовательностей. Например:
{ 4, 5, 6, 0, 1, 2, 3, 7, 8, 9}
этот массив разделен на три отсортированных под-последовательности. Мой алгоритм будет оценивать его как только 40% отсортированных, но интуитивно он должен получить более высокий балл. Есть ли стандартный алгоритм для такого рода вещи?
11 ответов
Я ожидаю, что выбор функции для использования очень сильно зависит от того, для чего вы собираетесь ее использовать. Основываясь на вашем вопросе, я бы предположил, что вы используете генетическую систему для создания программы сортировки, и это должна быть функция ранжирования. Если это так, то скорость выполнения имеет решающее значение. Исходя из этого, я уверен, что ваш алгоритм с самой длинной отсортированной подпоследовательностью будет работать довольно хорошо. Это звучит так, как будто это должно определить фитнес довольно хорошо.
Это кажется хорошим кандидатом для Левенштейна Дамерау–Левенштейна distance-количество свопов, необходимых для сортировки массива. Это должно быть пропорционально тому, как далеко каждый элемент находится от того места, где он должен быть в отсортированном массиве.
вот простой алгоритм ruby, который суммирует квадраты расстояний. Это кажется хорошей мерой сортированности-результат становится меньше каждый раз, когда два элемента не в порядке обменивать.
ap = a.sort
sum = 0
a.each_index{|i| j = ap.index(a[i])-i
sum += (j*j)
}
dist = sum/(a.size*a.size)
вот я только что придумала.
для каждой пары соседних значений вычислите числовую разницу между ними. Если второй больше или равен первому, добавьте это к sorted
total, в противном случае добавьте к unsorted
общий. Когда закончите, возьмите соотношение двух.
вычислите длины всех отсортированных подпоследовательностей, затем квадрат их и добавьте их. Если вы хотите откалибровать, сколько enphasis вы ставите на самый большой, используйте мощность, отличную от 2.
Я не уверен, что лучший способ нормализовать это по длине, может быть, разделить его на длину в квадрате?
то, что вы, вероятно, ищете, это Тау Кендалла. Это функция "один к одному" расстояния сортировки пузырьков между двумя массивами. Чтобы проверить, "почти отсортирован" ли массив, вычислите его Kendall Tau против отсортированного массива.
Я бы предложил посмотреть на Проблема Блин и расстояние разворота перестановок. Эти алгоритмы часто используются для поиска расстояния между двумя перестановками (идентификатором и перестановочной строкой). Эта мера расстояния должна учитывать больше сгустков значений порядка, а также развороты (монотонно убывающие вместо возрастающих подпоследовательностей). Есть также приближения, которые являются полиномиальным временем[PDF].
Это на самом деле все зависит от того, что означает число, и если эта функция расстояния имеет смысл в вашем контексте.
у меня такая же проблема (оценка монотонности), и я предлагаю вам попробовать Самая Длинная Возрастающая Подпоследовательность. Наиболее эффективный алгоритм работает в O(n log n)
не так уж и плохо.
взяв пример из вопроса, самая длинная возрастающая последовательность {4, 5, 6, 0, 1, 2, 3, 7, 8, 9}
is {0, 1, 2, 3, 7, 8, 9}
(протяженностью 7). Возможно, он лучше (70%), чем ваш алгоритм с самой длинной сортировкой.
Это сильно зависит от того, для чего вы собираетесь использовать меру, но один простой способ сделать это-ввести массив в стандартный алгоритм сортировки и измерить, сколько операций (свопов и/или сравнений) нужно сделать для сортировки массива.
некоторые эксперименты с модификатором Ratcliff & Obershelp
>>> from difflib import SequenceMatcher as sm
>>> a = [ 4, 5, 6, 0, 1, 2, 3, 7, 8, 9 ]
>>> c = [ 0, 1, 9, 2, 8, 3, 6, 4, 7, 5 ]
>>> b = [ 4, 5, 6, 0, 1, 2, 3, 7, 8, 9 ]
>>> b.sort()
>>> s = sm(None, a, b)
>>> s.ratio()
0.69999999999999996
>>> s2 = sm(None, c, b)
>>> s2.ratio()
0.29999999999999999
Так что делает то, что ему нужно. Хотя не знаю, как это доказать.
Как насчет подсчета количества шагов с увеличением значения против количества общих шагов. Это O(n)
.