如何计算Map <int,int>?</int,int>的中位数

时间:2010-06-16 11:47:18

标签: java algorithm median

对于一个地图,其中键表示一个序列的数量,并且该值计算该数字出现在序列中的频率,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
>

所以我要找的是calculateMedian的java实现。

4 个答案:

答案 0 :(得分:5)

线性时间

如果您知道数字的总数(在您的情况下是16),您可以从地图的开头或结尾开始,并总结计数,直到您得到第(n / 2)个元素,或者如果总和是平均值(n / 2)和ceil(n / 2)元素= median的平均值。

如果您不知道总计数,则必须至少完成一次。

次线性时间

如果您可以决定数据结构并且可以进行预处理,请参阅selection algorithm上的维基百科,您甚至可以获得次线性算法。 如果您对数据的分布有所了解,也可以获得次线性时间。

编辑: 因此,假设我们有一个计数序列,我们可以做的是

    插入key -> count对时,
  • 会维护另一张地图 - key -> running_total
  • 这样你就有了一个结构,你可以通过查看最后一个键的running_total来获得total_count
  • 您可以进行二元搜索,找到运行总计接近total_count / 2的元素

这会使内存使用量增加一倍,但会为中位数提供O(log n)性能,为total_count提供O(1)。

答案 1 :(得分:4)

使用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并保持运行总和会更快,但最简单的方法通常很好。

答案 2 :(得分:2)

  • 使用SortedMap,即TreeMap
  • 迭代地图一次以计算元素总数,即所有出现的总和
  • 再次迭代并累计出现,直到达到总数的一半。导致总和超过总数一半的数字是中位数
  • 广泛测试一对一错误

答案 3 :(得分:1)

对于简单但可能不那么高效的算法,我会这样做:

<强> 1。将地图展开到列表中。

实际说法:遍历地图并将键'value-times'添加到新列表中。最后对列表进行排序。

//...
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<Integer> 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;
  }
}

(我手边没有编译器 - 如果它有很多语法错误,请将其视为伪代码;)