HashMap nhận / đặt độ phức tạp


131

Chúng ta thường nói rằng các HashMap get/puthoạt động là O (1). Tuy nhiên, nó phụ thuộc vào việc thực hiện băm. Hàm băm đối tượng mặc định thực sự là địa chỉ nội bộ trong heap JVM. Chúng tôi có chắc rằng nó đủ tốt để tuyên bố rằng get/putO (1) không?

Bộ nhớ khả dụng là một vấn đề khác. Theo tôi hiểu từ javadocs, HashMap load factornên là 0,75. Điều gì xảy ra nếu chúng ta không có đủ bộ nhớ trong JVM và load factorvượt quá giới hạn?

Vì vậy, có vẻ như O (1) không được bảo đảm. Liệu nó có ý nghĩa hay tôi đang thiếu một cái gì đó?


1
Bạn có thể muốn tìm kiếm khái niệm về độ phức tạp khấu hao. Xem ví dụ tại đây: stackoverflow.com/questions/3949217/time-complexity-of-hash-table Độ phức tạp trường hợp xấu nhất không phải là biện pháp quan trọng nhất cho bảng băm
Dr G

3
Đúng - nó được khấu hao O (1) - không bao giờ quên phần đầu tiên đó và bạn sẽ không có những loại câu hỏi này :)
Kỹ sư

Trường hợp xấu nhất phức tạp về thời gian là O (logN) kể từ Java 1.8 nếu tôi không sai.
Tarun Kolla

Câu trả lời:


216

Nó phụ thuộc vào nhiều thứ. Đó thường là O (1), với hàm băm khá, thời gian không đổi ... nhưng bạn có thể có một hàm băm mất nhiều thời gian để tính toán nếu có nhiều mục trong bản đồ băm trả về cùng mã băm, getsẽ phải lặp đi lặp lại qua việc họ kêu gọi equalstừng người trong số họ tìm trận đấu.

Trong trường hợp xấu nhất, a HashMapcó tra cứu O (n) do đi qua tất cả các mục trong cùng một nhóm băm (ví dụ: nếu tất cả chúng đều có cùng mã băm). May mắn thay, theo kịch bản tồi tệ nhất đó không xảy ra thường xuyên trong cuộc sống thực, theo kinh nghiệm của tôi. Vì vậy, không, O (1) chắc chắn không được đảm bảo - nhưng đó thường là những gì bạn nên giả sử khi xem xét sử dụng thuật toán và cấu trúc dữ liệu nào.

Trong JDK 8, HashMapđã được điều chỉnh sao cho nếu các khóa có thể được so sánh để đặt hàng, thì bất kỳ nhóm có mật độ dân số cao nào cũng được triển khai dưới dạng cây, do đó ngay cả khi có nhiều mục có cùng mã băm, độ phức tạp là O (log n). Điều đó có thể gây ra vấn đề nếu bạn có một loại khóa trong đó sự bình đẳng và trật tự là khác nhau, tất nhiên.

Và vâng, nếu bạn không có đủ bộ nhớ cho bản đồ băm, bạn sẽ gặp rắc rối ... nhưng điều đó sẽ đúng với bất kỳ cấu trúc dữ liệu nào bạn sử dụng.


@marcog: Bạn giả sử O (n log n) cho một lần tra cứu ? Điều đó nghe thật buồn tẻ. Tất nhiên, nó sẽ phụ thuộc vào độ phức tạp của hàm băm và hàm bằng, nhưng điều đó khó có thể phụ thuộc vào kích thước của bản đồ.
Jon Skeet

1
@marcog: Vậy bạn giả sử là O (n log n) là gì? Chèn n mục?
Jon Skeet

1
+1 cho câu trả lời hay. Bạn có vui lòng cung cấp các liên kết như mục wikipedia này cho bảng băm trong câu trả lời của bạn không? Bằng cách đó, người đọc quan tâm hơn có thể hiểu được sự hiểu biết sâu sắc về lý do tại sao bạn đưa ra câu trả lời của mình.
David Weiser

