整数数组中出现次数的峰值

时间:2012-05-21 01:25:07

标签: java performance algorithm histogram

在Java中,我需要一种算法来查找最大值。整数集合中出现的次数。例如,如果我的集合为[2,4,3,2,2,1,4,2,2],则算法需要输出5,因为2是最常出现的整数,它出现5次。考虑它就像找到整数集的直方图的峰值一样。

面临的挑战是,我必须逐个为多组整数做一遍,因此需要高效。另外,我不知道哪些元素大部分会出现在集合中。这完全是随机的。

我考虑过将这些值的值放入一个数组中,对它进行排序,然后迭代数组,计算数字的连续出现次数并确定计数的最大值,但我猜这将需要很长时间。是否有任何库或算法可以帮助我有效地做到这一点?

7 个答案:

答案 0 :(得分:3)

排序有什么问题?那是 O(n log n),这一点都不错。任何更好的解决方案都需要有关输入集的更多信息(可能涉及的数字的上限)或涉及Map<Integer, Integer>或等效的内容。

答案 1 :(得分:3)

我会使用以下逻辑将集合插入Map数据结构:

  • 如果尚未将整数插入到地图中,则插入key = integer,value = 1。
  • 如果密钥存在,请递增值。

您可以使用Java中的两个地图 - HashMap和TreeMap - 这些在下面进行比较:

HashMap与TreeMap

如果您愿意,可以跳过详细说明直接跳转到摘要。

HashMap是一个存储数组中键值对的Map。用于密钥k的索引是:

  • h.hashCode()%map.size()

有时两个完全不同的键最终会在同一个索引处。为了解决这个问题,数组中的每个位置实际上都是一个链表,这意味着每个查询总是必须遍历链表并使用k.equals(other)方法检查是否相等。最糟糕的情况是,所有密钥都存储在同一位置,HashMap成为未编制索引的列表。

随着HashMap获得更多条目,这些冲突的可能性增加,结构的效率降低。要解决这个问题,当条目数达到临界点(由构造函数中的loadFactor参数确定)时,结构将调整大小:

  • 新数组的分配大约是当前大小的两倍
  • 在所有现有密钥上运行循环
    • 为新阵列重新计算密钥的位置
    • 键值对将插入新结构

正如您所看到的,如果有许多调整大小,这可能会变得相对昂贵。

如果您可以在开始之前以适当的大小预先分配HashMap,则可以解决此问题,例如map = new HashMap(input.size()* 1.5)。对于大型数据集,这可以大大减少内存流失。

因为键基本上随机定位在HashMap中,所以键迭代器将以随机顺序迭代它们。 Java确实提供了LinkedHashMap,它将按照插入键的顺序进行迭代。

HashMap的性能:

  • 鉴于散列的正确大小和良好分布,查找是恒定时间。
  • 分布不良,性能下降到(在最坏的情况下)线性搜索 - O(n)。
  • 初始大小不好时,性能会变得糟糕。我无法轻易计算出这一点,但这并不好。

OTOH树形图将条目存储在平衡树中 - 动态结构随着键值对的增加而逐渐建立。 Insert取决于树的深度(log(tree.size()),但是可以预测 - 与HashMap不同,没有线程,也没有性能下降的边缘条件。

给定分布均匀的HashMap,每次插入和查找都会更加昂贵。

此外,为了在树中插入密钥,每个密钥必须与每个其他密钥相当,需要来自Comparable接口的k.compare(other)方法。显然,鉴于问题是关于整数,这不是问题。

TreeMap的性能:

  • 插入n个元素是O(n log n)
  • 查找是O(log n)

<强>摘要

首先想法:数据集大小:

  • 如果小(即使在1000年代和10,000年代),它在任何现代硬件上都无关紧要
  • 如果大,到了造成机器内存不足的程度,那么TreeMap可能是唯一的选择
  • 否则,尺寸可能不是决定因素

在这种特定情况下,关键因素是与整体数据集大小相比,预期的唯一整数数量是大还是小?

  • 如果很小,那么总时间将由小组中的键查找控制,因此优化无关紧要(您可以在此处停止)。
  • 如果大,那么总时间将由插入主导,并且决定取决于更多因素:
    • 数据集的大小是多少?
      • 如果是:可以预先分配HashMap,从而消除内存流失。如果hashCode()方法很昂贵(在我们的例子中不是)
      • ,这一点尤为重要
      • 如果否:TreeMap提供更可预测的性能,可能是更好的选择
    • 是否可预测性能,不需要大的停顿,例如在实时系统或GUI的事件线程上?
      • 如果是:TreeMap提供更好的可预测性,没有停顿
      • 如果否:HashMap可能为整个计算提供更好的整体性能

如果上面没有压倒性的观点,最后一点:

  • 是有序键值的排序列表吗?
    • 如果是(例如打印直方图):TreeMap已经对键进行了排序,因此方便

但是,如果性能很重要,唯一的决定方法是实现Map接口,然后 profile HashMap和TreeMap,看看哪种情况在你的情况下更好。 Premature optimization是邪恶的根源:)

