Là một hashmap Java thực sự O (1)?


159

Tôi đã thấy một số tuyên bố thú vị về các hashtag SO re Java và O(1)thời gian tra cứu của chúng . Ai đó có thể giải thích tại sao điều này là như vậy? Trừ khi các hashtag này khác rất nhiều so với bất kỳ thuật toán băm nào tôi đã mua, phải luôn tồn tại một tập dữ liệu có chứa các va chạm.

Trong trường hợp đó, việc tra cứu sẽ O(n)thay vìO(1) .

Ai đó có thể giải thích liệu họ O (1) và, nếu vậy, làm thế nào họ đạt được điều này?


1
Tôi biết điều này có thể không phải là một câu trả lời nhưng tôi nhớ Wikipedia có một bài viết rất hay về điều này. Đừng bỏ lỡ phần phân tích hiệu suất
người chiến thắng hugo

28
Ký hiệu Big O đưa ra giới hạn trên cho loại phân tích cụ thể bạn đang thực hiện. Bạn vẫn nên xác định xem bạn có quan tâm đến trường hợp xấu nhất, trường hợp trung bình, v.v.
Dan Homerick

Câu trả lời:


127

Một tính năng đặc biệt của HashMap là không giống như, cây cân bằng, hành vi của nó là xác suất. Trong những trường hợp này, thông thường hữu ích nhất để nói về sự phức tạp về khả năng xảy ra trường hợp xấu nhất xảy ra. Đối với bản đồ băm, tất nhiên đó là trường hợp va chạm liên quan đến mức độ đầy đủ của bản đồ. Một vụ va chạm là khá dễ dàng để ước tính.

p va chạm = n / công suất

Vì vậy, một bản đồ băm với một số lượng nhỏ các yếu tố có khả năng gặp phải ít nhất một vụ va chạm. Ký hiệu Big O cho phép chúng ta làm một cái gì đó hấp dẫn hơn. Quan sát rằng đối với bất kỳ k tùy ý, cố định k.

O (n) = O (k * n)

Chúng tôi có thể sử dụng tính năng này để cải thiện hiệu suất của bản đồ băm. Thay vào đó chúng ta có thể nghĩ về xác suất của tối đa 2 vụ va chạm.

p va chạm x 2 = (n / công suất) 2

Đây là thấp hơn nhiều. Vì chi phí xử lý một vụ va chạm thêm không liên quan đến hiệu suất Big O, chúng tôi đã tìm ra cách cải thiện hiệu suất mà không thực sự thay đổi thuật toán! Chúng ta có thể nói chung điều này với

p va chạm xk = (n / công suất) k

Và bây giờ chúng ta có thể bỏ qua một số va chạm tùy ý và kết thúc với khả năng va chạm rất nhỏ của sự va chạm nhiều hơn chúng ta đang tính toán. Bạn có thể có được xác suất đến một mức độ nhỏ tùy ý bằng cách chọn đúng k, tất cả mà không làm thay đổi việc triển khai thực tế của thuật toán.

Chúng tôi nói về điều này bằng cách nói rằng bản đồ băm có quyền truy cập O (1) với xác suất cao


Ngay cả với HTML, tôi vẫn không thực sự hài lòng với các phân số. Làm sạch chúng nếu bạn có thể nghĩ ra một cách tốt đẹp để làm điều đó.
SingleNegationElimination

4
Trên thực tế, những gì ở trên nói rằng các hiệu ứng O (log N) bị chôn vùi, đối với các giá trị không cực trị của N, bởi chi phí cố định.
Hot Licks

Về mặt kỹ thuật, con số bạn đưa ra là giá trị dự kiến ​​của số lần va chạm, có thể bằng xác suất của một vụ va chạm.
Simon Kuang

1
Điều này có giống với phân tích khấu hao không?
mấtsoul29

1
@ OleV.V. hiệu suất tốt của HashMap luôn phụ thuộc vào phân phối tốt chức năng băm của bạn. Bạn có thể giao dịch chất lượng băm tốt hơn cho tốc độ băm bằng cách sử dụng chức năng băm mật mã trên đầu vào của bạn.
SingleNegationElimination

38

Bạn dường như trộn lẫn hành vi trong trường hợp xấu nhất với thời gian chạy trường hợp trung bình (dự kiến). Cái trước thực sự là O (n) cho các bảng băm nói chung (nghĩa là không sử dụng băm hoàn hảo) nhưng điều này hiếm khi có liên quan trong thực tế.

Bất kỳ việc thực hiện bảng băm đáng tin cậy nào, cùng với một nửa băm khá, đều có hiệu suất truy xuất O (1) với hệ số rất nhỏ (trên thực tế) trong trường hợp dự kiến, trong phạm vi phương sai rất hẹp.


