Как вычислить медиану Карты <Интервал, Интервал>?

Для карты, где ключ представляет много последовательность и значение количество, как часто это число появилось в squence, как будет реализация алгоритма в Java быть похожими для вычисления медианы?

Например:

1,1,2,2,2,2,3,3,3,4,5,6,6,6,7,7

в карте:

Map<Int,Int> map = ...
map.put(1,2)
map.put(2,4)
map.put(3,3)
map.put(4,1)
map.put(5,1)
map.put(6,3)
map.put(7,2)

double median = calculateMedian(map);
print(median);

привел бы к:

> print(median);
3
>

Таким образом, то, что я ищу, является реализацией Java calculateMedian.

8
задан Chris 16 June 2010 в 13:24
поделиться

4 ответа

Линейное время

Если вам известна сумма чисел (в вашем случае это 16), вы можете перейти от начала или до конца карты и суммировать счетчики, пока не получите перейти к округлению (n / 2) -го элемента, или в случае, если сумма четна к среднему значению минимального (n / 2) -го и ceil (n / 2) -го элементов = медианы .

Если вы не знаете общий счет, вам придется пройти их все хотя бы один раз.

Сублинейное время

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

РЕДАКТИРОВАТЬ: Итак, в предположении, что у нас есть последовательность со счетчиками, мы можем сделать следующее:

  • при вставке пары ключ -> счетчик поддерживать другую карту - ключ -> running_total
  • таким образом вы будете иметь структуру, в которой вы сможете получить total_count, посмотрев на running_total последнего ключа
  • и , вы сможете выполнить двоичный поиск, чтобы найти элемент, где текущая сумма близка к total_count / 2

Это удвоит использование памяти, но даст производительность O (log n) для медианы и O (1) для total_count.

5
ответ дан 5 December 2019 в 12:55
поделиться
  • Использовать SortedMap , то есть TreeMap
  • Проходить по карте один раз, чтобы вычислить общее количество элементов, то есть сумму всех вхождений
  • Повторите еще раз и суммируйте вхождения, пока не дойдете до половины от общего числа. Число, которое привело к тому, что сумма превысила половину общей суммы, является медианным значением
  • . Тестирование на предмет единичных ошибок.
2
ответ дан 5 December 2019 в 12:55
поделиться

Для простого, но, возможно, не очень эффективного алгоритма, я бы сделал это так:

1. развернуть карту в список.

практически сказано: перебрать карту и добавить ключ «значение-время» в новый список. Наконец отсортируйте список.

//...
List<Integer> field = new ArrayList<Integer>();
for (Integer key:map) {
  for (int i = 0; i < map.get(key); i++) {
    field.add(key);
  }
}
Collections.sort(field);

2. вычислить медиану

, теперь вам нужно реализовать метод int calculateMedian (List sorted) . Это зависит от того, какой тип медианы вам нужен. Если это просто медиана выборки, то результатом будет либо самое среднее значение (для списков с нечетным числом элементов), либо среднее из двух крайних средних значений (для списков с четной длиной). Учтите, что список нужно отсортировать!

(Ссылка: Sample Median / wikipedia )


Хорошо, хорошо, хотя Крис не упомянул об эффективности, вот идея, как вычислить среднюю выборку (!) Без расширения карты. ..

Set<Integer> sortedKeys = new TreeSet<Integer>(map.keySet()); // just to be sure ;)
Integer median = null;  // Using Integer to have a 'invalid/not found/etc' state
int total = 0;
for (Integer key:sortedKeys) {
  total += map.get(key);
}
if (isOddNumber(total)) { // I don't have to implement everything, do I?
  int counter = total / 2;  // index starting with 0
  for (Integer key:sortedKeys) {
    middleMost -= map.get(key);
    if (counter < 0) {
      // the sample median was in the previous bin
      break;
    }
    median = key;
  }
} else {
  int lower = total/2;
  int upper = lower + 1;
  for (Integer key:sortedKeys) {
    lower -= map.get(key);
    upper -= map.get(key);
    if (lower < 0 && upper < 0) {
      // both middlemost values are in the same bin
      break;
    } else (lower < 0 || upper < 0) {
      // lower is in the previous, upper in the actual bin
      median = (median + key) / 2; // now we need the average
      break;
    }
    median = key;
  }
}

(Компилятора у меня под рукой нет - если в нем много синтаксических ошибок, относитесь к нему как к псевдокоду;))

1
ответ дан 5 December 2019 в 12:55
поделиться

Использование Guava:

Multiset<Integer> values = TreeMultiset.create();
Collections.addAll(values, 1,1,2,2,2,2,3,3,3,4,5,6,6,6,7,7);

Теперь ответ на ваш вопрос:

return Iterables.get(values, (values.size() - 1) / 2);

Действительно. Вот и все. (Или проверьте равномерность размера и усредните два центральных значения, чтобы быть точным.)

Если подсчеты особенно велики, было бы быстрее использовать entrySet мультисета и вести текущую сумму, но самый простой способ обычно подходит.

5
ответ дан 5 December 2019 в 12:55
поделиться
Другие вопросы по тегам:

Похожие вопросы: