TreeMap没有正确排序

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了TreeMap没有正确排序相关的知识,希望对你有一定的参考价值。

我试图根据“重量”对TreeMap进行排序。但由于某种原因,即使密钥不同,它也会删除具有相同权重值的条目。

以下是代码:

class Edge  {
    int source;
    int destination;
    int weight;

    public Edge(int source, int destination, int weight) {
        this.source = source;
        this.destination = destination;
        this.weight = weight;
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + destination;
        result = prime * result + source;
        result = prime * result + weight;
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        Edge other = (Edge) obj;
        if (destination != other.destination)
            return false;
        if (source != other.source)
            return false;
        if (weight != other.weight)
            return false;
        return true;
    }

    @Override
    public String toString() {
        return "Edge [source=" + source + ", destination=" + destination + ", weight=" + weight + "]";
    }
}

HashMap的数据:

{Edge [source = 0,destination = 1,weight = 5] = 5,Edge [source = 1,destination = 2,weight = 4] = 4,Edge [source = 2,destination = 3,weight = 5] = 5,Edge [source = 0,destination = 3,weight = 6] = 6,Edge [source = 0,destination = 2,weight = 3] = 3,Edge [source = 1,destination = 3,weight = 7] = 7}

Map<Edge, Integer> treemap = new TreeMap<>(new MyWeightComp());
    treemap.putAll(map);

树形图的比较:

 class MyWeightComp implements Comparator<Edge>{

    @Override
    public int compare(Edge e1, Edge e2) {
        return e1.weight-e2.weight;
    }
}

排序后的数据:

{Edge [source = 0,destination = 2,weight = 3] = 3,Edge [source = 1,destination = 2,weight = 4] = 4,Edge [source = 0,destination = 1,weight = 5] = 5,Edge [source = 0,destination = 3,weight = 6] = 6,Edge [source = 1,destination = 3,weight = 7] = 7}

因此,您可以看到,由于某种原因,即使密钥是源和目标的组合,也会删除具有相同权重的数据。

答案

所有映射都删除重复项,如果compareTo返回0,则假定它是相同的密钥。

class MyWeightComp implements Comparator<Edge> {

    @Override
    public int compare(Edge e1, Edge e2) {
        int cmp = Integer.compare(e1.weight, e2.weight); // handle overflows.
        if (cmp == 0)
            cmp = Integer.compare(e1.source, e2.source);
        if (cmp == 0)
            cmp = Integer.compare(e1.destination, e2.destination);
        return cmp;
    }
}

如果您有对排序不重要的字段,您仍然必须选择任意但一致的排序,如果您不希望为重复目的忽略它们。

您需要确保的密钥一致性是compare(a, b) == -compare(b, a)或更准确sign(compare(a, b)) == -sign(compare(b, a))

另一答案

TreeMap使用比较器比较键。

你的比较器返回0两个相同重量的键。因此,从TreeMap的角度来看,这些关键是平等的。

另一答案

Peter和talex已经回答了这个问题。我将添加一个稍微深入的分析,因为您提到了学习数据结构。

首先,ListSet / Map之间存在一个关键区别。列表可以包含重复项,集合不能包含重复项,映射不能包含重复键(这适用于标准地图,而不适用于multimaps)。实际上,集合是使用地图在内部实现的。

Map如何决定哪个项目是重复的?

HashMap使用两个函数Object.hashCodeObject.equals。您可以将print语句放入这些函数中:

    System.out.println(String.format("Edge.hashCode.%d.%d.%d", source, destination, weight));
    System.out.println(String.format("Edge.equals.%d.%d.%d", source, destination, weight));

让我们假设以下7个边缘列表:

    List<Edge> edges = Arrays.asList(
            new Edge(0, 1, 5),
            new Edge(1, 2, 4),
            new Edge(2, 3, 5),
            new Edge(0, 3, 6),
            new Edge(0, 3, 6), // duplicate
            new Edge(0, 2, 3),
            new Edge(1, 3, 7)
    );

