寫在前面的話
2021.04,準備面試和CCF CSP認證的我準備做一套CCF模擬題,然後就有了此篇部落格(x
題目:201912-2 回收站報數
題目截圖:
第一個想法:讀取每個垃圾的位置,存入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)