TreeMap和HashMap的元素比較

寫在前面的話

2021.04,準備面試和CCF CSP認證的我準備作一套CCF模擬題,而後就有了此篇博客(x
題目:201912-2 回收站報數
題目截圖:java

第一個想法:讀取每一個垃圾的位置,存入TreeSet中,而後依次取出判斷是否能夠創建回收站和評分(不能夠創建回收站,評分爲-1)。
這時候就有讀者要問了:啊這你爲何要使用TreeSet呢?不使用HashSet呢?
我回答:由於輸出須要順序啊,HashSet輸出是(沒有順序的)不保證有序的,即有可能出現有序可是程序設計不能依賴於此。
讀者:噢,原來如此。
作完題目的我:一看你就是一大段文字不喜歡看(x,題目都沒看清(別罵了,我也是🤡),題目最後的輸出是須要統計每種得分的個數,不須要有序,用HashSet徹底是足夠了。
讀者:WTF,一種植物。node

可是在我看錯題目的解題過程當中(hhh),出現了錯誤,準確的說是TreeSet的使用錯誤,這篇博客記錄一下錯誤和過程。面試

TreeSet compare和contains

錯誤現象

在上述題目中,筆者使用的是TreeSet進行排序,而後每個依次判斷輸出,可是最後輸出的結果所有都是4,全部的垃圾點都是回收站而且評分都是4,即對於任意一個垃圾,周圍8個邊界都存在垃圾。數組

錯誤代碼

這是我進行判斷而且計算評分的代碼:ide

public static int score(int x, int y, Set<Point1912> set) {
        if (set.contains(new Point1912(x + 1, y)) && set.contains(new Point1912(x - 1, y)) && set.contains(new Point1912(x, y + 1)) && set.contains(new Point1912(x, y - 1))) {
            int sum = 0;
            sum += (set.contains(new Point1912(x + 1, y + 1)) ? 1 : 0);
            sum += (set.contains(new Point1912(x - 1, y + 1)) ? 1 : 0);
            sum += (set.contains(new Point1912(x + 1, y - 1)) ? 1 : 0);
            sum += (set.contains(new Point1912(x - 1, y - 1)) ? 1 : 0);
            return sum;
        } else {
            return -1;
        }
    }

座標類函數

class Point1912 {
    int x = 0;
    int y = 0;
    int seq = 0;
    
    public Point1912(int x, int y) {
        this.x = x;
        this.y = y;
    }

    public Point1912(int x, int y, int seq) {
        this.x = x;
        this.y = y;
        this.seq = seq;
    }

    @Override
    public boolean equals(Object obj) {
        Point1912 p = (Point1912) obj;
        return x == p.x && y == p.y;
    }

    @Override
    public int hashCode() {
        return Objects.hash(x, y);
    }
}

TreeSet代碼this

TreeSet<Point1912> set1 = new TreeSet<>(((o1, o2) -> o1.seq - o2.seq));

很明顯,在判斷的代碼中任意的set.contains()都返回了true,可是在座標類中我明確設計了equals和hashCode方法,保證只有座標相等的時候才能返回true。設計

調試

查看源碼

最終的比較源碼,位置在TreeMap.java調試

final Entry<K,V> getEntryUsingComparator(Object key) {
        @SuppressWarnings("unchecked")
            K k = (K) key;
        Comparator<? super K> cpr = comparator;
        if (cpr != null) {
            Entry<K,V> p = root;
            while (p != null) {
                int cmp = cpr.compare(k, p.key);
                if (cmp < 0)
                    p = p.left;
                else if (cmp > 0)
                    p = p.right;
                else
                    return p;
            }
        }
        return null;
    }

問題就出如今這裏了,TreeSet.contains()方法最終在這裏進行比較,重點在於該函數使用的是comparator,這個比較器即是TreeMap用於排序的比較器。由於進行判斷的元素seq都是默認值,因此判斷是否存在的函數要麼所有返回false,要麼所有返回true,由於我設置seq默認爲0,TreeSet中必定存在因此contains全爲true。code

解決方案

使用HashSet存儲座標,同時使用另外一個ArrayList或者數組存儲座標保證順序。
下面是HashSet.contains()方法的內容,位置HashMap.java

final Node<K,V> getNode(int hash, Object key) {
        Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
        if ((tab = table) != null && (n = tab.length) > 0 &&
            (first = tab[(n - 1) & hash]) != null) {
            if (first.hash == hash && // always check first node
                ((k = first.key) == key || (key != null && key.equals(k))))
                return first;
            if ((e = first.next) != null) {
                if (first instanceof TreeNode)
                    return ((TreeNode<K,V>)first).getTreeNode(hash, key);
                do {
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        return e;
                } while ((e = e.next) != null);
            }
        }
        return null;
    }

能夠這裏即是採用key.equals的方法進行比較,重寫equals起了做用。
同時這個first instance of TreeNode 能夠看到HashMap中若是同一個桶的元素超過了7個,在Java1.8中就就將鏈表轉換爲了紅黑樹,Node變成了TreeNode。

總結

TreeMap中元素的比較採用的是Comparator,或者是元素實現的Comparable接口;
HashMap中元素的比較即是元素的equals方法。
若是沒看錯題目,估計也沒這茬子事(嚶嚶嚶x.x)

人生此處,絕對樂觀

相關文章