Làm thế nào để một bảng băm hoạt động?


494

Tôi đang tìm kiếm một lời giải thích về cách một bảng băm hoạt động - bằng tiếng Anh đơn giản cho một người đơn giản như tôi!

Ví dụ, tôi biết nó lấy khóa, tính toán hàm băm (tôi đang tìm cách giải thích như thế nào) và sau đó thực hiện một số loại modulo để tìm ra vị trí của nó trong mảng nơi lưu trữ giá trị, nhưng đó là nơi kiến ​​thức của tôi dừng lại .

Bất cứ ai có thể làm rõ quá trình?

Chỉnh sửa: Tôi không hỏi cụ thể về cách tính mã băm, nhưng tổng quan chung về cách hoạt động của bảng băm.


4
Gần đây, tôi đã viết bài viết này ( en.algoritmy.net/article/50101/Hash-table ) mô tả một số cách, cách lưu trữ và tra cứu dữ liệu, với dấu trên bảng băm và chiến lược của chúng (tách chuỗi, thăm dò tuyến tính, băm kép )
malejpavouk

1
Bạn có thể nghĩ về bảng băm như một phiên bản mở rộng của một mảng, điều đó không chỉ giới hạn ở các khóa nguyên liên tiếp.
dùng253751

1
Dưới đây là một số khác: intelligentjava.wordpress.com/2016/10/19/...
nesvarbu

Câu trả lời:


913

Đây là một lời giải thích trong các điều khoản của giáo dân.

Giả sử bạn muốn lấp đầy thư viện bằng sách và không chỉ nhét chúng vào đó, mà bạn muốn có thể dễ dàng tìm lại chúng khi bạn cần.

Vì vậy, bạn quyết định rằng nếu người muốn đọc một cuốn sách biết tiêu đề của cuốn sách và tiêu đề chính xác để khởi động, thì đó là tất cả những gì nó nên làm. Với tiêu đề, người, với sự trợ giúp của thủ thư, có thể tìm thấy cuốn sách một cách dễ dàng và nhanh chóng.

Vì vậy, làm thế nào bạn có thể làm điều đó? Chà, rõ ràng bạn có thể giữ một số loại danh sách nơi bạn đặt mỗi cuốn sách, nhưng sau đó bạn có cùng một vấn đề như tìm kiếm thư viện, bạn cần tìm kiếm danh sách. Cấp, danh sách sẽ nhỏ hơn và dễ tìm kiếm hơn, nhưng bạn vẫn không muốn tìm kiếm tuần tự từ đầu này đến đầu kia của thư viện.

Bạn muốn một cái gì đó, với tiêu đề của cuốn sách, có thể cung cấp cho bạn vị trí phù hợp ngay lập tức, vì vậy tất cả những gì bạn phải làm chỉ là đi bộ đến kệ bên phải, và lấy cuốn sách.

Nhưng làm thế nào có thể được thực hiện? Vâng, với một chút suy nghĩ khi bạn điền vào thư viện và rất nhiều công việc khi bạn điền vào thư viện.

Thay vì chỉ bắt đầu lấp đầy thư viện từ đầu này sang đầu kia, bạn nghĩ ra một phương pháp nhỏ thông minh. Bạn lấy tiêu đề của cuốn sách, chạy nó thông qua một chương trình máy tính nhỏ, tạo ra số kệ và số vị trí trên giá đó. Đây là nơi bạn đặt cuốn sách.

Cái hay của chương trình này là sau này, khi một người quay lại để đọc cuốn sách, bạn đưa tiêu đề qua chương trình một lần nữa và lấy lại số kệ và số vị trí ban đầu mà bạn đã đưa ra, và đây là nơi cuốn sách được đặt.

Chương trình, như những người khác đã đề cập, được gọi là thuật toán băm hoặc tính toán băm và thường hoạt động bằng cách lấy dữ liệu được đưa vào (tiêu đề của cuốn sách trong trường hợp này) và tính toán một số từ nó.

Để đơn giản, hãy nói rằng nó chỉ chuyển đổi từng chữ cái và ký hiệu thành một số và tổng hợp tất cả chúng. Trong thực tế, nó phức tạp hơn thế nhiều, nhưng bây giờ chúng ta hãy để nó ở đó.

Cái hay của thuật toán này là nếu bạn cho cùng một đầu vào lặp đi lặp lại, nó sẽ tiếp tục phun ra cùng một số mỗi lần.

Ok, về cơ bản là cách bảng băm hoạt động.

Công cụ kỹ thuật sau.

Đầu tiên, có kích thước của số. Thông thường, đầu ra của thuật toán băm như vậy nằm trong một phạm vi một số lượng lớn, thường lớn hơn nhiều so với không gian bạn có trong bảng. Chẳng hạn, giả sử rằng chúng ta có chỗ cho chính xác một triệu cuốn sách trong thư viện. Đầu ra của phép tính băm có thể nằm trong khoảng từ 0 đến một tỷ, cao hơn rất nhiều.

Vậy ta phải làm sao? Chúng tôi sử dụng một cái gì đó gọi là tính toán mô đun, về cơ bản nói rằng nếu bạn tính đến số bạn muốn (nghĩa là số một tỷ) nhưng muốn ở trong phạm vi nhỏ hơn nhiều, mỗi lần bạn đạt đến giới hạn của phạm vi nhỏ hơn đó, bạn bắt đầu trở lại 0, nhưng bạn phải theo dõi khoảng cách trong chuỗi lớn bạn đã đến.

Giả sử đầu ra của thuật toán băm nằm trong phạm vi từ 0 đến 20 và bạn nhận được giá trị 17 từ một tiêu đề cụ thể. Nếu kích thước của thư viện chỉ có 7 cuốn sách, bạn đếm 1, 2, 3, 4, 5, 6 và khi bạn lên 7, bạn bắt đầu trở lại ở mức 0. Vì chúng ta cần đếm 17 lần, chúng ta có 1, 2, 3, 4, 5, 6, 0, 1, 2, 3, 4, 5, 6, 0, 1, 2, 3 và số cuối cùng là 3.

Tất nhiên tính toán mô đun không được thực hiện như vậy, nó được thực hiện với phép chia và phần còn lại. Phần còn lại chia 17 cho 7 là 3 (7 đi 2 lần thành 17 tại 14 và chênh lệch giữa 17 và 14 là 3).

Vì vậy, bạn đặt cuốn sách vào khe số 3.

Điều này dẫn đến vấn đề tiếp theo. Sự va chạm. Vì thuật toán không có cách nào để loại bỏ các cuốn sách để chúng điền vào thư viện chính xác (hoặc bảng băm nếu bạn muốn), nên cuối cùng nó sẽ tính toán một số đã được sử dụng trước đó. Theo nghĩa thư viện, khi bạn đến kệ và số vị trí bạn muốn đặt một cuốn sách, đã có một cuốn sách ở đó.

Có nhiều phương thức xử lý va chạm khác nhau, bao gồm chạy dữ liệu vào một phép tính khác để có một vị trí khác trong bảng ( băm kép ) hoặc đơn giản là tìm một khoảng trắng gần với vị trí bạn đã đưa ra (tức là ngay bên cạnh cuốn sách trước đó giả sử vị trí đã có sẵn còn được gọi là thăm dò tuyến tính ). Điều này có nghĩa là bạn có một số công việc phải làm khi bạn cố gắng tìm cuốn sách sau đó, nhưng vẫn tốt hơn là bắt đầu ở một đầu của thư viện.

