Nếu bạn chỉ muốn biết liệu các tập hợp có bằng nhau hay không, equals
phương thức trên AbstractSet
được thực hiện gần như như sau:
public boolean equals(Object o) {
if (o == this)
return true;
if (!(o instanceof Set))
return false;
Collection c = (Collection) o;
if (c.size() != size())
return false;
return containsAll(c);
}
Lưu ý cách nó tối ưu hóa các trường hợp phổ biến trong đó:
- hai đối tượng giống nhau
- đối tượng kia hoàn toàn không phải là một tập hợp, và
- kích thước của hai bộ là khác nhau.
Sau đó, containsAll(...)
sẽ trả về false
ngay khi nó tìm thấy một phần tử trong tập hợp khác mà cũng không nằm trong tập hợp này. Nhưng nếu tất cả các phần tử có mặt trong cả hai tập hợp, nó sẽ cần phải kiểm tra tất cả chúng.
Do đó, hiệu suất trường hợp xấu nhất xảy ra khi hai tập hợp bằng nhau nhưng không cùng các đối tượng. Chi phí đó thường O(N)
hoặc O(NlogN)
tùy thuộc vào việc thực hiện this.containsAll(c)
.
Và bạn sẽ có được hiệu suất gần như trường hợp xấu nhất nếu các tập hợp lớn và chỉ khác nhau ở một tỷ lệ phần trăm nhỏ của các phần tử.
CẬP NHẬT
Nếu bạn sẵn sàng đầu tư thời gian vào việc triển khai tập hợp tùy chỉnh, có một cách tiếp cận có thể cải thiện trường hợp "gần như giống nhau".
Ý tưởng là bạn cần tính toán trước và lưu vào bộ nhớ cache một mã băm cho toàn bộ tập hợp để bạn có thể nhận được giá trị mã băm hiện tại của tập hợp O(1)
. Sau đó, bạn có thể so sánh mã băm cho hai bộ như một gia tốc.
Làm thế nào bạn có thể triển khai một mã băm như vậy? Chà nếu mã băm đã đặt là:
- 0 cho một tập hợp trống và
- XOR của tất cả các mã băm phần tử cho một tập hợp không trống,
sau đó, bạn có thể cập nhật mã băm được lưu trong bộ nhớ cache của tập hợp mỗi lần bạn thêm hoặc xóa một phần tử. Trong cả hai trường hợp, bạn chỉ cần XOR mã băm của phần tử với mã băm được đặt hiện tại.
Tất nhiên, điều này giả định rằng mã băm phần tử ổn định trong khi các phần tử là thành viên của tập hợp. Nó cũng giả định rằng hàm băm của các lớp phần tử tạo ra một sự lây lan tốt. Đó là bởi vì khi hai mã băm được đặt giống nhau, bạn vẫn phải quay lại O(N)
so sánh tất cả các phần tử.
Bạn có thể đưa ý tưởng này xa hơn một chút ... ít nhất là trên lý thuyết.
CẢNH BÁO - Điều này mang tính đầu cơ cao. Một "thử nghiệm suy nghĩ" nếu bạn thích.
Giả sử rằng lớp phần tử tập hợp của bạn có một phương thức trả về tổng kiểm tra tiền điện tử cho phần tử. Bây giờ triển khai các tổng kiểm tra của tập hợp bằng cách XOR các tổng kiểm tra được trả về cho các phần tử.
Cái này mua chúng ta cái gì?
Chà, nếu chúng ta giả định rằng không có gì ngầm đang xảy ra, thì xác suất để hai phần tử tập hợp không bằng nhau bất kỳ có cùng tổng kiểm tra N-bit là 2 -N . Và xác suất 2 tập không bằng nhau có tổng kiểm tra N bit giống nhau cũng là 2 -N . Vì vậy, ý tưởng của tôi là bạn có thể thực hiện equals
như:
public boolean equals(Object o) {
if (o == this)
return true;
if (!(o instanceof Set))
return false;
Collection c = (Collection) o;
if (c.size() != size())
return false;
return checksums.equals(c.checksums);
}
Theo giả định ở trên, điều này sẽ chỉ cung cấp cho bạn câu trả lời sai một lần trong thời gian 2 -N . Nếu bạn tạo N đủ lớn (ví dụ 512 bit) thì xác suất trả lời sai trở nên không đáng kể (ví dụ: khoảng 10 -150 ).
Nhược điểm là việc tính toán tổng kiểm tra tiền điện tử cho các phần tử rất tốn kém, đặc biệt là khi số lượng bit tăng lên. Vì vậy, bạn thực sự cần một cơ chế hiệu quả để ghi nhớ tổng kiểm tra. Và đó có thể là vấn đề.
Và nhược điểm khác là xác suất sai số khác 0 có thể không được chấp nhận cho dù xác suất đó có nhỏ đến đâu. (Nhưng nếu đúng như vậy thì ... làm thế nào để đối phó với trường hợp một tia vũ trụ lật một bit tới hạn? Hoặc nếu nó đồng thời lật cùng một bit trong hai trường hợp của một hệ thống dư thừa?)