2
@SleimanJneidi: Vẫn là nếu khóa không triển khai <T> `có thể so sánh - nhưng tôi sẽ cập nhật câu trả lời khi tôi có nhiều thời gian hơn.
Jon Skeet

1
@ ip696: Có, putlà "khấu hao O (1)" - thường là O (1), đôi khi O (n) - nhưng hiếm khi đủ để cân bằng.
Jon Skeet

9

Tôi không chắc mã băm mặc định là địa chỉ - Tôi đã đọc nguồn OpenJDK để tạo mã băm cách đây một thời gian và tôi nhớ nó là một thứ gì đó phức tạp hơn một chút. Vẫn không phải là một cái gì đó đảm bảo một phân phối tốt, có lẽ. Tuy nhiên, đó là ở một mức độ nào đó, vì một số lớp bạn sử dụng làm khóa trong hashmap sử dụng mã băm mặc định - chúng cung cấp các triển khai của riêng chúng, điều này phải tốt.

Trên hết, điều bạn có thể không biết (một lần nữa, điều này dựa trên nguồn đọc - không được bảo đảm) là HashMap khuấy băm trước khi sử dụng nó, để trộn entropy từ trong suốt từ vào các bit dưới cùng, đó là nơi nó cần thiết cho tất cả trừ các hashtag lớn nhất. Điều đó giúp đối phó với băm mà cụ thể là không tự làm điều đó, mặc dù tôi không thể nghĩ ra bất kỳ trường hợp phổ biến nào mà bạn thấy điều đó.

Cuối cùng, điều xảy ra khi bảng bị quá tải là nó suy biến thành một tập hợp các danh sách được liên kết song song - hiệu suất trở thành O (n). Cụ thể, trung bình số lượng liên kết đi qua sẽ bằng một nửa hệ số tải.


6
Chết tiệt. Tôi chọn để tin rằng nếu tôi không phải gõ cái này trên màn hình cảm ứng của điện thoại di động, tôi có thể đánh bại Jon Sheet để đánh đấm. Có một huy hiệu cho điều đó, phải không?
Tom Anderson

8

Hoạt động HashMap là yếu tố phụ thuộc của việc thực hiện hashCode. Đối với kịch bản lý tưởng, giả sử triển khai băm tốt cung cấp mã băm duy nhất cho mọi đối tượng (Không có xung đột băm) thì kịch bản trường hợp tốt nhất, xấu nhất và trung bình sẽ là O (1). Chúng ta hãy xem xét một kịch bản trong đó việc triển khai hashCode không tốt luôn trả về 1 hoặc hàm băm đó có xung đột băm. Trong trường hợp này, độ phức tạp thời gian sẽ là O (n).

Bây giờ đến phần thứ hai của câu hỏi về bộ nhớ, thì có ràng buộc bộ nhớ sẽ được JVM chăm sóc.


8

Người ta đã đề cập rằng hashmap O(n/m)ở mức trung bình, nếu nlà số lượng vật phẩm và mlà kích thước. Nó cũng đã được đề cập rằng về nguyên tắc, toàn bộ sự việc có thể thu gọn thành một danh sách liên kết đơn với O(n)thời gian truy vấn. (Tất cả điều này giả định rằng việc tính toán hàm băm là thời gian không đổi).

Tuy nhiên, điều không thường được đề cập là, với xác suất ít nhất 1-1/n(vì vậy với 1000 mặt hàng có cơ hội 99,9%), thùng lớn nhất sẽ không được lấp đầy nhiều hơn O(logn)! Do đó phù hợp với độ phức tạp trung bình của cây tìm kiếm nhị phân. (Và hằng số là tốt, ràng buộc chặt chẽ hơn (log n)*(m/n) + O(1)).

Tất cả những gì cần thiết cho ràng buộc lý thuyết này là bạn sử dụng hàm băm khá hợp lý (xem Wikipedia: Universal Hashing . Nó có thể đơn giản như a*x>>m). Và tất nhiên, người cung cấp cho bạn các giá trị để băm không biết bạn đã chọn các hằng số ngẫu nhiên như thế nào.

TL; DR: Với Xác suất rất cao, trường hợp xấu nhất có được / độ phức tạp của hàm băm là O(logn).


(Và lưu ý rằng không ai trong số này giả định dữ liệu ngẫu nhiên. Xác suất xuất phát hoàn toàn từ sự lựa chọn hàm băm)
Thomas Ahle

Tôi cũng có câu hỏi tương tự về độ phức tạp thời gian chạy của tra cứu trong bản đồ băm. Có vẻ như đó là O (n) vì các yếu tố không đổi được cho là bị loại bỏ. 1 / m là một yếu tố không đổi và do đó được bỏ đi để lại O (n).
nickdu

4

Tôi đồng ý với:

  • độ phức tạp khấu hao chung của O (1)
  • hashCode()việc thực hiện không tốt có thể dẫn đến nhiều va chạm, điều đó có nghĩa là trong trường hợp xấu nhất, mọi đối tượng sẽ vào cùng một nhóm, do đó O ( N ) nếu mỗi nhóm được hỗ trợ bởi a List.
  • kể từ Java 8, HashMapthay thế động các Nút (danh sách được liên kết) được sử dụng trong mỗi nhóm bằng TreeNodes (cây đỏ đen khi danh sách lớn hơn 8 phần tử) dẫn đến hiệu suất O ( logN ) kém nhất.

Nhưng, đây không phải là sự thật đầy đủ nếu chúng ta muốn chính xác 100%. Việc triển khai hashCode()và loại khóa Object(không thay đổi / lưu trữ hoặc là Bộ sưu tập) cũng có thể ảnh hưởng đến độ phức tạp thực sự trong các điều khoản nghiêm ngặt.

Hãy giả sử ba trường hợp sau:

  1. HashMap<Integer, V>
  2. HashMap<String, V>
  3. HashMap<List<E>, V>

Họ có cùng độ phức tạp không? Vâng, độ phức tạp khấu hao của cái thứ nhất, như mong đợi, O (1). Nhưng, đối với phần còn lại, chúng ta cũng cần tính toán hashCode()phần tử tra cứu, điều đó có nghĩa là chúng ta có thể phải duyệt qua các mảng và danh sách trong thuật toán của mình.

Giả sử rằng kích thước của tất cả các mảng / danh sách trên là k . Sau đó, HashMap<String, V>HashMap<List<E>, V>sẽ có độ phức tạp khấu hao O (k) và tương tự, trường hợp xấu nhất O ( k + logN ) trong Java8.

* Lưu ý rằng việc sử dụng Stringkhóa là một trường hợp phức tạp hơn, bởi vì nó là bất biến và Java lưu trữ kết quả của hashCode()một biến riêng tư hash, do đó, nó chỉ được tính một lần.

/** Cache the hash code for the string */
    private int hash; // Default to 0

Nhưng, ở trên cũng có trường hợp xấu nhất của riêng nó, bởi vì String.hashCode()việc triển khai Java đang kiểm tra xem hash == 0trước khi tính toán hashCode. Nhưng này, có những Chuỗi không trống tạo ra hashcodesố 0, chẳng hạn như "f5a5a608", xem ở đây , trong trường hợp đó, việc ghi nhớ có thể không hữu ích.


2

Trong thực tế, nó là O (1), nhưng đây thực sự là một sự đơn giản hóa khủng khiếp và không có ý nghĩa về mặt toán học. Ký hiệu O () cho biết thuật toán ứng xử như thế nào khi kích thước của vấn đề có xu hướng vô cùng. Hashmap get / put hoạt động giống như thuật toán O (1) cho kích thước giới hạn. Giới hạn khá lớn từ bộ nhớ máy tính và từ quan điểm địa chỉ, nhưng xa vô tận.

Khi người ta nói rằng hashmap get / put là O (1) thì thực sự nên nói rằng thời gian cần thiết cho get / put là ít nhiều không đổi và không phụ thuộc vào số lượng phần tử trong hashmap cho đến khi hashmap có thể trình bày trên hệ thống máy tính thực tế. Nếu vấn đề vượt quá kích thước đó và chúng ta cần các hashtag lớn hơn thì sau một thời gian, chắc chắn số bit mô tả một phần tử cũng sẽ tăng lên khi chúng ta hết các phần tử khác nhau có thể mô tả. Ví dụ: nếu chúng tôi sử dụng hashmap để lưu trữ các số 32 bit và sau đó chúng tôi tăng kích thước bài toán để chúng tôi sẽ có nhiều hơn 2 ^ 32 bit trong hashmap, thì các phần tử riêng lẻ sẽ được mô tả với hơn 32 bit.

Số lượng bit cần thiết để mô tả các phần tử riêng lẻ là log (N), trong đó N là số phần tử tối đa, do đó get và put thực sự là O (log N).

Nếu bạn so sánh nó với một tập hợp cây, đó là O (log n) thì tập băm là O (dài (max (n)) và chúng tôi chỉ đơn giản cảm thấy rằng đây là O (1), bởi vì trên một max (n) thực hiện nhất định được cố định, không thay đổi (kích thước của các đối tượng chúng ta lưu trữ được đo bằng bit) và thuật toán tính mã băm là nhanh.

Cuối cùng, nếu tìm thấy một phần tử trong bất kỳ cấu trúc dữ liệu nào là O (1), chúng ta sẽ tạo ra thông tin ngoài không khí mỏng. Có cấu trúc dữ liệu của n phần tử tôi có thể chọn một phần tử theo n cách khác nhau. Với điều đó, tôi có thể mã hóa thông tin bit log (n). Nếu tôi có thể mã hóa số bit đó bằng 0 (đó là ý nghĩa của O (1)) thì tôi đã tạo ra một thuật toán ZIP nén vô hạn.


Không phải là sự phức tạp cho bộ cây O(log(n) * log(max(n))), sau đó? Trong khi so sánh ở mọi nút có thể thông minh hơn, trong trường hợp xấu nhất nó cần kiểm tra tất cả các O(log(max(n))bit, phải không?
maaartinus
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.