Cuối cùng, tại một số điểm, bạn có thể muốn đưa nhiều sách vào thư viện hơn thư viện cho phép. Nói cách khác, bạn cần xây dựng một thư viện lớn hơn. Vì vị trí chính xác trong thư viện được tính bằng kích thước chính xác và hiện tại của thư viện, nên theo bạn, nếu thay đổi kích thước thư viện, bạn có thể phải tìm điểm mới cho tất cả các sách kể từ khi tính toán xong để tìm điểm của chúng đã thay đổi.

Tôi hy vọng lời giải thích này là một chút xuống trái đất hơn so với xô và chức năng :)


Cảm ơn cho một lời giải thích tuyệt vời. Bạn có biết nơi tôi có thể tìm thêm chi tiết kỹ thuật về cách thức triển khai trong khung 4.x .Net không?
Johnny_D

Không, nó chỉ là một con số. Bạn chỉ cần đánh số từng kệ và vị trí bắt đầu từ 0 hoặc 1 và tăng 1 cho mỗi vị trí trên giá đó, sau đó tiếp tục đánh số trên giá tiếp theo.
Lasse V. Karlsen

2
'Có nhiều phương thức xử lý va chạm khác nhau, bao gồm cả việc chạy dữ liệu vào một phép tính khác để có một vị trí khác trong bảng' - ý của bạn là gì khi tính toán khác? Nó chỉ là một thuật toán khác? OK, vì vậy, giả sử chúng ta sử dụng một thuật toán khác tạo ra một số khác dựa trên tên sách. Sau đó, nếu tôi tìm thấy cuốn sách đó, làm sao tôi biết nên sử dụng thuật toán nào? Tôi sẽ sử dụng thuật toán đầu tiên, thuật toán thứ hai và cứ thế cho đến khi tôi tìm thấy cuốn sách có tiêu đề là cuốn sách tôi đang tìm?
dùng107986

1
@KyleDelaney: Không cho băm kín (nơi xử lý va chạm bằng cách tìm một thùng thay thế, có nghĩa là việc sử dụng bộ nhớ được khắc phục nhưng bạn mất nhiều thời gian hơn để tìm kiếm trên các thùng). Đối với băm mở hay còn gọi là chuỗi trong trường hợp bệnh lý (hàm băm khủng khiếp hoặc đầu vào được tạo ra một cách cố ý để va chạm bởi một số kẻ thù / tin tặc), bạn có thể kết thúc với hầu hết các thùng băm trống rỗng, nhưng tổng lượng sử dụng bộ nhớ không tệ hơn - chỉ cần thêm con trỏ NULL thay vì lập chỉ mục vào dữ liệu một cách hữu ích.
Tony Delroy

3
@KyleDelaney: cần điều "@Tony" để nhận thông báo về ý kiến ​​của bạn. Có vẻ bạn đang tự hỏi về chuỗi: nói rằng chúng ta có ba nút giá trị A{ptrA, valueA}, B{ptrB, valueB}, C{ptrC, valueC}và một bảng băm có ba nhóm [ptr1, ptr2, ptr3]. Bất kể có va chạm khi chèn, việc sử dụng bộ nhớ được cố định. Bạn có thể không có va chạm: A{NULL, valueA} B{NULL, valueB} C{NULL, valueC}[&A, &B, &C], hoặc tất cả các va chạm A{&B, valueA} B{&C, valueB}, C{NULL, valueC}[NULL, &A, NULL]: các thùng NULL có bị "lãng phí" không? Kinda, kinda không. Tổng bộ nhớ được sử dụng.
Tony Delroy

104

Cách sử dụng và Lingo:

  1. Các bảng băm được sử dụng để nhanh chóng lưu trữ và truy xuất dữ liệu (hoặc bản ghi).
  2. Bản ghi được lưu trữ trong các thùng bằng cách sử dụng khóa băm
  3. Các khóa băm được tính bằng cách áp dụng thuật toán băm cho một giá trị được chọn ( giá trị khóa ) có trong bản ghi. Giá trị được chọn này phải là một giá trị chung cho tất cả các hồ sơ.
  4. Mỗi nhóm có thể có nhiều bản ghi được sắp xếp theo một thứ tự cụ thể.

Ví dụ thế giới thực:

Hash & Co. , được thành lập vào năm 1803 và không có bất kỳ công nghệ máy tính nào có tổng cộng 300 tủ hồ sơ để giữ thông tin chi tiết (hồ sơ) cho khoảng 30.000 khách hàng của họ. Mỗi thư mục tệp được xác định rõ ràng với số máy khách của nó, một số duy nhất từ ​​0 đến 29.999.

Các thư ký nộp đơn thời đó phải nhanh chóng lấy và lưu trữ hồ sơ khách hàng cho nhân viên làm việc. Các nhân viên đã quyết định rằng sẽ hiệu quả hơn khi sử dụng phương pháp băm để lưu trữ và truy xuất hồ sơ của họ.

Để lưu hồ sơ khách hàng, nhân viên nộp đơn sẽ sử dụng số khách hàng duy nhất được ghi trên thư mục. Sử dụng số khách hàng này, họ sẽ điều chỉnh khóa băm bằng 300 để xác định tủ hồ sơ chứa trong đó. Khi họ mở tủ hồ sơ, họ sẽ phát hiện ra rằng nó chứa nhiều thư mục được đặt theo số của khách hàng. Sau khi xác định vị trí chính xác, họ chỉ cần trượt nó vào.

Để lấy lại hồ sơ khách hàng, nhân viên nộp đơn sẽ được cấp số khách hàng trên một tờ giấy. Sử dụng số khách hàng duy nhất này ( khóa băm ), họ sẽ điều chỉnh nó bằng 300 để xác định tủ hồ sơ nào có thư mục khách hàng. Khi họ mở tủ hồ sơ, họ sẽ phát hiện ra rằng nó chứa nhiều thư mục được sắp xếp theo số máy khách. Tìm kiếm thông qua các bản ghi họ sẽ nhanh chóng tìm thấy thư mục khách hàng và lấy nó.

Trong ví dụ thực tế của chúng tôi, các thùng của chúng tôi là tủ hồ sơhồ sơ của chúng tôi là các thư mục tệp .


Một điều quan trọng cần nhớ là máy tính (và thuật toán của chúng) xử lý số tốt hơn so với chuỗi. Vì vậy, truy cập vào một mảng lớn bằng cách sử dụng một chỉ mục nhanh hơn đáng kể so với truy cập tuần tự.

Như Simon đã đề cập , điều mà tôi tin là rất quan trọng là phần băm là biến đổi một không gian lớn (có độ dài tùy ý, thường là chuỗi, v.v.) và ánh xạ nó sang một không gian nhỏ (có kích thước đã biết, thường là số) để lập chỉ mục. Điều này nếu rất quan trọng để nhớ!

