TreeMap和HashMap的元素比較

CodeDancing 發表於 2021-04-07

寫在前面的話

2021.04,準備面試和CCF CSP認證的我準備做一套CCF模擬題,然後就有了此篇部落格(x
題目:201912-2 回收站報數
題目截圖:

TreeMap和HashMap的元素比較

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

但是在我看錯題目的解題過程中(hhh),出現了錯誤,準確的說是TreeSet的使用錯誤,這篇部落格記錄一下錯誤和過程。

TreeSet compare和contains

錯誤現象

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

錯誤程式碼

這是我進行判斷並且計算評分的程式碼:

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程式碼

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。

解決方案

使用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)

人生此處,絕對樂觀