6
Tôi đã luôn nghĩ giới hạn trên là trường hợp xấu nhất nhưng có vẻ như tôi đã nhầm - bạn có thể có giới hạn trên đối với trường hợp trung bình. Vì vậy, có vẻ như những người tuyên bố O (1) nên đã nói rõ rằng đó là trường hợp trung bình. Trường hợp xấu nhất là một tập dữ liệu có nhiều va chạm làm cho nó O (n). Điều đó có ý nghĩa bây giờ.
paxdiablo

2
Có lẽ bạn nên làm rõ rằng khi bạn sử dụng ký hiệu O lớn cho trường hợp trung bình, bạn đang nói về một giới hạn trên của hàm thời gian chạy dự kiến ​​là một hàm toán học được xác định rõ ràng. Nếu không, câu trả lời của bạn không có nhiều ý nghĩa.
ldog

1
gmatt: Tôi không chắc rằng tôi hiểu sự phản đối của bạn: ký hiệu big-O là một giới hạn trên của hàm theo định nghĩa . Vì vậy, những gì tôi có thể có nghĩa là gì?
Konrad Rudolph

3
thông thường trong tài liệu máy tính, bạn thấy ký hiệu O lớn đại diện cho một hàm trên trong thời gian chạy hoặc các hàm phức tạp không gian của thuật toán. Trong trường hợp này, phần trên thực sự nằm trên sự mong đợi mà bản thân nó không phải là một hàm mà là một toán tử trên các hàm (Biến ngẫu nhiên) và thực tế là một tích phân (lebesgue.) cho và không tầm thường.
ldog

31

Trong Java, HashMap hoạt động bằng cách sử dụng hashCode để xác định vị trí một nhóm. Mỗi thùng là một danh sách các vật phẩm nằm trong thùng đó. Các mục được quét, sử dụng bằng để so sánh. Khi thêm các mục, HashMap được thay đổi kích thước sau khi đạt được tỷ lệ phần trăm tải nhất định.

Vì vậy, đôi khi nó sẽ phải so sánh với một vài mặt hàng, nhưng nhìn chung nó gần với O (1) hơn nhiều so với O (n). Đối với mục đích thực tế, đó là tất cả những gì bạn cần biết.


11
Chà, vì big-O được cho là chỉ định các giới hạn, nên sẽ không có sự khác biệt nào dù nó có gần với O (1) hay không. Ngay cả O (n / 10 ^ 100) vẫn là O (n). Tôi nhận thấy quan điểm của bạn về hiệu quả mang lại sau đó giảm tỷ lệ nhưng điều đó vẫn đặt thuật toán ở O (n).
paxdiablo

4
Phân tích bản đồ băm thường dựa trên trường hợp trung bình, đó là O (1) (có thông đồng) Trong trường hợp xấu nhất, bạn có thể có O (n), nhưng đó thường không phải là trường hợp. liên quan đến sự khác biệt - O (1) có nghĩa là bạn có cùng thời gian truy cập bất kể số lượng mục trên biểu đồ và đó thường là trường hợp (miễn là có tỷ lệ tốt giữa kích thước của bảng và 'n ')
Liran Orevi

4
Điều đáng chú ý là nó vẫn chính xác là O (1), ngay cả khi quá trình quét của thùng mất một thời gian vì đã có một số yếu tố trong đó. Miễn là các thùng có kích thước tối đa cố định, đây chỉ là một yếu tố không đổi không liên quan đến phân loại O (). Nhưng tất nhiên có thể có nhiều yếu tố hơn với các khóa "tương tự" được thêm vào, để các thùng này tràn ra và bạn không thể đảm bảo một hằng số nữa.
sth

@sth Tại sao các thùng sẽ có kích thước tối đa cố định!?
Navin

31

Hãy nhớ rằng o (1) không có nghĩa là mỗi lần tra cứu chỉ kiểm tra một mục duy nhất - điều đó có nghĩa là số lượng vật phẩm trung bình được kiểm tra vẫn không đổi số lượng vật phẩm trong vật chứa. Vì vậy, nếu mất trung bình 4 so sánh để tìm một vật phẩm trong một thùng chứa có 100 vật phẩm, thì cũng cần trung bình 4 so sánh để tìm một vật phẩm trong một thùng chứa với 10000 vật phẩm và cho bất kỳ số lượng vật phẩm nào khác (luôn luôn có một một chút khác biệt, đặc biệt là xung quanh các điểm mà bảng băm thử lại và khi có một số lượng rất nhỏ các mục).

Vì vậy, các va chạm không ngăn container chứa các hoạt động o (1), miễn là số lượng khóa trung bình trên mỗi thùng vẫn nằm trong một ràng buộc cố định.


16

Tôi biết đây là một câu hỏi cũ, nhưng thực sự có một câu trả lời mới cho nó.