Vì vậy, trong ví dụ trên, 30.000 khách hàng có thể được ánh xạ tới một không gian nhỏ hơn.


Ý tưởng chính trong việc này là chia toàn bộ tập dữ liệu của bạn thành các phân đoạn để tăng tốc độ tìm kiếm thực tế thường tốn thời gian. Trong ví dụ của chúng tôi ở trên, mỗi tủ trong 300 tủ hồ sơ sẽ (theo thống kê) chứa khoảng 100 hồ sơ. Tìm kiếm (bất kể thứ tự) thông qua 100 hồ sơ nhanh hơn nhiều so với việc phải xử lý 30.000.

Bạn có thể nhận thấy rằng một số thực sự đã làm điều này. Nhưng thay vì nghĩ ra một phương pháp băm để tạo khóa băm, trong hầu hết các trường hợp, họ sẽ chỉ sử dụng chữ cái đầu tiên của tên cuối cùng. Vì vậy, nếu bạn có 26 tủ hồ sơ, mỗi tủ chứa một chữ cái từ A đến Z, về lý thuyết, bạn vừa phân đoạn dữ liệu của mình và tăng cường quá trình lưu trữ và truy xuất.

Hi vọng điêu nay co ich,

Jeach!


2
Bạn mô tả một loại chiến lược tránh va chạm trong bảng băm cụ thể, được gọi là địa chỉ mở có thể thay đổi, hay gọi là địa chỉ đóng kín (có, buồn nhưng có thật) hoặc trên mạng. Có một loại khác không sử dụng xô danh sách mà thay vào đó lưu trữ các mặt hàng trực tuyến.
Konrad Rudolph

2
mô tả tuyệt vời. trung bình, ngoại trừ mỗi tủ hồ sơ sẽ chứa trung bình khoảng 10030 hồ sơ (30k hồ sơ / 300 tủ = 100). Có thể có giá trị chỉnh sửa.
Ryan Tuck

@TonyD, hãy truy cập trang web này sha-1 trực tuyến và tạo hàm băm SHA-1 cho TonyDbạn nhập vào trường văn bản. Bạn sẽ kết thúc với một giá trị được tạo ra của một cái gì đó trông như thế nào e5dc41578f88877b333c8b31634cf77e4911ed8c. Điều này không gì khác hơn là một số thập lục phân lớn gồm 160 bit (20 byte). Sau đó, bạn có thể sử dụng điều này để xác định thùng nào (số lượng giới hạn) sẽ được sử dụng để lưu trữ hồ sơ của bạn.
Jeach

@TonyD, tôi không chắc thuật ngữ "khóa băm" được đề cập ở đâu trong một vấn đề mâu thuẫn? Nếu vậy, xin vui lòng chỉ ra hai hoặc nhiều địa điểm. Hay bạn đang nói rằng "chúng tôi" sử dụng thuật ngữ "khóa băm" trong khi các trang web khác như Wikipedia sử dụng "giá trị băm, mã băm, tổng băm hoặc đơn giản là băm"? Nếu vậy, ai quan tâm miễn là thuật ngữ được sử dụng là nhất quán trong một nhóm hoặc một tổ chức. Các lập trình viên thường sử dụng thuật ngữ "chìa khóa". Cá nhân tôi cho rằng một lựa chọn tốt khác sẽ là "giá trị băm". Nhưng tôi sẽ loại trừ bằng cách sử dụng "mã băm, tổng băm hoặc đơn giản là băm". Tập trung vào thuật toán và không phải là từ ngữ!
Jeach

2
@TonyD, tôi đã thay đổi văn bản thành "họ sẽ mô đun khóa băm bằng 300", hy vọng nó sẽ sạch hơn và rõ ràng hơn cho mọi người. Cảm ơn!
Jeach

64

Điều này hóa ra là một lĩnh vực khá sâu của lý thuyết, nhưng phác thảo cơ bản là đơn giản.

Về cơ bản, hàm băm chỉ là một hàm lấy mọi thứ từ một không gian (giả sử các chuỗi có độ dài tùy ý) và ánh xạ chúng vào một không gian hữu ích để lập chỉ mục (số nguyên không dấu, giả sử).

Nếu bạn chỉ có một không gian nhỏ để băm, bạn có thể thoát khỏi việc chỉ diễn giải những thứ đó dưới dạng số nguyên và bạn đã hoàn thành (ví dụ: chuỗi 4 byte)

Thông thường, mặc dù, bạn đã có một không gian lớn hơn nhiều. Nếu không gian của những thứ bạn cho phép làm khóa lớn hơn không gian của những thứ bạn đang sử dụng để lập chỉ mục (uint32 của bạn hoặc bất cứ thứ gì) thì bạn không thể có một giá trị duy nhất cho mỗi thứ. Khi hai hoặc nhiều thứ băm vào cùng một kết quả, bạn sẽ phải xử lý sự dư thừa theo cách thích hợp (điều này thường được gọi là xung đột và cách bạn xử lý hoặc không phụ thuộc một chút vào bạn là gì sử dụng hàm băm cho).

Điều này ngụ ý rằng bạn muốn nó không có kết quả tương tự, và có lẽ bạn cũng thực sự muốn hàm băm nhanh.

Cân bằng hai tài sản này (và một vài tài sản khác) đã khiến nhiều người bận rộn!

Trong thực tế, bạn thường có thể tìm thấy một chức năng được biết là hoạt động tốt cho ứng dụng của bạn và sử dụng chức năng đó.

Bây giờ để làm cho công việc này dưới dạng hashtable: Hãy tưởng tượng bạn không quan tâm đến việc sử dụng bộ nhớ. Sau đó, bạn có thể tạo một mảng miễn là bộ chỉ mục của bạn (ví dụ như tất cả các uint32). Khi bạn thêm một cái gì đó vào bảng, bạn băm khóa đó và xem mảng ở chỉ mục đó. Nếu không có gì ở đó, bạn đặt giá trị của bạn ở đó. Nếu đã có một cái gì đó ở đó, bạn thêm mục nhập mới này vào danh sách những thứ ở địa chỉ đó, cùng với đủ thông tin (khóa gốc của bạn hoặc một cái gì đó thông minh) để tìm mục nào thực sự thuộc về khóa nào.

Vì vậy, khi bạn đi lâu, mọi mục trong hashtable của bạn (mảng) đều trống hoặc chứa một mục hoặc danh sách các mục. Lấy ra là một cách đơn giản như lập chỉ mục vào mảng và trả về giá trị hoặc đi theo danh sách các giá trị và trả về giá trị đúng.

Tất nhiên trong thực tế bạn thường không thể làm điều này, nó lãng phí quá nhiều bộ nhớ. Vì vậy, bạn làm mọi thứ dựa trên một mảng thưa thớt (trong đó các mục duy nhất là những mục bạn thực sự sử dụng, mọi thứ khác hoàn toàn không có giá trị).

Có rất nhiều kế hoạch và thủ thuật để làm cho công việc này tốt hơn, nhưng đó là những điều cơ bản.