答案 2 :(得分:2)

  1. 基本方法是对集合进行排序,然后只运行已排序的集合。 (这将在O(nLog(n)+ n)中完成,即O(nLog(n)))。

  2. 如果数字有界(例如,-10000,10000)并且集合包含大量整数,则可以使用查找表并计算每个元素。这将取O(n + 1)(O(n)用于计数,O(l)找到max元素)其中l是范围长度(在这种情况下为20001)。 如您所见,如果n&gt;&gt;然后,这将变为优于1的O(n),但是如果n <&lt;然后它是O(l),它是恒定的但足够大,使其无法使用。

  3. 上一个的另一个变体是使用HashTable而不是查找表。这将改善O(n)的复杂度,但是当n> 1时,不能保证快于2。 好消息是价值不必受限制。

  4. 我不是一个java,但如果你需要帮助编码这些,请告诉我。

答案 3 :(得分:1)

以下是程序的示例实现。它返回大多数频率的no,如果发现两个nos出现max maxncence,则返回较大的no。如果你想返回频率,那么将代码的最后一行改为“return mf”。

{public int mode(int[]a,int n)
   {int i,j,f,mf=0,mv=a[0];
    for(i=0;i<n;i++)
       {f=0;
        for(j=0;j<n;j++)
           {if(a[i]==a[j])
               {f++;
               }
           }
        if(f>mf||f==mf && a[i]>mv)
           {mf=f;
            mv=a[i];
           }
       }
    return mv;        
   }

}

答案 4 :(得分:1)

这只小狗工作(编辑以返回频率而不是数字):

public static int mostFrequent(int[] numbers) {
    Map<Integer, AtomicInteger> map = new HashMap<Integer, AtomicInteger>() {
        public AtomicInteger get(Object key) {
            AtomicInteger value = super.get(key);
            if (value == null) {
                value = new AtomicInteger();
                super.put((Integer) key, value);
            }
            return value;
        }

    };

    for (int number : numbers)
        map.get(number).incrementAndGet();

    List<Entry<Integer, AtomicInteger>> entries = new ArrayList<Map.Entry<Integer, AtomicInteger>>(map.entrySet());
    Collections.sort(entries, new Comparator<Entry<Integer, AtomicInteger>>() {
        @Override
        public int compare(Entry<Integer, AtomicInteger> o1, Entry<Integer, AtomicInteger> o2) {
            return o2.getValue().get() - o1.getValue().get();
        }
    });

    return entries.get(0).getValue().get(); // return the largest *frequency*

    // Use this next line instead to return the most frequent *number*
    // return entries.get(0).getKey(); 
}

选择AtomicInteger是为了避免每次增量都创建新对象,代码看起来更清晰。

匿名地图类用于集中“if null”代码

这是一个测试:

public static void main(String[] args) {
    System.out.println(mostFrequent(new int[] { 2, 4, 3, 2, 2, 1, 4, 2, 2 }));
}

输出:

5

答案 5 :(得分:1)

因为它是一个整数集合,所以可以使用

  1. 基数排序对集合进行排序,取O(nb),其中b是用于表示整数的位数(32或64,如果使用java的原始整数数据类型),或者
  2. 基于比较的排序(快速排序,合并排序等),需要O(n log n)。
  3. 注意:

    • 你的n越大,基数排序比基于比较的排序越快。对于较小的n,使用基于比较的排序可能会更好。
    • 如果您知道集合中值的界限,则b甚至会小于32(或64),这使得基数排序更合适。

答案 6 :(得分:0)

使用HashMap:

export const hideSnackBar = () => ({
    type: SNACKBAR_HIDE
});