Итак, вот решение O (n log n) в java.
long merge(int[] arr, int[] left, int[] right) {
int i = 0, j = 0, count = 0;
while (i < left.length || j < right.length) {
if (i == left.length) {
arr[i+j] = right[j];
j++;
} else if (j == right.length) {
arr[i+j] = left[i];
i++;
} else if (left[i] <= right[j]) {
arr[i+j] = left[i];
i++;
} else {
arr[i+j] = right[j];
count += left.length-i;
j++;
}
}
return count;
}
long invCount(int[] arr) {
if (arr.length < 2)
return 0;
int m = (arr.length + 1) / 2;
int left[] = Arrays.copyOfRange(arr, 0, m);
int right[] = Arrays.copyOfRange(arr, m, arr.length);
return invCount(left) + invCount(right) + merge(arr, left, right);
}
Это почти обычная сортировка слиянием, вся магия скрыта в функции слияния. Обратите внимание, что при сортировке алгоритм убирает инверсии. Алгоритм при слиянии считает количество удаленных инверсий (можно сказать разобрано).
Единственный момент, когда инверсии удаляются, - это когда алгоритм берет элемент с правой стороны массива и объединяет его с основным массивом. Количество инверсий, удаляемых этой операцией, равно количеству элементов, оставшихся от левого массива, которые необходимо объединить. :)
Надеюсь, это достаточно объяснительно.
left.length - i
счетчика инверсии? Я бы подумал, что имеет смысл просто добавить 1, поскольку вы попали в логический случай, когда сравнение между двумя подмассивами имеет больший левый элемент массива, чем правый. Кто-нибудь может объяснить мне это, как будто мне 5?arr
. Но это не одна инверсия. Вы нашли инверсии для всех элементов в левом массиве, которые больше 6. В нашем случае он также включает 8. Итак, 2 добавляется кcount
, что равноleft.length - i
.Я нашел его за время O (n * log n) следующим способом.
Возьмите A [1] и найдите его позицию в отсортированном массиве B с помощью двоичного поиска. Количество инверсий для этого элемента будет на единицу меньше, чем порядковый номер его позиции в B, поскольку каждое меньшее число, которое появляется после первого элемента A, будет инверсией.
2а. накапливает количество инверсий для счетчика переменной num_inversions.
2b. удалить A [1] из массива A, а также из соответствующей позиции в массиве B
Вот пример запуска этого алгоритма. Исходный массив A = (6, 9, 1, 14, 8, 12, 3, 2)
1: объединить сортировку и скопировать в массив B
В = (1, 2, 3, 6, 8, 9, 12, 14)
2: Возьмите A [1] и выполните двоичный поиск, чтобы найти его в массиве B
A [1] = 6
В = (1, 2, 3, 6 , 8, 9, 12, 14)
6 находится в 4-й позиции массива B, поэтому есть 3 инверсии. Мы знаем это, потому что 6 было на первой позиции в массиве A, поэтому любой элемент с более низким значением, который впоследствии появляется в массиве A, будет иметь индекс j> i (поскольку i в этом случае равен 1).
2.b: Удалить A [1] из массива A, а также из соответствующей позиции в массиве B (жирные элементы удаляются).
A = ( 6, 9, 1, 14, 8, 12, 3, 2) = (9, 1, 14, 8, 12, 3, 2)
В = (1, 2, 3, 6, 8, 9, 12, 14) = (1, 2, 3, 8, 9, 12, 14)
3: Повторите попытку с шага 2 на новых массивах A и B.
A [1] = 9
В = (1, 2, 3, 8, 9, 12, 14)
9 теперь находится на 5-й позиции массива B, поэтому есть 4 инверсии. Мы знаем это, потому что 9 было на первой позиции в массиве A, поэтому любой элемент с более низким значением, который появляется впоследствии, будет иметь индекс j> i (поскольку i в этом случае снова равен 1). Удалите A [1] из массива A, а также из соответствующей позиции в массиве B (жирные элементы удаляются)
A = ( 9 , 1, 14, 8, 12, 3, 2) = (1, 14, 8, 12, 3, 2)
В = (1, 2, 3, 8, 9 , 12, 14) = (1, 2, 3, 8, 12, 14)
Продолжение в этом ключе даст нам общее количество инверсий для массива A после завершения цикла.
Для выполнения шага 1 (сортировка слиянием) потребуется O (n * log n). Шаг 2 будет выполняться n раз, и при каждом выполнении будет выполняться двоичный поиск, для выполнения которого требуется O (log n), всего O (n * log n). Таким образом, общее время работы будет O (n * log n) + O (n * log n) = O (n * log n).
Спасибо за вашу помощь. Запись массивов образцов на листе бумаги действительно помогла визуализировать проблему.
источник
В Python
источник
Интересно, почему еще никто не упомянул деревья с двоичной индексацией . Вы можете использовать один для поддержания сумм префиксов для значений ваших элементов перестановки. Затем вы можете просто перейти справа налево и подсчитать для каждого элемента количество элементов, меньшее, чем справа:
Сложность составляет O (n log n), а постоянный коэффициент очень низкий.
источник
i -= i & -i
строки? И аналогичноi += i & -i
timeit
сравниваются все ответы Python на этот вопрос, поэтому он включает ваш код. Возможно, вам будет интересно посмотреть на результаты по времени.На самом деле у меня был вопрос, похожий на этот, для домашнего задания. Я был ограничен, что он должен иметь эффективность O (nlogn).
Я воспользовался предложенной вами идеей использования Mergesort, поскольку она уже имеет правильную эффективность. Я просто вставил некоторый код в функцию слияния, которая была в основном: всякий раз, когда число из массива справа добавляется к выходному массиву, я добавляю к общему количеству инверсий количество чисел, оставшихся в левом массиве.
Теперь, когда я достаточно подумал, это имеет для меня большой смысл. Вы подсчитываете, сколько раз перед числами идет большее число.
hth.
источник
Основная цель этого ответа - сравнить скорости различных версий Python, найденных здесь, но у меня также есть несколько собственных статей. (FWIW, я только что обнаружил этот вопрос во время поиска дубликатов).
Относительная скорость выполнения алгоритмов, реализованных в CPython, может отличаться от той, которую можно было бы ожидать от простого анализа алгоритмов и от опыта работы с другими языками. Это потому, что Python предоставляет множество мощных функций и методов, реализованных на C, которые могут работать со списками и другими коллекциями со скоростью, близкой к скорости, которую можно получить в полностью скомпилированном языке, поэтому эти операции выполняются намного быстрее, чем эквивалентные алгоритмы, реализованные «вручную» с помощью Python. код.
Код, использующий преимущества этих инструментов, часто может превзойти теоретически более совершенные алгоритмы, которые пытаются делать все с помощью операций Python над отдельными элементами коллекции. Конечно, на это влияет и фактическое количество обрабатываемых данных. Но для умеренных объемов данных код, использующий алгоритм O (n²), работающий на скорости C, может легко превзойти алгоритм O (n log n), который выполняет большую часть своей работы с отдельными операциями Python.
Многие из опубликованных ответов на этот вопрос о подсчете инверсии используют алгоритм, основанный на сортировке слиянием. Теоретически это хороший подход, если только размер массива не очень мал. Но встроенный в Python TimSort (гибридный стабильный алгоритм сортировки, полученный на основе сортировки слиянием и сортировки вставкой) работает со скоростью C, и сортировка слиянием, написанная вручную на Python, не может рассчитывать на то, что сможет конкурировать с ним по скорости.
Одно из наиболее интригующих решений здесь, в ответе, опубликованном Никласом Б , использует встроенную сортировку для определения ранжирования элементов массива и двоичное индексированное дерево (также известное как дерево Фенвика) для хранения совокупных сумм, необходимых для вычисления инверсии. считать. Пытаясь понять эту структуру данных и алгоритм Никласа, я написал несколько собственных вариаций (опубликованных ниже). Но я также обнаружил, что для списков умеренных размеров на самом деле быстрее использовать встроенную
sum
функцию Python, чем прекрасное дерево Фенвика.В конце концов, когда размер списка приближается к 500, аспект O (n²) вызова
sum
внутри этогоfor
цикла поднимает свою уродливую голову, и производительность начинает резко падать.Сортировка слиянием - не единственная сортировка O (nlogn), и несколько других могут использоваться для выполнения инверсионного подсчета. В ответе prasadvk используется сортировка двоичного дерева, однако его код, похоже, находится на С ++ или одной из его производных. Итак, я добавил версию Python. Первоначально я использовал класс для реализации узлов дерева, но обнаружил, что dict заметно быстрее. В конце концов я использовал список, который работает еще быстрее, хотя и делает код менее читаемым.
Одним из преимуществ treeort является то, что его намного проще реализовать итеративно, чем слияние. Python не оптимизирует рекурсию и имеет ограничение глубины рекурсии (хотя его можно увеличить, если вам это действительно нужно). И, конечно, вызовы функций Python относительно медленны, поэтому, когда вы пытаетесь оптимизировать скорость, лучше избегать вызовов функций, когда это возможно.
Другая сортировка O (nlogn) - это почтенная сортировка по основанию. Большим преимуществом является то, что он не сравнивает ключи друг с другом. Недостатком является то, что он лучше всего работает с непрерывными последовательностями целых чисел, в идеале перестановка целых чисел,
range(b**m)
гдеb
обычно 2. Я добавил несколько версий на основе сортировки по основанию после попытки прочитать Подсчет инверсий, Автономный подсчет ортогональных диапазонов и связанные проблемы, которые связаны при вычислении количества «инверсий» в перестановке .Чтобы эффективно использовать поразрядную сортировку для подсчета инверсий в общей последовательности
seq
длины n, мы можем создать перестановку,range(n)
которая имеет такое же количество инверсий, какseq
. Мы можем сделать это (в худшем случае) за время O (nlogn) через TimSort. Хитрость состоит в том, чтобы переставить индексыseq
путем сортировкиseq
. Это проще объяснить на небольшом примере.вывод
Путем сортировки пар (значение, индекс)
seq
мы переставили индексыseq
с тем же количеством свопов, которые необходимо поместитьseq
в исходный порядок из его отсортированного порядка. Мы можем создать эту перестановку, отсортировавrange(n)
подходящую ключевую функцию:вывод
Мы можем избежать этого
lambda
с помощьюseq
«s.__getitem__
метод:Это ненамного быстрее, но мы ищем все возможные улучшения скорости. ;)
Приведенный ниже код выполняет
timeit
тесты всех существующих алгоритмов Python на этой странице, а также некоторых моих собственных: пара версий O (n²) методом грубой силы, несколько вариантов алгоритма Никласа B и, конечно же, на основе сортировки слиянием. (который я написал без ссылки на существующие ответы). В нем также есть мой код древовидной сортировки на основе списков, примерно полученный из кода prasadvk, и различные функции, основанные на сортировке по основанию, некоторые из которых используют стратегию, аналогичную подходам сортировки слиянием, а некоторые используютsum
дерево Фенвика.Эта программа измеряет время выполнения каждой функции в серии случайных списков целых чисел; он также может проверить, что каждая функция дает те же результаты, что и другие, и что она не изменяет список ввода.
Каждый
timeit
вызов дает вектор, содержащий 3 результата, которые я сортирую. Основное значение здесь смотрите минимальную один, остальные значения лишь дают представление о том , как надежности , что минимальное значение, как описано в примечании в техtimeit
модулях документации .К сожалению, результат этой программы слишком велик для включения в этот ответ, поэтому я публикую его в отдельном ответе (вики сообщества) .
Результат - три запуска на моем древнем 32-битном одноядерном компьютере с частотой 2 ГГц, на котором запущен Python 3.6.0 на старом производном от Debian дистрибутиве. YMMV. Во время тестов я закрыл свой веб-браузер и отключился от маршрутизатора, чтобы минимизировать влияние других задач на ЦП.
При первом запуске проверяются все функции с размерами списков от 5 до 320, с размерами цикла от 4096 до 64 (при удвоении размера списка размер цикла уменьшается вдвое). Случайный пул, используемый для создания каждого списка, составляет половину размера самого списка, поэтому мы, вероятно, получим много дубликатов. Некоторые алгоритмы инверсионного подсчета более чувствительны к дубликатам, чем другие.
Во втором прогоне используются более крупные списки: от 640 до 10240 и фиксированный размер цикла 8. Для экономии времени из тестов исключаются некоторые из самых медленных функций. Мой перебором O (N²) функции просто путь слишком медленно в этих размерах, и , как упоминалось ранее, мой код , который использует
sum
, который делает так хорошо на малых и средних списков, просто не может держать на больших списках.Последний прогон охватывает списки размером от 20480 до 655360 и фиксированным размером цикла 4 с 8 самыми быстрыми функциями. Для списков размером менее 40 000 или около того код Тима Бабича - явный победитель. Молодец Тим! Код Niklas B также является хорошим универсальным исполнителем, хотя в меньших списках он проигрывает. Код «python», основанный на делении пополам, также работает довольно хорошо, хотя он кажется немного медленнее с огромными списками с множеством дубликатов, вероятно, из-за того линейного
while
цикла, который он использует для обхода дубликатов .Однако для списков очень большого размера алгоритмы, основанные на делении пополам, не могут конкурировать с настоящими алгоритмами O (nlogn).
Пожалуйста, смотрите здесь для вывода
источник
bisect
это C? Я почти уверен, что это Python.Количество инверсий можно узнать, проанализировав процесс слияния в сортировке слиянием:
При копировании элемента из второго массива в массив слияния (9 в этом примере) он сохраняет свое место относительно других элементов. При копировании элемента из первого массива в массив слияния (здесь 5) он инвертируется, и все элементы остаются во втором массиве (2 инверсии с 3 и 4). Таким образом, небольшая модификация сортировки слиянием может решить проблему за O (n ln n).
Например, просто раскомментируйте две строки # в приведенном ниже коде Python для сортировки слиянием, чтобы получить счетчик.
ИЗМЕНИТЬ 1
Эту же задачу можно решить с помощью стабильной версии быстрой сортировки, которая, как известно, немного быстрее:
Если выбрать опорный элемент в качестве последнего элемента, инверсии хорошо подсчитываются, а время выполнения на 40% лучше, чем слияние, указанное выше.
РЕДАКТИРОВАТЬ 2
Для производительности в python версия numpy & numba:
Сначала часть numpy, которая использует argsort O (n ln n):
И самое важное для эффективного подхода BIT :
источник
timeit
сравниваются все ответы Python на этот вопрос, поэтому он включает ваш код. Возможно, вам будет интересно посмотреть на результаты по времени.timeit
коллекцию.Обратите внимание, что ответ Джеффри Ирвинга неверен.
Возьмем для примера последовательность {3, 2, 1}. Есть три инверсии: (3, 2), (3, 1), (2, 1), поэтому число инверсии равно 3. Однако, согласно указанному методу, ответ был бы 2.
источник
Проверьте это: http://www.cs.jhu.edu/~xfliu/600.363_F03/hw_solution/solution1.pdf
Я надеюсь, что это даст вам правильный ответ.
источник
Вот одно из возможных решений с вариацией двоичного дерева. Он добавляет поле rightSubTreeSize к каждому узлу дерева. Продолжайте вставлять числа в двоичное дерево в том порядке, в котором они появляются в массиве. Если число идет влево от узла, счетчик инверсий для этого элемента будет (1 + rightSubTreeSize). Поскольку все эти элементы больше текущего, и они должны были появиться в массиве раньше. Если элемент переходит в правую часть узла, просто увеличьте его rightSubTreeSize. Ниже приведен код.
источник
if(p->data < q->data)
иначе дубликаты не будут обрабатываться правильно. И нет необходимости тестироватьq
наверху цикла, безусловныйwhile
цикл работает нормально. Кроме того, вы не упомянули, что это за язык. :) И ваша функция, похоже, потеряла строку заголовка.источник
Поскольку это старый вопрос, я отвечу на C.
источник
Вот решение С ++
источник
Этот ответ содержит результаты
timeit
тестов, произведенных кодом в моем основном ответе . Пожалуйста, смотрите этот ответ для подробностей!источник
Вот код C для инверсии счетчика
Подробное объяснение было дано здесь: http://www.geeksforgeeks.org/counting-inversions/
источник
O (n log n) время, O (n) космическое решение в java.
Сортировка слиянием с настройкой для сохранения количества инверсий, выполненных на этапе слияния. (для хорошо объясненной сортировки слиянием взгляните на http://www.vogella.com/tutorials/JavaAlgorithmsMergesort/article.html )
Поскольку сортировка слиянием может быть выполнена на месте, сложность пространства может быть улучшена до O (1).
При использовании этой сортировки инверсии происходят только на этапе слияния и только тогда, когда мы должны поместить элемент второй части перед элементами из первой половины, например
слился с
имеем 3 + 2 + 0 = 5 инверсий:
После того, как мы сделали 5 инверсий, наш новый объединенный список будет 0, 1, 5, 6, 10, 15, 22.
В Codility есть демонстрационная задача ArrayInversionCount, где вы можете протестировать свое решение.
источник
Вот реализация Perl O (n * log (n)):
источник
Мой ответ на Python:
1- Сначала отсортируйте массив и сделайте его копию. В моей программе B представляет отсортированный массив. 2- Перебрать исходный массив (несортированный) и найти индекс этого элемента в отсортированном списке. Также запишите индекс элемента. 3- Убедитесь, что элемент не имеет дубликатов, если они есть, вам нужно изменить значение вашего индекса на -1. Условие while в моей программе делает именно это. 4- Продолжайте подсчитывать инверсию, которая будет вашим значением индекса, и удалите элемент, как только вы рассчитали его инверсию.
источник
timeit
сравниваются все ответы Python на этот вопрос, поэтому он включает ваш код. Возможно, вам будет интересно посмотреть на результаты по времени.Ну, у меня есть другое решение, но я боюсь, что оно будет работать только для отдельных элементов массива.
Чтобы объяснить мой код, мы продолжаем добавлять элементы с конца массива. Для любого входящего элемента массива мы находим индекс первого элемента в векторе v, который больше, чем наш входящий элемент, и присваиваем это значение счетчику инверсии индекса входящего элемента . После этого мы вставляем этот элемент в вектор v в его правильную позицию так, чтобы вектор v оставался в отсортированном порядке.
источник
Еще одно решение Python, короткое. Использует встроенный модуль bisect, который предоставляет функции для вставки элемента на его место в отсортированном массиве и поиска индекса элемента в отсортированном массиве.
Идея состоит в том, чтобы хранить элементы слева от n-го в таком массиве, что позволило бы нам легко найти их количество больше n-го.
источник
timeit
сравниваются все ответы Python на этот вопрос, поэтому он включает ваш код. Возможно, вам будет интересно посмотреть на результаты по времени. : DПростой ответ O (n ^ 2) - использовать вложенные циклы for и увеличивать счетчик для каждой инверсии.
Теперь, я полагаю, вам нужно более эффективное решение, я подумаю.
источник
Одно из возможных решений в C ++, удовлетворяющих требованию временной сложности O (N * log (N)), будет следующим.
От обычной сортировки слиянием она отличается только счетчиком.
источник
Вот мое решение O (n log n) в Ruby:
И несколько тестовых примеров:
источник
Лучшим оптимизированным способом будет решить эту проблему с помощью сортировки слиянием, при которой слияние произойдет само, мы можем проверить, сколько инверсий требуется, сравнив левый и правый массив. Всякий раз, когда элемент в левом массиве больше, чем элемент в правом массиве, это будет инверсия.
Подход к сортировке слиянием: -
Вот код. Код точно такой же, как сортировка слиянием, за исключением фрагмента кода в
mergeToParent
методе, где я считаю инверсию при условии else(left[leftunPicked] < right[rightunPicked])
Другой подход, при котором мы можем сравнить входной массив с отсортированным массивом: - Это реализация ответа Diablo. Хотя это не должно быть предпочтительным подходом, поскольку удаление n элементов из массива или списка - это журнал (n ^ 2).
источник
Максимальное количество инверсий, возможное для списка размера,
n
можно обобщить с помощью выражения:Таким образом, для массива размера
6
максимально возможные инверсии будут равны15
.Чтобы добиться сложности,
n logn
мы могли бы использовать алгоритм инверсии при сортировке слиянием.Вот общие шаги:
inversionCount += leftSubArray.length
Это оно!
Это простой пример, который я сделал с помощью Javascript:
источник
Реализация подсчета инверсий в массиве с сортировкой слиянием в Swift:
Обратите внимание, что количество свопов увеличивается на
(что является относительной длиной левой части массива минус индекс текущего элемента в левой части)
... потому что это количество элементов, которые элемент в правой части массива должен был пропустить (количество инверсий), чтобы стать отсортированным.
источник
Большинство ответов основаны на,
MergeSort
но это не единственный способ решить эту проблему.O(nlogn)
Обсуду несколько подходов.
Использовать
Balanced Binary Search Tree
Что-то вроде этого.
Binary Indexed Tree
Segment Tree
[0, a[i]-1]
и обновлениемa[i] with 1
Также при использовании
BIT
илиSegment-Tree
хорошей идеей будет сделатьCoordinate compression
источник
C ++ Θ (n lg n) Решение с печатью пары, составляющей инверсию count.
источник
Используйте сортировку слиянием в инкрементном счетчике шага слияния, если число, скопированное на вывод, находится из правого массива.
источник
Недавно мне пришлось сделать это в R:
источник