1
Xin lỗi, tôi biết đây là một câu hỏi / câu trả lời cũ, nhưng tôi đã cố gắng hiểu điểm cuối cùng này của bạn. Một bảng băm có độ phức tạp thời gian O (1). Tuy nhiên, một khi bạn sử dụng một mảng thưa thớt, bạn không cần phải thực hiện tìm kiếm nhị phân để tìm giá trị của mình chứ? Tại thời điểm đó, độ phức tạp thời gian không trở thành O (log n)?
Herbrandson

@herbrandson: không ... một mảng thưa thớt chỉ đơn giản có nghĩa là tương đối ít chỉ số đã được điền với các giá trị - bạn vẫn có thể lập chỉ mục trực tiếp đến phần tử mảng cụ thể cho giá trị băm bạn đã tính từ khóa của mình; Tuy nhiên, việc triển khai mảng thưa thớt mà Simon mô tả chỉ lành mạnh trong các trường hợp rất hạn chế: khi kích thước nhóm theo thứ tự kích thước trang bộ nhớ (so với intcác phím có độ thưa 1 trong 1000 và 4k trang = hầu hết các trang được chạm) và khi HĐH xử lý hiệu quả tất cả các trang 0 trang (vì vậy các trang không sử dụng tất cả không cần bộ nhớ sao lưu), khi không gian địa chỉ dồi dào ....
Tony Delroy

@TonyDelroy - đó là sự thật, đó là sự đơn giản hóa nhưng ý tưởng là đưa ra một cái nhìn tổng quan về chúng là gì và tại sao, không phải là một triển khai thực tế. Các chi tiết sau này có nhiều sắc thái hơn, khi bạn gật đầu trong bản mở rộng của mình.
simon

48

Rất nhiều câu trả lời, nhưng không ai trong số chúng rất trực quan và các bảng băm có thể dễ dàng "nhấp chuột" khi hiển thị.

Các bảng băm thường được thực hiện dưới dạng các mảng của danh sách được liên kết. Nếu chúng ta tưởng tượng một bảng lưu trữ tên của mọi người, sau một vài lần chèn, nó có thể được đặt trong bộ nhớ như dưới đây, trong đó các ()số được bao gồm là giá trị băm của văn bản / tên.

bucket#  bucket content / linked list

[0]      --> "sue"(780) --> null
[1]      null
[2]      --> "fred"(42) --> "bill"(9282) --> "jane"(42) --> null
[3]      --> "mary"(73) --> null
[4]      null
[5]      --> "masayuki"(75) --> "sarwar"(105) --> null
[6]      --> "margaret"(2626) --> null
[7]      null
[8]      --> "bob"(308) --> null
[9]      null

Một vài điểm:

  • mỗi mục trong mảng (chỉ mục [0], [1]...) được gọi là nhóm và bắt đầu một danh sách các giá trị được liên kết - có thể trống (trong phần tử này , trong ví dụ này - tên người )
  • mỗi giá trị (ví dụ "fred"với hàm băm 42) được liên kết từ nhóm, [hash % number_of_buckets]vd 42 % 10 == [2]; %toán tử modulo - phần còn lại khi chia cho số lượng xô
  • nhiều giá trị dữ liệu có thể va chạm tại và được liên kết từ cùng một nhóm, thường là do giá trị băm của chúng va chạm sau khi hoạt động modulo (ví dụ 42 % 10 == [2]9282 % 10 == [2]), nhưng đôi khi vì các giá trị băm giống nhau (ví dụ "fred""jane"cả hai được hiển thị với hàm băm 42ở trên)
    • hầu hết các bảng băm xử lý các xung đột - với hiệu suất giảm nhẹ nhưng không có sự nhầm lẫn về chức năng - bằng cách so sánh toàn bộ giá trị (văn bản ở đây) của một giá trị đang được tìm kiếm hoặc chèn vào từng giá trị đã có trong danh sách được liên kết tại nhóm băm

Độ dài danh sách được liên kết liên quan đến hệ số tải, không phải số lượng giá trị

Nếu kích thước bảng tăng lên, các bảng băm được triển khai như trên có xu hướng tự thay đổi kích thước (nghĩa là tạo một mảng lớn hơn, tạo danh sách liên kết mới / được cập nhật từ đó, xóa mảng cũ) để giữ tỷ lệ giá trị cho các nhóm (còn gọi là tải hệ số ) ở đâu đó trong phạm vi 0,5 đến 1,0.

Hans đưa ra công thức thực tế cho các yếu tố tải khác trong một nhận xét bên dưới, nhưng đối với các giá trị chỉ định: với hệ số tải 1 và hàm băm cường độ mã hóa, 1 / e (~ 36,8%) các thùng sẽ có xu hướng trống, 1 / e khác (~ 36,8%) có một yếu tố, 1 / (2e) hoặc ~ 18,4% hai yếu tố, 1 / (3! E) khoảng 6,1% ba yếu tố, 1 / (4! E) hoặc ~ 1,5% bốn yếu tố, 1 / (5! E) ~ .3% có năm, v.v. - chiều dài chuỗi trung bình từ các thùng không trống là ~ 1,58 cho dù có bao nhiêu phần tử trong bảng (nghĩa là có 100 phần tử và 100 nhóm hay 100 triệu các phần tử và 100 triệu xô), đó là lý do tại sao chúng ta nói tra cứu / chèn / xóa là các hoạt động thời gian không đổi O (1) .

Làm thế nào một bảng băm có thể liên kết các khóa với các giá trị

Đưa ra cách thực hiện bảng băm như được mô tả ở trên, chúng ta có thể tưởng tượng việc tạo ra một loại giá trị, chẳng hạn như struct Value { string name; int age; };, hàm so sánh và hàm băm chỉ nhìn vào nametrường (bỏ qua tuổi), và sau đó một điều tuyệt vời xảy ra: chúng ta có thể lưu trữ Valuecác bản ghi như {"sue", 63}trong bảng , sau đó tìm kiếm "kiện" mà không biết tuổi của cô ấy, tìm giá trị được lưu trữ và khôi phục hoặc thậm chí cập nhật tuổi của cô ấy
- chúc mừng sinh nhật Sue - điều thú vị là không thay đổi giá trị băm nên không yêu cầu chúng tôi chuyển bản ghi của Sue sang bản khác Gầu múc.

Khi chúng tôi thực hiện điều này, chúng tôi đang sử dụng bảng băm dưới dạng bản đồ kết hợp hay còn gọi là bản đồ và các giá trị mà nó lưu trữ có thể được coi là bao gồm một khóa (tên) và một hoặc nhiều trường khác vẫn được gọi là - khó hiểu - giá trị ( trong ví dụ của tôi, chỉ là tuổi). Việc thực hiện bảng băm được sử dụng làm bản đồ được gọi là bản đồ băm .

Điều này trái ngược với ví dụ trước đó trong câu trả lời này, nơi chúng tôi lưu trữ các giá trị rời rạc như "kiện", mà bạn có thể nghĩ là khóa riêng của mình: loại sử dụng đó được gọi là bộ băm .

Có nhiều cách khác để thực hiện bảng băm

Không phải tất cả các bảng băm đều sử dụng danh sách được liên kết (được gọi là chuỗi riêng ), nhưng hầu hết các mục đích chung đều thực hiện, như cách băm đóng thay thế chính (còn gọi là địa chỉ mở ) - đặc biệt với các thao tác xóa được hỗ trợ - có các đặc tính hiệu suất kém ổn định hơn với các khóa dễ bị va chạm / hàm băm.


