Làm thế nào là giá trị của bảng băm được lưu trữ vật lý trong bộ nhớ?


7

Câu hỏi:

Làm thế nào các giá trị của bảng băm được lưu trữ trong bộ nhớ sao cho không gian nếu được sử dụng hiệu quả và các giá trị không phải di dời thường xuyên?

Hiểu biết hiện tại của tôi (có thể sai):

Giả sử tôi có 3 đối tượng được lưu trữ trong bảng băm. Hàm băm của chúng tạo ra các giá trị này:

  • 0
  • 10
  • 20

Tôi sẽ cho rằng các con trỏ của các đối tượng này sẽ không được lưu trữ tại các địa chỉ bộ nhớ sau bởi vì sẽ có những khoảng trống lớn giữa chúng:

  • startOfHashTable + 0
  • startOfHashTable + 10
  • startOfHashTable + 20

Các bài viết trên Wikipedia về bảng băm nói rằng "chỉ số" được tính toán như vậy:

hash = hashfunc(key)
index = hash % array_size 

Vì vậy, trong ví dụ của tôi, các chỉ số sẽ là:

  • 0% 3 = 0
  • 10% 3 = 1
  • 20% 3 = 2

Điều này được thoát khỏi những khoảng trống lớn mà tôi đã đề cập trước đó. Ngay cả với sơ đồ modulo này, vẫn có vấn đề khi bạn thêm nhiều đối tượng vào bảng băm. Nếu tôi thêm một đối tượng thứ tư vào bảng băm, tôi sẽ cần áp dụng% 4 để lấy chỉ mục. Điều đó có làm mất hiệu lực tất cả các% 3 mà tôi đã làm trong quá khứ không? Tất cả những người trước đây của% 3 có cần được chuyển đến vị trí của% 4 không?

Câu trả lời:


15

Các mục của bảng băm được lưu trữ trong một mảng. Tuy nhiên, bạn đã hiểu nhầm ứng dụng của toán tử modulo vào các giá trị băm. Nếu bảng băm được lưu trữ trong một mảng kích thước n, sau đó hàm băm được tính toán modulo n, bất kể có bao nhiêu mục hiện đang được lưu trữ trong bảng. Vì vậy, trong ví dụ của bạn, nếu bạn đang lưu trữ các mục trong một mảng có kích thước 6, ba mục có giá trị băm 0, 10 và 20 sẽ được lưu trữ tại các vị trí 0, 4 và 2, tương ứng. Nếu bạn đã thêm một yếu tố thứ tư với giá trị băm, giả sử, 31, sẽ được lưu trữ tại vị trí 1, mà không cần phải di chuyển bất kỳ mục nào trong ba mục đầu tiên. Nếu bảng băm của bạn đã đầy và bạn muốn di chuyển nó thành một mảng lớn hơn, thì bạn sẽ cần tính toán lại vị trí của tất cả các mục trong bảng và di chuyển chúng một cách thích hợp.


1
Vì vậy, bạn đang nói rằng bảng băm được tạo với kích thước tiềm năng ước tính và các mục chỉ được di chuyển khi bạn cần tăng kích thước ... Vì vậy, không có vấn đề gì nếu hàm băm có phân phối đồng đều. Ví dụ: các giá trị băm 0, 5 và 10 được phân phối đồng đều, nhưng khi được chèn vào bảng băm có kích thước tiềm năng 5, tất cả chúng va chạm vào nhóm 0. Tốt hơn là hash % table sizenên phân phối đồng đều, không phải là băm chinh no.
Pwner 27/2/2015

@Pwner Tất cả điều đó là chính xác, vâng.
David Richerby 27/2/2015

1
Làm thế nào có thể tạo một phân phối đồng đều hash % tableSizekhi kích thước bảng có thể thay đổi? Các giá trị băm là 0, 5 và 10 tạo ra nhiều xung đột khi kích thước bảng là 5, nhưng không có xung đột khi kích thước bảng là 20.
Pwner 27/2/2015

1
@Pwner Hãy nhớ rằng hashtables chỉ có các hoạt động thời gian không đổi dự kiến , nếu vậy. Nhưng chỉ khi hàm băm là (xấp xỉ) thống nhất.
Raphael

1
@Pwner Việc phân phối không đồng nhất theo nghĩa đen - nhưng bạn sẽ nhắm đến sự gần gũi với đồng phục.
David Richerby

7

Bàn băm thường làm lãng phí không gian. Nhiều thuật toán làm, vì sự đánh đổi không gian thời gian là phổ biến, nhưng chúng thường che giấu nó tốt hơn :) . Giống như các thuật toán khác, các bảng băm làm điều đó để có hiệu suất thời gian tốt hơn.

Điểm đầu tiên là bạn cố gắng tránh các va chạm trong bảng băm của mình, vì điều đó giữ cho chi phí thời gian truy cập không đổi (nhưng các va chạm thường được cho phép và có thể được xử lý, do đó cho phép một số mục nằm trong cùng một mục, với chi phí thời gian ). Điểm thứ hai là bạn cố gắng tránh những khoảng trống lớn không sử dụng vì chi phí bộ nhớ. Điểm thứ ba là bạn tránh thay đổi chức năng băm của mình (do đó cũng là kích thước bảng) vì nó yêu cầu sắp xếp lại toàn bộ bảng, có chi phí thời gian lớn.

