Эффективно находить ранги элементов в массиве?
Как эффективно найти ранг каждого элемента массива, усредняя его в случае связей? Например:
float[] rank(T)(T[] input) {
// Implementation
}
auto foo = rank([3,6,4,2,2]); // foo == [3, 5, 4, 1.5, 1.5]
Единственный способ, который я могу придумать, требует выделения 3 массивов:
- дубликат входного массива, потому что он должен быть отсортирован, а мы им не владеем.
- массив для отслеживания порядка сортировки входного массива.
- массив возвращаемых рангов.
Знает ли кто-нибудь, как это сделать за O (N log N) время и O (1) вспомогательное пространство (имеется в виду, что единственный массив, который мы должны выделить, - это тот, который мы собираемся вернуть) или, по крайней мере, избавиться от одного из трех массивов выше?
7 ответов:
Вы можете выделить массив, который собираетесь вернуть (назовем его R), инициализировать его до 0..n-1, а затем "сортирует" входящий массив (называемый I), но используя сравнение I[R[k]] против I[R[j]] вместо обычного R[k] против R[j], а затем меняет значения в массиве R по мере необходимости (вместо значений в массиве I, как обычно).
Вы можете реализовать эту косвенную сортировку, используя либо quicksort, либо heapsort (или bubblesort, но это испортит вашу сложность).
Вам нужно только выделить один массив-и некоторое пространство стека для индексов.
Итак, вы дублируете входной массив в
foo
. Сортировкаfoo
на месте за O (N log n) время с помощьюheapsort . Теперь возьмите первый элемент вашего входного массива и найдите его ранг вfoo
за O(log n) время с помощью двоичного поиска , вставьте ранг в массивranks
и верните его.Теперь вы используете 2 массива вместо 3.
Если вы не владеете массивом, я не думаю, что это возможно сделать в O(N log N) и в пространстве O(1).
Если диапазон элементов (насколько большим может быть элемент) мал, используйте подсчет. Подсчитайте, сколько существует каждого элемента, а затем вычислите результирующий массив на основе входного массива с помощью Счетного массива.
c - is counting result, C - is cumulative counting C[i] = c[i] + c[i-1] + c[i-2] + ... + c[0] result[i] = 1 / c[in[i]] + C[in[i]-1]
Почему бы просто не скопировать и не отсортировать массив и не перейти оттуда? Существует множество алгоритмов сортировки на месте, таких как heapsort.
Возможно, было бы полезно обобщить ответ Флорина (и связанные с ним комментарии) с помощью некоторого простого кода.
Вот как это сделать в Ruby:
arr = [5,1,0,3,2,4] ranks = (0..arr.length-1).to_a.sort_by{ |x| arr[x] } # ranks => [2, 1, 4, 3, 5, 0]
И в Python:
arr = [5,1,0,3,2,4] ranks = range(len(arr)) ranks.sort(key=lambda x:arr[x]) # ranks => [2, 1, 4, 3, 5, 0]
Массив рангов говорит вам, что 0 имеет ранг 2, 1 имеет ранг 1, 2 имеет ранг 4 и т. д. (Конечно, эти ранги начинаются с нуля, а не с единицы.)
Как насчет использования бинарного дерева поиска и вставки элементов один за другим в это BST. Затем ранг можно определить, сохраняя счетчик на всех элементах, появляющихся слева от узла элемента, который мы хотим найти, используя ранг для обхода BST.
Я использовал это для быстрого и грязного выполнения в python:
Первый пример будет работать в том случае, если у вас нет дубликатов в исходном списке. Это можно сделать лучше, но я играл с некоторыми хаками и вышел с этим. Второй вариант будет работать, если у вас есть дубликаты.def rank(X): B = X[:] B.sort() return [ float(B.index(x)+1) for x in X] def rank(X): B = X[:] B = list(set(B)) B.sort() return [ float(B.index(x)+1) for x in X]