Một vài từ về hàm băm

Băm mạnh ...

Mục đích chung, công việc của hàm băm giảm thiểu va chạm trong trường hợp xấu nhất là phun các khóa xung quanh các bảng băm một cách hiệu quả một cách ngẫu nhiên, trong khi luôn tạo ra cùng một giá trị băm cho cùng một khóa. Ngay cả một bit thay đổi bất cứ nơi nào trong khóa sẽ lý tưởng - ngẫu nhiên - lật khoảng một nửa số bit trong giá trị băm kết quả.

Điều này thường được sắp xếp với toán học quá phức tạp để tôi phải mò mẫm. Tôi sẽ đề cập đến một cách dễ hiểu - không phải là thân thiện nhất với bộ nhớ cache hoặc bộ nhớ cache mà vốn rất thanh lịch (như mã hóa với bộ đệm một lần!) - vì tôi nghĩ rằng nó giúp lái xe về những phẩm chất mong muốn được đề cập ở trên. Giả sử bạn đã băm 64 bit double- bạn có thể tạo 8 bảng cho mỗi 256 số ngẫu nhiên (mã bên dưới), sau đó sử dụng mỗi lát 8 bit / 1 byte của doublebiểu diễn bộ nhớ để lập chỉ mục vào một bảng khác, XOR số ngẫu nhiên bạn tìm kiếm. Với cách tiếp cận này, thật dễ dàng để thấy rằng một chút (theo nghĩa chữ số nhị phân) thay đổi bất cứ nơi nào trong doublekết quả trong một số ngẫu nhiên khác nhau được tìm kiếm trong một trong các bảng và giá trị cuối cùng hoàn toàn không tương quan.

// note caveats above: cache unfriendly (SLOW) but strong hashing...
size_t random[8][256] = { ...random data... };
const char* p = (const char*)&my_double;
size_t hash = random[0][p[0]] ^ random[1][p[1]] ^ ... ^ random[7][p[7]];

Băm yếu nhưng nhanh chóng ...

Các hàm băm của nhiều thư viện chuyển các số nguyên qua không thay đổi (được gọi là hàm băm tầm thường hoặc danh tính ); đó là một thái cực khác từ băm mạnh được mô tả ở trên. Băm danh tính là vô cùngva chạm dễ xảy ra trong những trường hợp xấu nhất, nhưng hy vọng là trong trường hợp khá phổ biến của các khóa số nguyên có xu hướng tăng dần (có lẽ với một số khoảng trống), chúng sẽ ánh xạ vào các thùng liên tiếp để lại ít trống hơn các lá băm ngẫu nhiên (~ 36,8 của chúng tôi % tại hệ số tải 1 đã đề cập trước đó), do đó có ít va chạm hơn và danh sách các yếu tố va chạm được liên kết lâu hơn so với ánh xạ ngẫu nhiên đạt được. Thật tuyệt vời khi tiết kiệm thời gian để tạo ra một hàm băm mạnh và nếu các khóa được tìm kiếm theo thứ tự chúng sẽ được tìm thấy trong các thùng gần đó trong bộ nhớ, cải thiện các lần truy cập bộ đệm. Khi các khóa không tăng độc đáo, hy vọng là chúng sẽ đủ ngẫu nhiên, chúng sẽ không cần hàm băm mạnh để hoàn toàn ngẫu nhiên vị trí của chúng vào các thùng.


6
Cho phép tôi chỉ nói: câu trả lời tuyệt vời.
CRThaze

@Tony Delroy Cảm ơn câu trả lời tuyệt vời. Tôi vẫn có một điểm mở trong tâm trí của tôi mặc dù. Bạn nói rằng ngay cả khi có 100 triệu thùng, thời gian tra cứu sẽ là O (1) với hệ số tải 1 và hàm băm cường độ mã hóa. Nhưng những gì về việc tìm đúng xô trong 100 triệu? Ngay cả khi chúng ta có tất cả các nhóm được sắp xếp, không phải là O (log100.000.000) sao? Làm thế nào có thể tìm thấy xô là O (1)?
selman

@selman: câu hỏi của bạn không cung cấp nhiều chi tiết để giải thích lý do tại sao bạn nghĩ đó có thể là O (log100.000.000), nhưng bạn có nói "ngay cả khi chúng tôi có tất cả các nhóm được sắp xếp" - hãy nhớ rằng các giá trị trong các bảng băm được bao giờ "sắp xếp" theo nghĩa thông thường: đó giá trị xuất hiện trong đó xô được xác định bằng cách áp dụng hàm băm để chìa khóa. Nghĩ rằng sự phức tạp là O (log100.000.000) ngụ ý bạn tưởng tượng thực hiện tìm kiếm nhị phân thông qua các nhóm được sắp xếp, nhưng đó không phải là cách băm hoạt động. Có thể đọc một vài câu trả lời khác và xem nếu nó bắt đầu có ý nghĩa hơn.
Tony Delroy

@TonyDelroy Thật vậy, "xô được sắp xếp" là trường hợp tốt nhất mà tôi tưởng tượng. Do đó O (log100.000.000). Nhưng nếu đây không phải là trường hợp, làm thế nào ứng dụng có thể tìm thấy xô liên quan trong số hàng triệu? Hàm băm có tạo ra một vị trí bộ nhớ nào đó không?
selman

1
@selman: bởi vì bộ nhớ máy tính cho phép "truy cập ngẫu nhiên" thời gian liên tục: nếu bạn có thể tính toán một địa chỉ bộ nhớ, bạn có thể truy xuất nội dung bộ nhớ mà không phải truy cập bộ nhớ trong các phần khác của mảng. Vì vậy, cho dù bạn truy cập nhóm đầu tiên, nhóm cuối cùng hoặc nhóm ở bất kỳ đâu ở giữa, nó sẽ có các đặc tính hiệu suất giống nhau (một cách lỏng lẻo, mất cùng thời gian, mặc dù chịu tác động của bộ nhớ đệm CPU L1 / L2 / L3 nhưng chúng chỉ hoạt động để giúp bạn nhanh chóng truy cập lại các thùng được truy cập gần đây hoặc ngẫu nhiên gần đó và có thể bỏ qua để phân tích big-O).
Tony Delroy

24

Các bạn rất gần để giải thích điều này đầy đủ, nhưng thiếu một vài điều. Hashtable chỉ là một mảng. Bản thân mảng sẽ chứa một cái gì đó trong mỗi khe. Tối thiểu bạn sẽ lưu trữ giá trị băm hoặc chính giá trị trong vị trí này. Ngoài ra, bạn cũng có thể lưu trữ một danh sách các giá trị được liên kết / xâu chuỗi đã va chạm vào vị trí này hoặc bạn có thể sử dụng phương thức địa chỉ mở. Bạn cũng có thể lưu trữ một con trỏ hoặc con trỏ tới dữ liệu khác mà bạn muốn lấy ra khỏi vị trí này.

