大量数字中最常见的重复数字

时间:2009-09-10 00:28:46

标签: java data-structures performance

我有一个文件,其中有许多随机整数(大约一百万),每个整数由一个空格分隔。我需要在该文件中找到前10个最常出现的数字。在java中执行此操作的最有效方法是什么? 我能想到 1.创建哈希映射,键是文件中的整数,值是计数。对于文件中的每个数字,检查哈希映射中是否已存在该键,如果是,则值为++,否则在哈希中创建一个新条目 2.制作BST,每个节点都是文件中的整数。对于文件中的每个整数,如果是,则查看BST中是否有节点,执行value ++,value是节点的一部分。

如果我能提出良好的散列函数,我觉得哈希映射是更好的选择, 有人可以告诉我这样做最好吗?我可以使用其他任何有效的算法吗?

11 个答案:

答案 0 :(得分:7)

编辑#2:

好吧,我搞砸了自己的第一条规则 - 永远不要过早优化。最糟糕的情况可能是使用范围广泛的股票HashMap - 所以我就这样做了。它仍然在一秒钟内运行,所以忘记其他所有事情并且就这样做。

在担心棘手的实施之前,我会另外注意自己总是测试速度。

(以下是较旧的过时帖子,如果有人有超过一百万的点数,那么这个帖子仍然有效)

HashSet可以工作,但是如果你的整数有一个合理的范围(比如1-1000),那么创建一个1000个整数的数组会更有效,并且对于你的每个百万整数,增加阵列。 (与HashMap几乎相同的想法,但优化Hash必须允许的一些未知数应该使它快几倍)。

您还可以创建一棵树。树中的每个节点都包含(value,count),树将按值组织(左侧较低值,右侧较高)。遍历到您的节点,如果它不存在 - 插入它 - 如果是,则只增加计数。

您的值的范围和分布将决定这两个(或常规哈希)中的哪一个会表现得更好。我认为常规哈希不会有很多“获胜”的情况(它必须是一个宽范围和“分组”的数据,即使这样,树也可能会赢。

由于这非常简单 - 我建议您针对实际数据集实施多个解决方案和测试速度。

编辑:RE评论

TreeMap可以工作,但仍会添加一层间接(实现自己非常简单有趣)。如果使用stock实现,则必须使用Integers并在每次增加时不断地转换为int。指向Integer的指针是间接的,并且您存储的对象数量至少为2x。这甚至不计算方法调用的任何开销,因为它们应该内联运气。

通常这将是一个优化(邪恶),但是当你开始接近成千上万的节点时,你偶尔必须确保效率,所以内置的TreeMap因为内置的原因而效率低下-in HashSet会。

答案 1 :(得分:5)

Java处理散列。您不需要编写哈希函数。刚开始在哈希映射中推送东西。

此外,如果这只需要运行一次(或仅偶尔运行),那么不要两者都进行优化。它会足够快。如果它会在应用程序中运行,那就太麻烦了。

答案 2 :(得分:4)

HashMap中

一百万个整数并不是很多,即使对于解释型语言也是如此,尤其是对于像Java这样的快速语言。你可能几乎没有注意到执行时间。如果你认为这个太慢,我会先尝试这个并转向更复杂的事情。

使用HashMap进行字符串拆分和解析以转换为整数可能需要更长的时间,而不是最简单的算法来查找频率。

答案 3 :(得分:3)

为什么要使用哈希表?只需使用与数字范围相同的数组。然后,您不必浪费时间执行散列函数。然后在完成后对值进行排序。 O(N log N)

答案 4 :(得分:1)

  1. 分配与您拥有的输入项目数相同大小的数组/向量
  2. 使用数字填充文件中的数组,每个元素一个数字
  3. 按顺序列出
  4. 遍历列表并跟踪您遇到的前10位数字。
  5. 最后输出前十名。
  6. 作为第4步的改进,您只需要按照与第10个最长跑步相同的步骤向前迈进阵列。任何比这更长的运行都会与您的采样重叠。如果第十个最长运行长度为100个元素,则只需要对元素100,200,300进行采样,并在每个点计算在那里找到的整数运行(向前和向后)。任何超过第10个长度的运行肯定会与您的采样重叠。

    在第10次运行长度与数组中的其他运行相比非常长时,应该应用此优化。

    对于这个问题,地图是过度的,除非您的每个重复数字都很少的唯一数字。

    注意:与gshauger的答案相似,但充实了

答案 5 :(得分:1)

如果必须使其尽可能高效,请使用一个整数数组,其中位置代表值,内容代表计数。这样就可以避免自动装箱和拆箱,这是标准Java集合最可能的杀手锏。

如果数字范围太大,请查看PJC及其IntKeyIntMap实现。它也将避免自动装箱。不过,我不知道它对你来说是否足够快。

答案 6 :(得分:1)

如果数字范围很小(例如0-1000),请使用数组。否则,使用HashMap<Integer, int[]>,其中值均为长度为1的数组。每次想要增加值时,增加基元数组中的值比创建新的Integer要快得多。你仍然在为密钥创建Integer对象,但这很难避免。毕竟,创建一个2 ^ 31-1整数的数组是不可行的。

如果所有输入都已标准化,因此您没有像01而不是1这样的值,请在地图中使用字符串作为键,这样您就不必创建整数键。

答案 7 :(得分:1)

在遍历文件时,使用HashMap在内存中创建数据集(值 - 计数对)。在创建数据集时,HashMap应该让您接近O(1)访问元素(从技术上讲,在最坏的情况下,HashMap是O(n))。完成搜索文件后,对HashMap.values()返回的值Collection使用Collections.sort()来创建值 - 计数对的排序列表。使用Collections.sort()可以保证O(nLogn)。 例如:

public static class Count implements Comparable<Count> {
    int value;
    int count;
    public Count(int value) {
        this.value = value;
        this.count = 1;
    }
    public void increment() {
        count++;
    }
    public int compareTo(Count other) {
        return other.count - count;
    }
}

public static void main(String args[]) throws Exception {
    Scanner input = new Scanner(new FileInputStream(new File("...")));
    HashMap<Integer, Count> dataset = new HashMap<Integer, Count>();
    while (input.hasNextInt()) {
        int tempInt = input.nextInt();
        Count tempCount = dataset.get(tempInt);
        if (tempCount != null) {
            tempCount.increment();
        } else {
            dataset.put(tempInt, new Count(tempInt));
        }
    }

    List<Count> counts = new ArrayList<Count>(dataset.values());
    Collections.sort(counts);

答案 8 :(得分:1)

实际上,有一种O(n)算法可以完全按照您的要求进行操作。您的用例类似于LFU缓存,其中元素的访问计数确定它是在缓存中进行通信还是从中逐出。

http://dhruvbird.blogspot.com/2009/11/o1-approach-to-lfu-page-replacement.html

答案 9 :(得分:0)

这是java.lang.Integer.hashCode()的来源,如果您将条目存储为HashMap<Integer, Integer>,则会使用散列函数:

public int hashCode() {
return value;
}

换句话说,java.lang.Integer的(默认)哈希值本身就是整数。

什么比那更有效?

答案 10 :(得分:0)

正确的方法是使用链接列表。当你插入一个元素时,你沿着链接列表向下,如果它在那里增加节点数,否则创建一个计数为1的新节点。插入每个元素后,你将有一个排序的元素列表在O(n *的log(n))。

对于你的方法,你正在进行n次插入,然后在O(n * log(n))中进行排序,因此你的复杂度系数更高。