Bạn nói đúng rằng bản đồ băm không thực sự O(1) , nói đúng, bởi vì số lượng phần tử trở nên lớn tùy ý, cuối cùng bạn sẽ không thể tìm kiếm trong thời gian liên tục (và ký hiệu O được xác định theo số có thể nhận lớn tùy ý).

Nhưng nó không theo sự phức tạp thời gian thực là O(n) - bởi vì không có quy tắc nào nói rằng các thùng phải được thực hiện như một danh sách tuyến tính.

Trong thực tế, Java 8 thực hiện các nhóm TreeMapskhi chúng vượt quá ngưỡng, điều này tạo ra thời gian thực tế O(log n).


4

Nếu số lượng xô (gọi là b) được giữ không đổi (trường hợp thông thường), thì việc tra cứu thực sự là O (n).
Khi n trở nên lớn, số phần tử trong mỗi nhóm trung bình n / b. Nếu độ phân giải va chạm được thực hiện theo một trong những cách thông thường (ví dụ danh sách được liên kết), thì tra cứu là O (n / b) = O (n).

Ký hiệu O là về những gì xảy ra khi n ngày càng lớn hơn. Nó có thể gây hiểu nhầm khi áp dụng cho các thuật toán nhất định và các bảng băm là một trường hợp điển hình. Chúng tôi chọn số lượng thùng dựa trên số lượng yếu tố chúng tôi mong muốn xử lý. Khi n có cùng kích thước với b, thì việc tra cứu gần như là thời gian không đổi, nhưng chúng ta không thể gọi nó là O (1) vì O được định nghĩa theo giới hạn là n →.



2

Chúng tôi đã thiết lập rằng mô tả chuẩn của tra cứu bảng băm là O (1) đề cập đến thời gian dự kiến ​​trường hợp trung bình, không phải là hiệu suất trường hợp xấu nhất nghiêm ngặt. Đối với bảng băm giải quyết các xung đột với chuỗi (như hàm băm của Java), đây là kỹ thuật O (1 + α) với hàm băm tốt , trong đó α là hệ số tải của bảng. Vẫn không đổi miễn là số lượng đối tượng bạn lưu trữ không nhiều hơn một hệ số không đổi lớn hơn kích thước bảng.

Điều đó cũng được giải thích rằng nói một cách nghiêm túc rằng có thể xây dựng đầu vào yêu cầu tra cứu O ( n ) cho bất kỳ hàm băm xác định nào. Nhưng cũng thật thú vị khi xem xét thời gian dự kiến ​​trong trường hợp xấu nhất , khác với thời gian tìm kiếm trung bình. Sử dụng chuỗi này là O (1 + chiều dài của chuỗi dài nhất), ví dụ (log n / log log n ) khi α = 1.

Nếu bạn quan tâm đến các cách lý thuyết để đạt được thời gian tìm kiếm trường hợp xấu nhất được mong đợi, bạn có thể đọc về băm hoàn hảo động để giải quyết các va chạm theo cách đệ quy với một bảng băm khác!


2

Đó là O (1) chỉ khi chức năng băm của bạn rất tốt. Việc thực hiện bảng băm Java không bảo vệ chống lại các hàm băm xấu.

Việc bạn có cần tăng bảng khi bạn thêm các mục hay không không liên quan đến câu hỏi vì đó là về thời gian tra cứu.


2

Các phần tử bên trong HashMap được lưu trữ dưới dạng một mảng của danh sách (nút) được liên kết, mỗi danh sách được liên kết trong mảng biểu thị một nhóm cho giá trị băm duy nhất của một hoặc nhiều khóa.
Trong khi thêm một mục trong HashMap, mã băm của khóa được sử dụng để xác định vị trí của nhóm trong mảng, đại loại như:

location = (arraylength - 1) & keyhashcode

Ở đây & đại diện cho toán tử bitwise AND.

Ví dụ: 100 & "ABC".hashCode() = 64 (location of the bucket for the key "ABC")

Trong quá trình vận hành, nó sử dụng cùng một cách để xác định vị trí của thùng cho khóa. Trong trường hợp tốt nhất, mỗi khóa có mã băm duy nhất và kết quả là một nhóm duy nhất cho mỗi khóa, trong trường hợp này, phương thức get chỉ dành thời gian để xác định vị trí của nhóm và lấy giá trị không đổi O (1).

Trong trường hợp xấu nhất, tất cả các khóa đều có cùng mã băm và được lưu trong cùng một nhóm, điều này dẫn đến việc duyệt qua toàn bộ danh sách dẫn đến O (n).

Trong trường hợp của java 8, nhóm Danh sách được liên kết được thay thế bằng TreeMap nếu kích thước tăng lên hơn 8, điều này làm giảm hiệu quả tìm kiếm trường hợp xấu nhất thành O (log n).