Điều quan trọng cần lưu ý là bản thân giá trị băm thường không chỉ ra vị trí đặt giá trị. Ví dụ: giá trị băm có thể là giá trị nguyên âm. Rõ ràng một số âm không thể trỏ đến một vị trí mảng. Ngoài ra, giá trị băm sẽ có xu hướng nhiều lần hơn số lượng lớn hơn các vị trí có sẵn. Do đó, một phép tính khác cần được thực hiện bởi chính hashtable để tìm ra vị trí nào mà giá trị sẽ đi vào. Điều này được thực hiện với một phép toán mô đun như:

uint slotIndex = hashValue % hashTableSize;

Giá trị này là vị trí mà giá trị sẽ đi vào. Trong địa chỉ mở, nếu vị trí đã được điền với một giá trị băm khác và / hoặc dữ liệu khác, thao tác mô đun sẽ được chạy lại một lần nữa để tìm vị trí tiếp theo:

slotIndex = (remainder + 1) % hashTableSize;

Tôi cho rằng có thể có các phương pháp nâng cao khác để xác định chỉ số vị trí, nhưng đây là phương pháp phổ biến tôi từng thấy ... sẽ quan tâm đến bất kỳ phương pháp nào khác hoạt động tốt hơn.

Với phương pháp mô đun, nếu bạn có một bảng có kích thước 1000, bất kỳ giá trị băm nào nằm trong khoảng từ 1 đến 1000 sẽ đi vào vị trí tương ứng. Bất kỳ giá trị âm và bất kỳ giá trị nào lớn hơn 1000 sẽ có khả năng va chạm các giá trị vị trí. Cơ hội xảy ra điều đó phụ thuộc cả vào phương pháp băm của bạn, cũng như tổng số mục bạn thêm vào bảng băm. Nói chung, cách tốt nhất là tạo kích thước của hàm băm sao cho tổng số giá trị được thêm vào chỉ bằng khoảng 70% kích thước của nó. Nếu hàm băm của bạn thực hiện tốt công việc phân phối đồng đều, bạn thường sẽ gặp rất ít hoặc không có va chạm xô / khe và nó sẽ thực hiện rất nhanh cho cả thao tác tra cứu và ghi. Nếu không biết trước tổng số giá trị cần thêm, hãy dự đoán tốt bằng bất kỳ phương tiện nào,

Mình hy vọng rằng nó đã có ích.

PS - Trong C #, GetHashCode()phương thức này khá chậm và dẫn đến xung đột giá trị thực trong nhiều điều kiện tôi đã thử nghiệm. Để giải trí thực sự, hãy xây dựng hàm băm của riêng bạn và cố gắng làm cho nó KHÔNG BAO GIỜ va chạm vào dữ liệu cụ thể mà bạn đang băm, chạy nhanh hơn GetHashCode và có phân phối khá đồng đều. Tôi đã thực hiện điều này bằng cách sử dụng các giá trị mã băm dài thay vì kích thước int và nó hoạt động khá tốt với tối đa 32 triệu lượt nhập các giá trị băm trong hàm băm với 0 va chạm. Thật không may, tôi không thể chia sẻ mã vì nó thuộc về chủ nhân của tôi ... nhưng tôi có thể tiết lộ nó có thể cho một số miền dữ liệu nhất định. Khi bạn có thể đạt được điều này, hashtable rất nhanh. :)


Tôi biết bài đăng khá cũ nhưng ai đó có thể giải thích (phần còn lại + 1) nghĩa là gì ở đây
Hari

3
@Hari remainderđề cập đến kết quả của phép tính modulo ban đầu và chúng tôi thêm 1 vào nó để tìm vị trí có sẵn tiếp theo.
x4nd3r

"Bản thân mảng sẽ chứa một cái gì đó trong mỗi vị trí. Tối thiểu bạn sẽ lưu trữ giá trị băm hoặc chính giá trị trong vị trí này." - thông thường là "khe" (xô) không lưu trữ giá trị nào cả; triển khai địa chỉ mở thường lưu trữ NULL hoặc con trỏ tới nút đầu tiên trong danh sách được liên kết - không có giá trị trực tiếp trong vị trí / nhóm. "Sẽ quan tâm đến bất kỳ ai khác" - "+1" mà bạn minh họa được gọi là thăm dò tuyến tính , hoạt động tốt hơn: thăm dò bậc hai . "thường gặp rất ít va chạm xô / khe" - @ 70% dung lượng, ~ 12% vị trí với 2 giá trị, ~ 3% 3 ....
Tony Delroy

"Tôi đã thực hiện điều này bằng cách sử dụng các giá trị mã băm dài thay vì kích thước int và nó hoạt động khá tốt với tối đa 32 triệu lượt nhập các giá trị băm trong hàm băm với 0 va chạm." - điều này chỉ đơn giản là không thể trong trường hợp chung khi các giá trị của khóa là ngẫu nhiên một cách hiệu quả trong phạm vi lớn hơn nhiều so với số lượng xô. Lưu ý rằng việc có các giá trị băm riêng biệt thường đủ dễ dàng (và nói về longgiá trị băm của bạn ngụ ý đó là những gì bạn đã đạt được), nhưng đảm bảo chúng không va chạm vào bảng băm sau khi hoạt động mod /% không xảy ra (trong trường hợp chung ).
Tony Delroy

(Tránh tất cả các va chạm được gọi là băm hoàn hảo . Nói chung, nó thực tế đối với một vài trăm hoặc nghìn khóa được biết trước - gperf là ​​một ví dụ về một công cụ để tính toán hàm băm như vậy. Bạn cũng có thể tự viết rất hạn chế. hoàn cảnh - ví dụ: nếu các khóa của bạn là con trỏ tới các đối tượng từ nhóm bộ nhớ của bạn được giữ khá đầy đủ, với mỗi con trỏ cách nhau một khoảng cách cố định, bạn có thể chia con trỏ theo khoảng cách đó và thực sự có một chỉ mục thành một mảng hơi thưa thớt, tránh va chạm.)
Tony Delroy

17

Đây là cách nó hoạt động theo cách hiểu của tôi:

Đây là một ví dụ: hình ảnh toàn bộ bảng dưới dạng một loạt các thùng. Giả sử bạn có một triển khai với mã băm alpha-số và có một nhóm cho mỗi chữ cái của bảng chữ cái. Việc triển khai này đặt từng mục có mã băm bắt đầu bằng một chữ cái cụ thể trong nhóm tương ứng.

Giả sử bạn có 200 đối tượng, nhưng chỉ có 15 đối tượng có mã băm bắt đầu bằng chữ 'B.' Bảng băm chỉ cần tra cứu và tìm kiếm trong 15 đối tượng trong nhóm 'B', thay vì tất cả 200 đối tượng.

Theo như tính toán mã băm, không có gì kỳ diệu về nó. Mục tiêu là để các đối tượng khác nhau trả về các mã khác nhau và cho các đối tượng bằng nhau trả lại các mã bằng nhau. Bạn có thể viết một lớp luôn trả về cùng một số nguyên như mã băm cho tất cả các trường hợp, nhưng về cơ bản bạn sẽ phá hủy tính hữu dụng của bảng băm, vì nó sẽ trở thành một nhóm khổng lồ.


13

Ngắn và ngọt:

