【问题标题】:Java: [Performance] Storing and searching <Integer, Integer> for the most occuring oneJava:[Performance] 存储和搜索 <Integer, Integer> 中出现次数最多的
【发布时间】:2012-03-17 22:02:14
【问题描述】:

我有这个问题,解决它不是问题,更像是最快的方法。 所以我要求你们中更有经验的人帮助我找到一个快速的解决方案。

我有 People,每个定义为 1000 到 3000 之间的整数。 这些人中的每一个都可以分配给其他人,如下所示: 这些连接有一些规则,不会超过10000,但至少有一个 而且每一对人只能出现一次,所以和是不允许的! 目前,我将所有这些连接存储在 LinkedList 中,其中 Connection 是一个包含两个人的两个整数的类。

然后我需要找到在所有连接中出现次数最多的人,如果有多个,我需要将它们全部取消排序。

之后,我将遍历 LinkedList 并删除这些人参与的所有连接并重做该过程,直到列表为空。

我遇到的一些问题是并发访问或使用了错误的地图/列表以及一种缓慢的排序方法。

我目前没有代码,因为我看到了旧代码的性能并从头开始,现在除了处理输入之外什么都没有(女巫已经优化);)

对我最有帮助的是有人查看我的案例并告诉我他对不同数据类型的不同解决方案有多快的经验。我想主要是自己编写代码,我只需要一些提示如何正确地编写代码。

感谢您的关注,并希望得到答复。 如果有什么不清楚的地方,我对此表示歉意,并会在询问时澄清:)

【问题讨论】:

  • 您既想要 SetConnection 对象,又想要从集合中的每个 Person 到它所属的 Connections 的 Map。这两个都是 O(1) 的搜索,你只需要将它们包装在你自己的类中以确保它们是一致的。 (我会看看我是否可以破解一些代码。)
  • 与 ArrayList 相比,Java 中的 LinkedList 在大多数任务中的性能相当糟糕。与 ArrayList 相比,它做的一件事是当您通过 ListIterator 在列表中间进行大量插入/删除时。如果您不使用 ListIterator,那么性能又会很糟糕。
  • 我相信几个月前我在 SO 看到了同样的问题 - 闻起来像家庭作业......
  • @RonK 如果我问了一个已经回答的问题,我很抱歉,但我在开始提问之前已经搜索了一段时间。而且,无论是否推我,这都不是功课。就像我说的,我已经找到了解决我遇到的问题的方法,我只是想到了糟糕的性能并寻求帮助

标签: java performance sorting optimization integer


【解决方案1】:

如果我们以面向对象的方式来看,我们可以让每个人存储一个他们的朋友列表:

class Person {
    private Set<Person> friends = new HashSet<>();

    public void addFriend(Person newFriend) {
        friends.add(newFriend);
        newFriend.friends.add(this);
    }

    public void removeFriend(Person oldFriend) {
        friends.remove(oldFriend);
        oldFriend.friends.remove(this);
    }

    public int numberOfFriends() {
        return friends.size();
    }

    public void disappear() {
        for (Person friend : friends) {
            friend.friends.remove(this);
        }
    }
}

这种方法的优点是所有操作都在恒定的预期时间内完成。

这比保留一个友谊的链接列表要好得多,因为要找到一个人的朋友数量需要我们遍历所有 10000 个友谊的列表。

它也比 rogelware 描述的二维数组快得多,其中查找朋友的数量需要检查所有 2000 个其他人的友谊,而删除一个人需要清除所有 2000 个其他人的友谊。

【讨论】:

  • 谢谢你的回答,我能看到多远,这似乎是目前最优雅的解决方案,我会试试看,如果我写对了
  • 我对您的解决方案的一个问题是为您的 Person 类获取正确的哈希码方法,因为它引用了自身的 HashSet。也许简单地给每个人一个 ID 并使用这个 ID 作为哈希值会有所帮助,这会有点 hacky,但会提供快速且唯一的哈希值
  • 为什么会很老套?如果我想知道某人是否是乔,我不会问这个人和乔他们的朋友是谁,这样我就可以比较他们——我绝对不会问他们朋友的朋友。我只是询问他们的 ID 并将其与 Joe 的进行比较。
  • 你说得对,我只是忘记了每个人都有一个唯一的 id,这是在执行之前预定义的,所以应该没问题
  • 考虑到面向对象的方法,这个解决方案更好。考虑到最佳性能,使用数组更好。计数问题可以通过保持朋友数量的整数数组来解决。