1

Điều này về cơ bản áp dụng cho hầu hết các triển khai bảng băm trong hầu hết các ngôn ngữ lập trình, vì bản thân thuật toán không thực sự thay đổi.

Nếu không có va chạm nào trong bảng, bạn chỉ phải thực hiện một lần tra cứu, do đó thời gian chạy là O (1). Nếu có sự va chạm hiện tại, bạn phải thực hiện nhiều hơn một lần tra cứu, điều này làm giảm hiệu suất về phía O (n).


1
Điều đó giả định rằng thời gian chạy bị giới hạn bởi thời gian tra cứu. Trong thực tế, bạn sẽ tìm thấy rất nhiều tình huống trong đó hàm băm cung cấp ranh giới (Chuỗi)
Stephan Eggermont

1

Nó phụ thuộc vào thuật toán bạn chọn để tránh va chạm. Nếu việc triển khai của bạn sử dụng chuỗi riêng biệt thì trường hợp xấu nhất xảy ra trong đó mọi phần tử dữ liệu được băm đến cùng một giá trị (ví dụ: sự lựa chọn kém của hàm băm). Trong trường hợp đó, tra cứu dữ liệu không khác với tìm kiếm tuyến tính trên danh sách được liên kết, tức là O (n). Tuy nhiên, xác suất xảy ra điều đó là không đáng kể và các trường hợp tra cứu tốt nhất và trung bình không đổi, tức là O (1).


1

Về mặt học thuật, từ góc độ thực tế, HashMaps nên được chấp nhận là có tác động hiệu suất không quan trọng (trừ khi trình hồ sơ của bạn nói với bạn khác.)


4
Không có trong các ứng dụng thực tế. Ngay khi bạn sử dụng một chuỗi làm khóa, bạn sẽ nhận thấy rằng không phải tất cả các hàm băm đều lý tưởng và một số thực sự rất chậm.
Stephan Eggermont

1

Chỉ trong trường hợp lý thuyết, khi mã băm luôn khác nhau và xô cho mỗi mã băm cũng khác nhau, O (1) sẽ tồn tại. Mặt khác, nó là thứ tự không đổi, tức là khi tăng hashmap, thứ tự tìm kiếm của nó không đổi.


0

Tất nhiên hiệu năng của hashmap sẽ phụ thuộc vào chất lượng của hàm hashCode () cho đối tượng đã cho. Tuy nhiên, nếu chức năng được triển khai sao cho khả năng va chạm là rất thấp, nó sẽ có hiệu suất rất tốt (điều này không hoàn toàn là O (1) trong mọi trường hợp có thể nhưng hầu hết là trong trường hợp có thể trường hợp có thể xảy ra).

Ví dụ, việc triển khai mặc định trong Oracle JRE là sử dụng một số ngẫu nhiên (được lưu trữ trong thể hiện đối tượng để nó không thay đổi - nhưng nó cũng vô hiệu hóa khóa bị sai lệch, nhưng đó là một cuộc thảo luận khác) vì vậy khả năng xảy ra va chạm là rất thấp.


"đó là trong hầu hết các trường hợp". Cụ thể hơn, tổng thời gian sẽ có xu hướng về K lần N (trong đó K không đổi) khi N có xu hướng vô cùng.
ChrisW

7
Cái này sai. Chỉ số trong bảng băm sẽ được xác định thông qua hashCode % tableSizeđó có nghĩa là chắc chắn có thể có va chạm. Bạn không sử dụng hết 32 bit. Đó là điểm của bảng băm ... bạn giảm không gian lập chỉ mục lớn xuống nhỏ.
FogleBird

1
"bạn được đảm bảo rằng sẽ không có va chạm" Không phải bạn không phải vì kích thước của bản đồ nhỏ hơn kích thước của hàm băm: ví dụ nếu kích thước của bản đồ là hai, thì sự va chạm được đảm bảo (không quan trọng băm gì) nếu / khi tôi cố gắng chèn ba phần tử.
ChrisW

Nhưng làm thế nào để bạn chuyển đổi từ một khóa sang địa chỉ bộ nhớ trong O (1)? Ý tôi là như x = mảng ["key"]. Khóa không phải là địa chỉ bộ nhớ nên nó vẫn phải là một tra cứu O (n).
paxdiablo

1
"Tôi tin rằng nếu bạn không triển khai hashCode, nó sẽ sử dụng địa chỉ bộ nhớ của đối tượng". Nó có thể sử dụng điều đó, nhưng mã băm mặc định cho Java Java tiêu chuẩn thực sự là một số ngẫu nhiên 25 bit được lưu trữ trong tiêu đề đối tượng, vì vậy 64/32-bit không có kết quả.
Boann
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.