Một bảng băm kết thúc một mảng, hãy gọi nó internalArray. Các mục được chèn vào mảng theo cách này:

let insert key value =
    internalArray[hash(key) % internalArray.Length] <- (key, value)
    //oversimplified for educational purposes

Đôi khi hai khóa sẽ băm vào cùng một chỉ mục trong mảng và bạn muốn giữ cả hai giá trị. Tôi muốn lưu trữ cả hai giá trị trong cùng một chỉ mục, việc mã hóa đơn giản bằng cách tạo internalArraymột mảng các danh sách được liên kết:

let insert key value =
    internalArray[hash(key) % internalArray.Length].AddLast(key, value)

Vì vậy, nếu tôi muốn lấy một mục ra khỏi bảng băm của mình, tôi có thể viết:

let get key =
    let linkedList = internalArray[hash(key) % internalArray.Length]
    for (testKey, value) in linkedList
        if (testKey = key) then return value
    return null

Xóa các hoạt động chỉ đơn giản là viết. Như bạn có thể nói, chèn, tra cứu và xóa khỏi danh sách các danh sách được liên kết của chúng tôi gần O (1).

Khi InternalArray của chúng ta quá đầy, có thể với khoảng 85% dung lượng, chúng ta có thể thay đổi kích thước mảng bên trong và di chuyển tất cả các mục từ mảng cũ sang mảng mới.


11

Nó thậm chí còn đơn giản hơn thế.

Một hashtable không có gì khác hơn là một mảng (thường là thưa thớt ) các vectơ có chứa các cặp khóa / giá trị. Kích thước tối đa của mảng này thường nhỏ hơn số lượng mục trong tập hợp các giá trị có thể cho loại dữ liệu được lưu trữ trong hàm băm.

Thuật toán băm được sử dụng để tạo một chỉ mục vào mảng đó dựa trên các giá trị của mục sẽ được lưu trữ trong mảng.

Đây là nơi lưu trữ vectơ của các cặp khóa / giá trị trong mảng. Bởi vì tập hợp các giá trị có thể là chỉ mục trong mảng thường nhỏ hơn số lượng của tất cả các giá trị có thể có mà loại có thể có, nên có thể hàm băm của bạn thuật toán sẽ tạo ra cùng một giá trị cho hai khóa riêng biệt. Một thuật toán băm tốt sẽ ngăn chặn điều này càng nhiều càng tốt (đó là lý do tại sao nó bị loại xuống loại thường vì nó có thông tin cụ thể mà thuật toán băm chung không thể biết), nhưng không thể ngăn chặn được.

Do đó, bạn có thể có nhiều khóa sẽ tạo cùng một mã băm. Khi điều đó xảy ra, các mục trong vectơ được lặp qua và so sánh trực tiếp được thực hiện giữa khóa trong vectơ và khóa đang được tra cứu. Nếu nó được tìm thấy, rất lớn và giá trị liên quan đến khóa được trả về, nếu không, không có gì được trả về.


10

Bạn có một loạt các thứ, và một mảng.

Đối với mỗi thứ, bạn tạo một chỉ mục cho nó, được gọi là hàm băm. Điều quan trọng về hàm băm là nó 'phân tán' rất nhiều; bạn không muốn hai thứ tương tự có băm tương tự.

Bạn đặt những thứ của bạn vào mảng ở vị trí được chỉ định bởi hàm băm. Nhiều thứ có thể kết thúc ở một hàm băm nhất định, vì vậy bạn lưu trữ những thứ trong mảng hoặc thứ gì đó phù hợp, mà chúng ta thường gọi là một cái xô.

Khi bạn tìm kiếm mọi thứ trong hàm băm, bạn sẽ thực hiện các bước tương tự, tìm ra giá trị băm, sau đó xem những gì trong thùng ở vị trí đó và kiểm tra xem đó có phải là thứ bạn đang tìm kiếm không.

Khi băm của bạn hoạt động tốt và mảng của bạn đủ lớn, sẽ chỉ có một vài thứ ở bất kỳ chỉ số cụ thể nào trong mảng, vì vậy bạn sẽ không phải xem xét nhiều.

Đối với các điểm thưởng, hãy làm cho nó để khi bảng băm của bạn được truy cập, nó sẽ di chuyển thứ được tìm thấy (nếu có) sang đầu thùng, vì vậy lần sau là lần đầu tiên kiểm tra.


1
cảm ơn vì điểm cuối cùng mà mọi người khác đã bỏ qua đề cập đến
Sandeep Raju Mitchhakar

4

Tất cả các câu trả lời cho đến nay đều tốt, và nhận được ở các khía cạnh khác nhau về cách hoạt động của một hashtable. Đây là một ví dụ đơn giản có thể hữu ích. Hãy nói rằng chúng tôi muốn lưu trữ một số mặt hàng với các chuỗi chữ cái viết thường dưới dạng khóa.

Như simon đã giải thích, hàm băm được sử dụng để ánh xạ từ một không gian lớn sang một không gian nhỏ. Việc triển khai hàm băm đơn giản, ngây thơ cho ví dụ của chúng tôi có thể lấy chữ cái đầu tiên của chuỗi và ánh xạ nó thành một số nguyên, vì vậy "alligator" có mã băm là 0, "bee" có mã băm là 1, " ngựa vằn "sẽ là 25, v.v.

Tiếp theo, chúng tôi có một mảng gồm 26 nhóm (có thể là ArrayLists trong Java) và chúng tôi đặt vật phẩm vào nhóm phù hợp với mã băm của khóa của chúng tôi. Nếu chúng ta có nhiều mục có một khóa bắt đầu bằng cùng một chữ cái, chúng sẽ có cùng mã băm, vì vậy tất cả sẽ đi vào nhóm cho mã băm đó để tìm kiếm tuyến tính phải được thực hiện trong nhóm để tìm một mục cụ thể

Trong ví dụ của chúng tôi, nếu chúng ta chỉ có vài chục mục với các phím kéo dài trong bảng chữ cái, nó sẽ hoạt động rất tốt. Tuy nhiên, nếu chúng tôi có một triệu mục hoặc tất cả các khóa bắt đầu bằng 'a' hoặc 'b', thì bảng băm của chúng tôi sẽ không lý tưởng. Để có hiệu suất tốt hơn, chúng ta sẽ cần một hàm băm khác và / hoặc nhiều nhóm hơn.


3

Đây là một cách khác để xem xét nó.

Tôi giả sử bạn hiểu khái niệm về mảng A. Đó là thứ hỗ trợ cho hoạt động lập chỉ mục, nơi bạn có thể đến phần tử thứ I, A [I], trong một bước, cho dù A có lớn đến đâu.

Vì vậy, ví dụ, nếu bạn muốn lưu trữ thông tin về một nhóm người có độ tuổi khác nhau, một cách đơn giản là có một mảng đủ lớn và sử dụng tuổi của mỗi người làm chỉ số vào mảng. Thay vào đó, bạn có thể có quyền truy cập một bước vào thông tin của bất kỳ ai.