【解决方案2】:

你所拥有的是一个无向图。存在一组节点之间有连接,并且每个连接都是双向的。

可以在here 中找到四种常见的图形表示。

您需要决定哪种表示最适合您的需求以及是否可以对其进行调整以提高性能。

我的建议是使用邻接列表,但让每个节点存储一个它链接到的所有节点的列表,以及另一个链接到它的所有节点的列表。

例如。

class Node {

    Integer personID;
    List<Integer> links;

}

// graph data type
Map<Integer, Node> graph;

现在,由于数据的存储方式,找出一个人总共有多少连接变得如此简单:

Integer personID = ...;
Node n = graph.get(personID);
int totalConnections = n.links.size();

然后您需要创建一个对象列表,其中存储人员 ID 和他们总共有多少链接,然后按总链接排序(这会将所有高总链接计数分组到列表末尾)。

当然,您必须确保在初始化阶段正确构建图形数据。

要记住的一点是,这种表示会在一定程度上增加图形的内存复杂度,但会显着降低算法的时间复杂度。在你的程序、时间或记忆中,你更看重什么?

但是,根据图中连接的密集程度,邻接矩阵可能更适合您的需求。

其他问题:

与 ArrayList 相比,Java 中的 LinkedList 在大多数任务中的性能都非常糟糕。与 ArrayList 相比,它做的一件事是当您通过 ListIterator 在列表中间进行大量插入/删除时。如果您不使用 ListIterator,那么性能又会很糟糕。由于 LinkedLists 的实现,java Collections API 中的默认排序算法对 LinkedLists 的排序性能很差;

使用 foreach 循环并在循环期间修改集合时,会发生集合 API 的并发访问异常。您需要使用 Iterator 或 ListIterator 遍历集合,并通过 Iterator/ListIterator 添加/删除元素。

【讨论】:

  • 哎呀,没有读到你放在那里的“不”。然而。边缘的方向性对整体解决方案意义不大。
  • 这意味着您需要以某种方式防止重复。不过总体思路确实是一样的。
  • 我在编写解决方案时让 Kostronor 解决,但 Meriton 对 Sets 的使用可能是最好的解决方案。
【解决方案3】:

如果空间不是问题,我会使用矩阵来存储连接。

第一个维度是 p1,第二个维度是 p2。我会有一个

boolean[][] connection = new boolean [2001][2001];

(我会考虑从 0 到 2000)。

当 455 和 985 之间有连接时,我必须检查两个方向。例如:

connection[455][985] = true;
connection[985][455] = true;

如果我想测试两个人之间是否有联系,我会这样做

 if(connection[455][985]) //the other end will have the same values

这会浪费太多空间,但它会非常快速且易于使用。

