插入后计算HashMap值的平均值

时间:2014-05-16 13:55:25

标签: java performance algorithm map mean

每次插入新的键/值对时,我想有效地计算HashMap的两种值的方法。

假设我们目前有HashMap<Double, Double>

3 4
5 6
8 8
1 3
6 8 <- Latest insertion

最新的插入是值为6的键8

计算的第一个均值包括所有值,其中键小于插入的键,即6

这些是键4,6,3的值3,5,1,因此均值为(4+6+3)/3=4.3...

第二个意思是“相反”,所以所有键的所有值的平均值都大于6

值为8的密钥1将此均值设为8/1=8

现在,插入一个新的密钥/对:

3 4
5 6
6 8
8 8
1 3
4 9 <- Latest insertion

因此,我们需要计算密钥小于4的所有值的均值。

这些是键4,3的值3,1,因此“较小的均值”现在为(4+3)/2=3.5

键值/值对(6+8+8)/3=7.3...的“更大均值”现在为5/6,6/8,8/8

天真的实现可能是这样的:

public class CalculateMapMean {

        private double smallerMean = 0.0;
        private double greaterMean = 0.0;

        private HashMap<Double, Double> someMap = new HashMap<Double, Double>();

        public void calculateMeans(double latestInsertedKey) {
            double sumGreater = 0;
            double sumSmaller = 0;
            double sumGreaterCount = 0;
            double sumSmallerCount = 0;
            for (Map.Entry<Double, Double> entry : someMap.entrySet()) {
                double key = entry.getKey();
                double value = entry.getValue();
                if (key > latestInsertedKey) {
                    sumGreater += value;
                    ++sumGreaterCount;
                }
                else if (key < latestInsertedKey) {
                    sumSmaller += value;
                    ++sumSmallerCount;
                }
            }
            if (sumGreaterCount != 0) {
                greaterMean = sumGreater / sumGreaterCount;
            }
            else {
                greaterMean = 0.0;
            }
            if (sumSmallerCount != 0) {
                smallerMean = sumSmaller / sumSmallerCount;
            }
            else {
                smallerMean = 0.0;
            }
        }
    }

问题在于,使用TreeMap或其他数据可以显着改善平均值的计算,以便在每次插入时都不会迭代所有键。

是否有一种优雅的方式可以重复使用以前的计算?

3 个答案:

答案 0 :(得分:1)

我能想到的唯一一种方法是每次更改地图时都要花费O(n)时间来保持平衡的二叉搜索树(BBST)和键。在每个节点中,您需要保留一些额外的字段

  • 以该节点为根的子树中的节点数
  • 以该节点为根的所有节点的值之和

插入/删除后重新平衡BBST需要O(log n)次。在相同的余额操作中,您也可以在O(log n)时间内更新计数和总和(因为您执行O(log n)O(1)次操作。

要获得正确的方法,您需要遍历树并添加正确的计数。让我们举一个简单的例子。假设我有以下7个键值对。我希望你能想象相应的BBST会是什么样子。

(3, 5) (4, 3) (7, 1) (8, 4) (11, 3) (12, 1)(13, 3)

在根目录中 - (8, 4) - 存储总计数和总和:[7, 20]。在左子树的根中 - (4, 3) - 存储该子树的总计数和总和:[3, 9]。我现在将这些额外的值绘制为树中深度的函数:

[         7, 20        ]
[   3, 9   ][   3, 7   ]
[1, 5][1, 1][1, 3][1, 3]

假设我现在添加一个带有键10的新元组。我开始在根处遍历树。由于8 < 10,我不需要遍历左子树:该子树中的所有键都小于10,因此我们可以使用缓存的值[3, 9]。对于正确的子树,我们需要递归,因为某些键可能小于10而某些键可能更大。我们不必在那里遍历正确的子树,因为12 > 10,所以我们可以直接使用[1, 3]

在树的每一层中,我们可以忽略一个分支并递归另一个分支。因此,查找小于上一个插入密钥的密钥的总值和计数以及大于上一个插入密钥的密钥,也需要O(log n)时间。

答案 1 :(得分:1)

是的,TreeSet会有所帮助。

假设带有e=(k,v)的元素进入。如果将元组保存在树集中,则可以使用tailSet(e)来获取值大于{{1 }}。同样适用于v。然后,您通常可以找到这些集合中的数字的平均值,费用为headSet(e),并插入费用为O(n*log(n))的新元组。

我相信你可以通过使用平衡的二叉树来加快速度,除了键和值之外,还可以跟踪具有较低键的元素数量及其平均值。类似地,对于具有较高值的​​右分支的元素。然后,当一个新元素进入时,你二进制搜索它的插入点,并跟踪你遇到的平均值,适当地构建更高和更低数字的平均值。我认为实现平衡位是很棘手的,因为一切都会移动,你必须确保O(log(n))标签的完整性。

那就是说,我建议你只使用TreeSet。

答案 2 :(得分:-1)

您可以将这些值存储在您的实现中,例如:

public class MyHashMap extends HashMap<Double, Double> {
    private double sum = 0;

    @Override
    public void put(Double key, Double value) {
        super (key, value);
        if (containsKey(key)) {
            sum -= get(key);
        }
        sum += value;
        super(key, value);
    }

    @Override
    public void putAll(Map<? extends Double, ? extends Double> map) {
        for (Map.Entry<? extends Double, ? extends Double> entry: map) {
            put(entry.getKey(), entry.getValue());
        }
    }

    @Override
    public void remove(Object key) {
        Double value = get(key);
        if (value != null)
            sum -= value;
        super(key);
    }

    public double getMean() {
        return sum / size();
    }
}