Nhưng tất nhiên có thể có nhiều hơn một người cùng tuổi, vì vậy những gì bạn đặt trong mảng ở mỗi mục là một danh sách tất cả những người có độ tuổi đó. Vì vậy, bạn có thể truy cập thông tin của một cá nhân trong một bước cộng với một chút tìm kiếm trong danh sách đó (được gọi là "nhóm"). Nó chỉ chậm lại nếu có nhiều người đến mức xô trở nên lớn. Sau đó, bạn cần một mảng lớn hơn và một số cách khác để có thêm thông tin nhận dạng về người đó, như vài chữ cái đầu của họ của họ, thay vì sử dụng tuổi.

Đó là ý tưởng cơ bản. Thay vì sử dụng tuổi tác, bất kỳ chức năng nào của người tạo ra sự lan truyền giá trị tốt đều có thể được sử dụng. Đó là hàm băm. Giống như bạn có thể lấy từng bit thứ ba của đại diện ASCII của tên người đó, xáo trộn theo thứ tự nào đó. Tất cả vấn đề là bạn không muốn có quá nhiều người băm vào cùng một nhóm, vì tốc độ phụ thuộc vào các nhóm nhỏ còn lại.


2

Làm thế nào băm được tính toán thường không phụ thuộc vào hashtable, mà phụ thuộc vào các mục được thêm vào nó. Trong các thư viện khung / lớp cơ sở như .net và Java, mỗi đối tượng có một phương thức GetHashCode () (hoặc tương tự) trả về mã băm cho đối tượng này. Thuật toán mã băm lý tưởng và việc thực hiện chính xác phụ thuộc vào dữ liệu được biểu thị trong đối tượng.


2

Một bảng băm hoàn toàn hoạt động trên thực tế là tính toán thực tế theo mô hình máy truy cập ngẫu nhiên, tức là giá trị tại bất kỳ địa chỉ nào trong bộ nhớ có thể được truy cập trong thời gian O (1) hoặc thời gian không đổi.

Vì vậy, nếu tôi có một vũ trụ các khóa (tập hợp tất cả các khóa có thể mà tôi có thể sử dụng trong một ứng dụng, ví dụ: cuộn số cho học sinh, nếu đó là 4 chữ số thì vũ trụ này là một tập hợp các số từ 1 đến 9999) và cách để ánh xạ chúng thành một tập hợp số lượng kích thước hữu hạn Tôi có thể phân bổ bộ nhớ trong hệ thống của mình, theo lý thuyết, bảng băm của tôi đã sẵn sàng.

Nói chung, trong các ứng dụng, kích thước vũ trụ của các khóa rất lớn hơn số phần tử tôi muốn thêm vào bảng băm (Tôi không muốn lãng phí bộ nhớ 1 GB để băm, giả sử, 10000 hoặc 100000 giá trị số nguyên vì chúng là 32 bit dài trong reprsentaion nhị phân). Vì vậy, chúng tôi sử dụng băm này. Đó là một loại hoạt động "toán học" pha trộn, ánh xạ vũ trụ lớn của tôi thành một tập hợp nhỏ các giá trị mà tôi có thể chứa trong bộ nhớ. Trong các trường hợp thực tế, thường không gian của bảng băm có cùng "thứ tự" (big-O) với (số phần tử * kích thước của mỗi phần tử), vì vậy, chúng ta không lãng phí nhiều bộ nhớ.

Bây giờ, một tập hợp lớn được ánh xạ thành một tập hợp nhỏ, ánh xạ phải là nhiều thành một. Vì vậy, các khóa khác nhau sẽ được phân bổ cùng một không gian (?? không công bằng). Có một vài cách để xử lý việc này, tôi chỉ biết hai cách phổ biến:

  • Sử dụng không gian được phân bổ cho giá trị làm tham chiếu đến danh sách được liên kết. Danh sách được liên kết này sẽ lưu trữ một hoặc nhiều giá trị, nằm trong cùng một vị trí trong nhiều ánh xạ. Danh sách được liên kết cũng chứa các khóa để giúp ai đó tìm kiếm. Giống như nhiều người trong cùng một căn hộ, khi một người giao hàng đến, anh ta đến phòng và hỏi cụ thể anh chàng.
  • Sử dụng hàm băm kép trong một mảng cung cấp cùng một chuỗi các giá trị mỗi lần thay vì một giá trị. Khi tôi đi lưu trữ một giá trị, tôi sẽ xem liệu vị trí bộ nhớ cần thiết là miễn phí hay bị chiếm dụng. Nếu nó miễn phí, tôi có thể lưu trữ giá trị của mình ở đó, nếu nó bị chiếm đóng, tôi lấy giá trị tiếp theo từ chuỗi và cứ thế cho đến khi tôi tìm thấy một vị trí miễn phí và tôi lưu trữ giá trị của mình ở đó. Khi tìm kiếm hoặc truy xuất giá trị, tôi quay lại trên cùng một đường dẫn như được đưa ra bởi chuỗi và tại mỗi vị trí, hãy hỏi vaue nếu nó ở đó cho đến khi tôi tìm thấy nó hoặc tìm kiếm tất cả các vị trí có thể có trong mảng.

Giới thiệu về Thuật toán của CLRS cung cấp cái nhìn sâu sắc rất tốt về chủ đề này.


0

Đối với tất cả những người tìm kiếm cách lập trình, đây là cách nó hoạt động. Việc triển khai nội bộ của hashtables tiên tiến có nhiều điều phức tạp và tối ưu hóa cho việc phân bổ / phân bổ lưu trữ và tìm kiếm, nhưng ý tưởng cấp cao nhất sẽ rất giống nhau.

(void) addValue : (object) value
{
   int bucket = calculate_bucket_from_val(value);
   if (bucket) 
   {
       //do nothing, just overwrite
   }
   else   //create bucket
   {
      create_extra_space_for_bucket();
   }
   put_value_into_bucket(bucket,value);
}

(bool) exists : (object) value
{
   int bucket = calculate_bucket_from_val(value);
   return bucket;
}

nơi calculate_bucket_from_val()là hàm băm nơi mà tất cả sự độc đáo kỳ diệu phải xảy ra.

Nguyên tắc cơ bản là: Để chèn một giá trị nhất định, xô phải KHÔNG ĐỘC ĐÁO & TUYỆT VỜI TỪ GIÁ TRỊ mà nó được cho là LƯU TRỮ.

Xô là bất kỳ không gian nơi lưu trữ các giá trị - vì ở đây tôi đã giữ nó int như một chỉ mục mảng, nhưng nó cũng có thể là một vị trí bộ nhớ.


1
"quy tắc của ngón tay cái là: Để chèn một giá trị nhất định, xô phải TUYỆT VỜI & TUYỆT VỜI TỪ GIÁ TRỊ mà nó được cho là LƯU TRỮ."- điều này mô tả một hàm băm hoàn hảo , thường chỉ có thể cho vài trăm hoặc nghìn giá trị được biết tại thời điểm biên dịch. Hầu hết các bảng băm phải xử lý va chạm . Ngoài ra, các bảng băm có xu hướng phân bổ không gian cho tất cả các thùng dù chúng trống hay không, trong khi mã giả của bạn ghi lại một create_extra_space_for_bucket()bước trong khi chèn các khóa mới. Xô có thể là con trỏ mặc dù.
Tony Delroy
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.