现在,让我们把项目放入HashMap

    Map<Edge, Integer> hashMap = new HashMap<>();
    edges.forEach(edge -> hashMap.put(edge, edge.weight));
    hashMap.forEach((edge, value) -> System.out.printf("%s%n", edge));

生成的输出显示,HashMap如何决定,哪些项目是重复的,哪些不是:

Edge.hashCode.0.1.5
Edge.hashCode.1.2.4
Edge.hashCode.2.3.5
Edge.hashCode.0.3.6
Edge.hashCode.0.3.6
Edge.equals.0.3.6
Edge.hashCode.0.2.3
Edge.hashCode.1.3.7

您可以看到,HashMap知道,前四个项目不重复,因为它们有不同的哈希码。第五个值与第四个值具有相同的哈希码,而HashMap需要确认,这通过使用equals实际上是相同的Edge。 HashMap将包含6项内容:

Edge [source=0, destination=1, weight=5]
Edge [source=1, destination=2, weight=4]
Edge [source=2, destination=3, weight=5]
Edge [source=0, destination=3, weight=6]
Edge [source=0, destination=2, weight=3]
Edge [source=1, destination=3, weight=7]

让我们把相同的项目放入TreeMap

    SortedMap<Edge, Integer> treeMap = new TreeMap<>(new MyWeightComp());
    edges.forEach(edge -> treeMap.put(edge, edge.weight));
    treeMap.forEach((edge, value) -> System.out.printf("%s%n", edge));

这次,hashCodeequals根本没用过。相反,只使用compare

Edge.compare.0.1.5:0.1.5 // first item = 5
Edge.compare.1.2.4:0.1.5 // 4 is less than 5
Edge.compare.2.3.5:0.1.5 // 5 is already in the map, this item is discarded
Edge.compare.0.3.6:0.1.5 // 6 is more than 5
Edge.compare.0.3.6:0.1.5 // 6 is already in the map, this item is discarded
Edge.compare.0.3.6:0.3.6 // 6 is already in the map, this item is discarded
Edge.compare.0.2.3:0.1.5 // 3 is less than 5
Edge.compare.0.2.3:1.2.4 //   and also less than 4
Edge.compare.1.3.7:0.1.5 // 7 is more than 5
Edge.compare.1.3.7:0.3.6 //   and also more than 6

TreeMap仅包含5个项目:

Edge [source=0, destination=2, weight=3]
Edge [source=1, destination=2, weight=4]
Edge [source=0, destination=1, weight=5]
Edge [source=0, destination=3, weight=6]
Edge [source=1, destination=3, weight=7]

正如已经建议的那样,您可以通过不仅按重量比较,还可以通过所有其他字段来“修复”此问题。 Java8提供了一个很好的API来比较属性的“链”:

    Comparator<Edge> myEdgeComparator = Comparator
            .comparingInt(Edge::getWeight)
            .thenComparing(Edge::getSource)
            .thenComparing(Edge::getDestination);

但是,这可能表明,您根本不应该使用TreeMap进行排序。毕竟,您的初始要求可能如下:

  • 按重量排序
  • 保持所有边缘
  • 对具有相同权重的边缘进行排序并不重要

在这种情况下,您可能只需使用一个列表并对其进行排序:

    List<Edge> list = new ArrayList<>(edges);
    list.sort(myEdgeComparator);
    list.forEach(System.out::println);

或者使用Java8流:

    List<Edge> list2 = edges.stream().sorted(myEdgeComparator).collect(Collectors.toList());
    list2.forEach(System.out::println);

这些例子的源代码可以在here找到。

以上是关于TreeMap没有正确排序的主要内容,如果未能解决你的问题,请参考以下文章

TreeMap理解

如何使用预先排序的数据初始化 TreeMap?

TreeMap

treemap反序输出 逆序输出 输出倒数几个值

addToBackStack 没有导航回正确的片段

TreeMap按照value进行排序