【问题标题】:Peak Value of Number of Occurences in Array of Integers整数数组中出现次数的峰值
【发布时间】:2012-05-27 13:27:48
【问题描述】:

在 Java 中,我需要一个算法来找到最大值。整数集合中出现的次数。例如,如果我的集合是[2,4,3,2,2,1,4,2,2],则算法需要输出 5,因为 2 是出现次数最多的整数,它出现了 5 次。将其视为寻找整数集的直方图的峰值。

挑战是,我必须对多个整数的多个集合一个一个地做,所以它需要高效。另外,我不知道哪个元素将主要出现在集合中。这是完全随机的。

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

【问题讨论】:

  • 为避免混淆,您可能需要将单词“set”更改为“collection”,因为术语“set”在 Java 中具有特定含义,并且 Java 集不允许重复。考虑使用HashMap<Integer, Integer> 来保存数字(键)及其频率(值)。
  • 您是实际将值插入整数集中的人吗?还是它们来自某些外部来源。
  • 我在括号内明确指出,但以防万一。
  • 它们是由外部来源计算的,因此它们是完全随机的。

标签: java performance algorithm histogram


【解决方案1】:

排序有什么问题?那是 O(n log n),一点也不差。任何更好的解决方案要么需要有关输入集的更多信息(可能是所涉及数字的上限),要么涉及Map<Integer, Integer> 或类似的东西。

【讨论】:

    【解决方案2】:
    1. 基本方法是对集合进行排序,然后简单地运行排序后的集合。 (这将在 O(nLog(n) + n) 中完成,即 O(nLog(n)))。

    2. 如果数字是有界的(例如,-10000,10000)并且集合包含很多整数,您可以使用查找表并计算每个元素。这将花费 O(n + l)(O(n) 进行计数,O(l) 找到最大元素)其中 l 是范围长度(在这种情况下为 20001)。 正如你所看到的,如果 n >> l 那么这将变成 O(n),它比 1 好,但是如果 n

    3. 前面的另一个变体是使用哈希表而不是查找表。这会将复杂度提高到 O(n),但不能保证在 n>>l 时快于 2。 好消息是这些值不必有界。

    我不太会 java,但如果您需要帮助编写这些代码,请告诉我。

    【讨论】:

      【解决方案3】:

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

      • 如果整数尚未插入映射,则插入 key=integer, value=1。
      • 如果键存在,则增加值。

      您可以使用 Java 中的两个 Map - 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 TreeMap 将条目存储在平衡树中 - 一种动态结构,随着键值对的添加而逐渐建立。插入取决于树的深度 (log(tree.size()),但可预测 - 与 HashMap 不同,没有中断,也没有性能下降的边缘条件。

      考虑到分布良好的 HashMap,每次插入和查找的成本都更高。

      此外,为了在树中插入键,每个键都必须与其他所有键可比较,这需要 Comparable 接口中的 k.compare(other) 方法。显然,鉴于问题是关于整数的,这不是问题。

      TreeMap 的性能:

      • n 个元素的插入是 O(n log n)
      • 查找时间为 O(log n)

      总结

      第一想法:数据集大小:

      • 如果很小(即使在 1000 和 10,000 中),在任何现代硬件上都无关紧要
      • 如果大到导致机器内存不足的地步,那么 TreeMap 可能是唯一的选择
      • 否则,大小可能不是决定因素

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

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

      如果从上方没有压倒性的一点,最后一点:

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

      但是,如果性能很重要,唯一的决定方法是实现 Map 接口,然后 profile HashMap 和 TreeMap 以查看哪个在您的情况下实际上更好。 Premature optimization 是万恶之源 :)

      【讨论】:

      • 感谢您的回答。在这个例子中使用 TreeMap 而不是 HashMap 有什么好处?
      • 我对我的帖子进行了重大修改以解决您的问题。总结总结:如果数据集很小,性能可能太接近而无需担心,TreeSet 会按排序顺序为您提供数字。对于大型数据集(数百万、数十亿甚至更大),如果您有内存,HashMap 可能会更好,但您应该分析性能。
      • 多么棒的答案。非常感谢您的时间和精力。您不仅清楚地启发了我有关解决方案的信息,而且还回答了我关于 HashMap 与 TreeMap 的许多问题和歧义。就我而言,整数的数量很少,所以我会尝试一下 HashMap。干得好。
      【解决方案4】:

      这是您的程序的示例实现。它返回频率最高的否,如果找到两个出现次数最多的否,则返回较大的否。如果您想返回频率,请将代码的最后一行更改为“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;        
         }
      

      }

      【讨论】:

      • 'n'是数组'a'的元素个数
      【解决方案5】:

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

      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
      

      【讨论】:

      • 谢谢,但我不是在寻找数字本身,而是在寻找它的频率。
      • 没问题 - 只返回值,而不是数字。查看更新的代码。
      【解决方案6】:

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

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

      注意事项:

      • n 越大,基数排序就越有可能比基于比较的排序更快。对于较小的 n,您可能最好使用基于比较的排序。
      • 如果您知道集合中值的界限,b 甚至会小于 32(或 64),从而使基数排序更理想。

      【讨论】:

        【解决方案7】:

        使用 HashMap:

          import java.util.HashMap;
        public class NumberCounter {
        
           static    HashMap<Integer,Integer> map;
           static int[] arr = {1, 2, 1, 23, 4, 5, 4, 1, 2, 3, 12, 23};
           static int max=0;
        
           public NumberCounter(){
        
        
                 map=new HashMap<Integer, Integer>();
        
            }
        
            public static void main (String[] args)
            {
                Integer newValue=1;
                NumberCounter c=new NumberCounter();
        
                for(int i=0;i<arr.length;i++){
                    if(map.get(arr[i])!=null) {
                        newValue = map.get(arr[i]);
                        newValue += 1;
                        map.put(arr[i], newValue);
                    }
                    else
                        map.put(arr[i],1);
        
        
                }
        
                max=map.get(arr[0]);
                for(int i=0;i<map.size();i++){
                 if(max<map.get(arr[i]))
                     max=map.get(arr[i]);
                }
                System.out.print(max);
        
            }
        
        }
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2021-12-29
          相关资源
          最近更新 更多