Thật không may, bạn càng có ít khoảng trống, nhiều khả năng một mục băm mới sẽ gây ra xung đột. Hàm băm tốt, đối với một tập dữ liệu nhất định, sẽ hạn chế khả năng va chạm ngay cả khi sử dụng tốt hơn không gian chỉ mục có sẵn.

Trên thực tế, bạn nên xem xét rằng có hai loại bảng băm: bảng tĩnh và bảng động.

Đối với dữ liệu tĩnh, dữ liệu được băm không thay đổi, vì vậy bạn có thể cố gắng tìm một hàm băm mà không có xung đột nào cho tập dữ liệu đó. Điều đó được gọi là một băm hoàn hảo . Nhưng tốt nhất là một hàm băm hoàn hảo tối thiểu , đạt được kết quả mà không có khoảng trống.

Nhưng điều đó là không khả thi khi dữ liệu được băm thay đổi linh hoạt, trong một tập hợp lớn các khả năng. Sau đó, bạn không thể tránh va chạm, nhưng bạn cố gắng hạn chế chúng bằng cách có đủ khoảng trống.

Có nhiều kỹ thuật để quản lý khác nhau, điều chỉnh kích thước bảng theo số lượng giá trị được băm, tăng bảng khi có nhiều va chạm hoặc giảm khi có khoảng trống quá lớn. Nhưng điều này phải được xử lý rất cẩn thận, bằng cách sử dụng các biến thể bảng theo cấp số nhân, để hạn chế tác động của việc sắp xếp lại bảng đối với chi phí chung của việc sử dụng bảng băm.

Điều này được dự định là một giới thiệu trực quan. Để biết thêm chi tiết kỹ thuật và tài liệu tham khảo, bạn có thể xem câu trả lời cho câu hỏi này: (Khi nào) là bảng băm tra cứu O (1)? . Bảng băm và băm là một chủ đề quan trọng, với nhiều biến thể.


3

Một cách tốt để xem các bảng băm giống như một bảng tra cứu với phạm vi chỉ mục vô hạn (tốt, không thực sự vô hạn, bạn vẫn bị hạn chế bởi giới hạn giá trị của khóa bạn đang sử dụng).

Giả sử bạn đang cố lưu trữ một số giá trị cụ thể của sqrt (x) trong bảng tra cứu trong đó X là số nguyên, nó sẽ có dạng như sau:

[1] = 1
[3] = 1.732
[10000] = 100

Điều này làm cho việc root vuông rất rẻ vì thay vì tính toán expencive, bạn chỉ cần lấy giá trị từ mảng. Tuy nhiên, việc sử dụng bộ nhớ rất kém hiệu quả vì [2] và [4 - 9999] trống.

Để giải cứu hàm băm, mục đích của hàm băm trong ngữ cảnh này là biến đổi chỉ mục thành một thứ thực sự phù hợp với một mảng có kích thước hợp lý, ví dụ như nó có thể làm điều này:

(1) = [5] = 1
(3) = [2] = 1.732
(10000) = [3] = 100

bây giờ tất cả 3 giá trị phù hợp trong một mảng có kích thước 6.

Làm thế nào để hàm băm đạt được điều này? Hàm băm cơ bản nhất là (Index% ArraySize), toán tử modulo chia Chỉ số bạn chọn theo kích thước của mảng và cung cấp cho bạn phần còn lại luôn nhỏ hơn kích thước mảng.

Nhưng nếu nhiều chỉ số băm cho cùng một kết quả thì sao? Điều này được gọi là va chạm băm và có nhiều cách khác nhau để đối phó với nó. Cách đơn giản nhất là lưu trữ từng giá trị cùng với Chỉ mục ban đầu của nó trong mảng, nếu vị trí mảng đó được lấy, hãy chuyển tiếp 1 cho đến khi tìm thấy một ô trống. Khi truy xuất giá trị, đi đến vị trí được cung cấp bởi hàm băm và lặp qua các phần tử cho đến khi tìm thấy giá trị có chỉ số gốc phù hợp.

Đây là lý do tại sao một hàm băm tốt cũng rất tốt trong việc phân tán dữ liệu để cho dù các chỉ mục đến là tuần tự hay ngẫu nhiên, kết quả băm phải được phân tán rộng nhất có thể để giữ chi phí truy cập dữ liệu tương đối ổn định.

Tất nhiên, mảng bên dưới càng lớn, bạn sẽ càng ít va chạm để có sự đánh đổi giữa tốc độ và hiệu quả kích thước. Các bảng băm hiện đại thường lấp đầy tới ~ 70% trong khi có ít hơn 10 va chạm cho mỗi lần truy cập. Cùng với hàm băm, điều này có nghĩa là mỗi dữ liệu tìm nạp chi phí ~ 20 chu kỳ (đối với một số mục đích) là một sự thỏa hiệp tốt giữa tốc độ (bảng tra cứu) và hiệu quả (danh sách).

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.