【讨论】:

  • 这是个好主意,但我需要找到出现次数最多的人,所以我不得不去:while(p1
  • 如果你总是使用较低的值作为第一个索引来设置和测试矩阵,你可以只存储一半的矩阵。但是需要更复杂的代码。例如,如果您有 963 和 223。您可以像 [223][963] 一样使用它,因此您不必存储 [963][223] 值。
  • 最简单的解决方案是将每个人的连接数存储在一个整数中。一个这样的数组供人们使用:int[] numberOfConnections = new int[2001]。然后将所有这些东西封装在一个类中,并根据需要增加/减少人员连接数。
【解决方案4】:

不要使用 LinkedList,使用 2 个元素的整数数组,或两个字段的特殊类。

class Relation {

    private int id1, id2;

    public Relation(int id1, int id2) {   
         if( id1 > id2 ) {   
             this.id2 = id1;
             this.id1 = id2;
         }
         else {
             this.id1 = id1;
             this.id2 = id2;
         }
    }


    public int hashCode() { 
        return id1 ^ id2;
    }

    public boolean equals(object o) {
        return 
             ((Relation)o).p1 == p1 &&
             ((Relation)o).p2 == p2;
    }

}

如果您需要检查唯一性,最后两种方法用于使用 HashSet

然后把你所有的关系放到HashSet&lt;Relation&gt;,也把它们备份成一些线性结构,比如数组或者Vector&lt;Relation&gt;

【讨论】:

    【解决方案5】:

    我在评论中的意思的粗略概述:

    class Person {
        long id;
    
        Person(long id) {
            this.id = id;
        }
    
        @Override
        public boolean equals(Object o) {
            // Compare by id
        }
    
        @Override
        public int hashCode() {
            // Hash by id
        }
    }
    

    连接

    class Connection {
        Person person1;
        Person person2;
    
        Connection(Person person1, Person person2) {
            if (person1.equals(person2)) throw new IllegalArgumentException("Cannot connect a person to itself");
    
            if (person1.id < person2.id) {
                this.person1 = person1;
                this.person2 = person2;
            } else {
                // The person1 field should contain the person with the smaller id
                this.person1 = person2;
                this.person2 = person1;
            }
        }
    
        @Override
        public boolean equals(Object o) {
            // Compare person1 and person2
        }
    
        @Override
        public int hashCode() {
            // Hash person1 and person2
        }
    }
    

    连接管理器

    class ConnectionManager {
        Set<Connection> connections = new HashSet<Connection>();
        Map<Person, Set<Person>> adjacency = new HashMap<Person, Set<Person>>();
    
        public void connect(Person p1, Person p2) {
            Connection connection = new Connection(p1, p2);
            if (connections.add(connection)) {
                getAdjacency(p1).add(p2);
                getAdjacency(p2).add(p1);
            } else {
                throw new RuntimeException(String.format("Persons %d and %d are already connected", p1.id, p2.id));
            }
        }
    
        private Set<Person> getAdjacency(Person person) {
            Set<Person> result = adjacency.get(person);
            if (result == null) {
                adjacency.put(person, result = new HashSet<Person>());
            }
            return result;
        }
    
        public void disconnect(Person p1, Person p2) {
            if (connections.remove(new Connection(p1, p2))) {
                getAdjacency(p1).remove(p2);
                getAdjacency(p2).remove(p1);
            } else {
                throw new RuntimeException(String.format("No connection between persons %d and %d exists", p1.id, p2.id));
            }
        }
    
        public Collection<Map.Entry<Person, Set<Person>>> getMostConnected() {
            int maxConnections = 0;
            List<Map.Entry<Person, Set<Person>>> result = new ArrayList<Map.Entry<Person, Set<Person>>>();
            // return all the entries with the maximum size;
    
            for (Map.Entry<Person, Set<Person>> entry : adjacency.entrySet()) {
                int connections = entry.getValue().size();
    
                if (connections > maxConnections) {
                    result.clear();
                    maxConnections=connections;
                }
    
                if (connections == maxConnections) {
                    result.add(entry);
                } 
            }
    
            return result;
        }
    
    
        public Set<Person> getConnections(Person person) {
            return new HashSet(getAdjacency(person));
        }
    }
    

    为简洁起见,省略了 Getters/setters 和 equals()/hashCode() 实现 - 无论 IDE 为后者生成什么都可以。

    这段代码本质上是一个矩阵,用邻接表表示。唯一不是 O(1) 的部分是搜索具有最多联系的人的部分,即 O(n)。

    您可以通过使用PriorityQueue 来降低性能损失,该PriorityQueue 保存存储在adjacency 映射中的Set&lt;Person&gt; 对象,并将设置大小作为“优先级”。每当这样的集合即将被触及时,将其从队列中移除、更改并再次插入。 (但我的直觉是,这只会让连接和断开连接的人变慢,从而让连接最多的人更快。)

    免责声明:以上代码完全未经测试,只是为了让您了解可以尝试的内容。

    【讨论】:

    • 呸,我错过了你提到你需要所有人都拥有最大连接数的部分。
    • Erm,真的没有必要排序来找到最大值,甚至所有最大值。简单地迭代列表,并跟踪迄今为止遇到的最大元素(所有同样大的元素)。 JDK 确实包含一个可变集合,允许有效检索最大元素:PriorityQueue。
    • ...如果我要实现 PriorityQueue,我会使用 heap,而不是二叉搜索树。
    • @meriton 是的。钻研数据结构和算法有时会让人忘记线性搜索并不总是坏事。我忘记了优先队列。由于它是一个堆,删除一个元素应该是 O(n),这可能会使树在添加/删除连接时更快。 (当然它可能不会,或者不足以让它值得与第三方树实现混淆。)我现在将更正我的答案中的代码。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-05-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多