You are on page 1of 9

Bảng băm

Cấu trúc dữ liệu và giải thuật – INT2210 24

Lê Duy Quang – 21020380


Hồng Quân – 21029999 Đức Tân – 21029999

1/11/2022
I – Khái niệm

Trong nhiều bài toán trong thực tế, thường nảy sinh ra nhu cầu gán mỗi một
giá trị nguồn ﴾khóa﴿ tương ứng với một giá trị đích ﴾giá trị﴿ nào đó. Cách đơn
giản nhất là sử dụng một mảng để lưu trữ các cặp khóa – giá trị, rõ ràng là
không thể dùng cách này cho một số lượng lớn giá trị được do phải truy cập
vào từng phần tử trong mảng. Có nhiều giải pháp để làm tăng hiệu quả tìm
kiếm khóa và theo đó là giá trị tương ứng, một trong số đó là sử dụng cấu trúc
dữ liệu bảng băm.
Bảng băm là một cấu trúc dữ liệu dưới dạng như một từ điển: các phần tử là
các cặp khóa – giá trị và bằng cách truy vấn sử dụng khóa thì có thể lấy được
giá trị tương ứng. Kĩ thuật chủ đạo của cấu trúc dữ liệu này là kĩ thuật băm
﴾hashing﴿: mỗi giá trị khóa sẽ được sử dụng làm tham số đầu vào cho một hàm
tính toán gọi là hàm băm, kết quả của hàm này là một số tự nhiên nằm trong
một khoảng cụ thể gọi là giá trị băm. Giá trị băm sẽ được sử dụng để truy cập
bảng băm và tìm ra giá trị đích tương ứng, thường thì bảng băm sẽ là một
mảng với giá trị băm được sử dụng làm chỉ số truy cập vào trong mảng. Do
đó, một khi đã tính toán được giá trị băm thì việc truy cập giá trị đích trở nên
rất dễ dàng.

II – Cấu tạo

1. Hàm băm

Một hàm băm h(k) nhận vào một giá trị khóa k và cho ra một số tự nhiên
nằm trong khoảng [0; m). Giới hạn m cho phép biểu diễn giá trị băm với một
độ dài cố định và thường thì đây cũng chính là kích cỡ của bảng băm. Một hàm
băm được coi là tốt phải thỏa mãn các điều kiện sau:

1. Tốc độ tính toán cao.


2. Các giá trị băm được phân bố đều trong miền giá trị.
3. Xử lí được các loại giá trị khóa khác nhau.

Điều kiện 1 là cần thiết do hàm băm được thực hiện cho mọi thao tác trên
bảng băm. Điều kiện 2 là cần thiết để có thể giảm thiểu các trường hợp có

2
nhiều giá trị khóa sinh ra chung một giá trị băm, gây ra đụng độ. Điều kiện 3
cho phép dễ dàng sử dụng bảng băm cho nhiều kiểu dữ liệu khác nhau.
Dưới đây là một số ví dụ về các hàm có thể được sử dụng làm hàm băm cho
bảng băm.

a﴿ Hàm băm sử dụng phép chia lấy dư

Đây là một trong những hàm băm đơn giản nhất, có dạng h(k) = k mod m,
với k là một số nguyên dương. Nếu chọn m = bd với b và d là các số nguyên
dương thì h(k) sẽ là d chữ số cuối cùng của k biểu diễn dưới cơ số b, tức là
không phụ thuộc hoàn toàn vào tất cả nội dung của k, do đó cần phải tránh
trường hợp này. Tốt nhất là nên chọn m sao cho h(k) phụ thuộc đầy đủ vào
khóa k, thông thường m sẽ được chọn là một số nguyên tố.

b﴿ Hàm băm sử dụng phương pháp nhân

Hàm băm có dạng h(k) = ⌊m frac(kA)⌋, với√0 < A < 1 và k là một số nguyên
5−1
dương. Một giá trị được coi là tốt cho A là ; các giá trị được coi là tốt
2
cho m là 2p với p là một số nguyên dương.

c﴿ Hàm băm phổ quát

Nếu lựa chọn được một tập hợp các hàm băm H sao cho với mọi hàm h(k)
thuộc H và hai khóa phân biệt k1 và k2 xác suất h(k1 ) = h(k2 ) là m-1 , thì H được
gọi là một tập hợp hàm băm phổ quát. Khi sử dụng một hàm băm ngẫu nhiên
từ H cho mỗi khóa thì khả năng xảy ra đụng độ sẽ thấp.

III – Bảng băm

Bảng băm ở bên trong là một mảng có kích cỡ xác định chứa các giá trị đích.
Giá trị khóa được sử dụng làm chỉ số để truy cập vào mảng này đến giá trị đích
tương ứng với khóa.
Các thao tác cơ bản trên bảng băm là:

3
• Thêm phần tử: tìm một chỗ trống trong bảng băm tương ứng với khóa
và đặt giá trị đích vào chỗ đó.
• Tra cứu: tìm vị trí trong bảng ứng với khóa được cho và trả về giá trị đích
tại vị trí đó nếu có.
• Xỏa phần tử: tra cứu tra vị trí trong bảng tương ứng với khóa, nếu tồn
tại, loại bỏ phần tử tại vị trí đó khỏi bảng.
Do không gian các giá trị khóa thường lớn hơn không gian giá trị băm, nên
chắc chắn sẽ có các tập hợp giá trị khóa với giá trị băm giống nhau, đây gọi là
sự đụng độ. Khi đó, cần thiết phải có các cơ chế xử lí để bảng băm vẫn có thể
hoạt động đúng và hiệu quả. Một số phương pháp xử lí là:
• Kết nối trực tiếp: mỗi vị trí của bảng băm chứa một danh sách liên kết,
chứa các khóa có cùng giá trị băm tương ứng với vị trí đó cùng với các
giá trị đích tương ứng.
• Dò tuần tự: mỗi vị trí của bảng băm chỉ chứa một phần tử, khi thêm một
phần tử mới mà vị trí tương ứng đã có một phần tử khác, các vị trí tiếp
theo sẽ được xét đến cho đến khi tìm thấy một vị trí trống để đặt phần tử
vào; khi tìm kiếm, thực hiện xét từng vị trí bắt đầu từ vị trí tương ứng với
giá trị băm cho đến khi tìm thấy vị trí chứa khóa cần tìm hoặc tìm thấy
một ô trống.
• Dò tuyến tính: tương tự như dò tuần tự, với vị trí tiếp theo để xét cách
một khoảng cố định D vị trí so với vị trí trước đó.
• Băm kép: tương tự như dò tuần tự, với vị trí tiếp theo để xét cách d(k) vị
trí so với vị trí trước đó, d(k) là một hàm băm khác.
• Kết nối hợp nhất: mỗi phần tử chứa một con trỏ đến phần tử tiếp theo
đang xung đột.
Các phương pháp dò được gọi chung là phương pháp đánh địa chỉ mở,
ngược lại phương pháp kết nối trực tiếp là một phương pháp đánh địa chỉ
đóng.

IV – Đặc điểm và tính chất

Bảng băm không giữ lại thứ tự của các phần tử, do hàm băm đã thực hiện
xáo trộn và rải đều các phần tử vào các vị trí của bảng.
4
Một thông số đáng lưu tâm đối với bảng băm là hệ số tải, đây là tỉ lệ giữa số
phần tử được lưu trữ trong bảng băm tại một thời điểm và số vị trí của bảng
băm. Với một hàm băm đạt chất lượng, thao tác tra cứu một giá trị trong bảng
băm sẽ có thể được thực hiện với chi phí không đổi khi hệ số tải không quá
1; trong trường hợp còn lại, các sự xung đột sẽ làm cho thao tác phải tốn thời
gian để giải quyết, hoặc trong trường hợp sử dụng phương pháp đánh địa chỉ
mở, không còn chỗ trong bảng băm để có thể chứa thêm các phần tử mới nữa.
Khi đó, cần phải tạo ra một bảng băm mới có kích cỡ lớn hơn, kéo theo đó là m
cũng sẽ lớn hơn, nên cần phải băm lại các khóa của các phần tử để đưa chúng
vào bảng băm mới. Theo chiều ngược lại, một hệ số tải quá nhỏ cũng có nghĩa
là phần lớn các vị trí không được dùng đến, gây ra lãng phí; khi đó có thể tạo
lại một bảng băm nhỏ hơn và băm lại các phần tử vào bảng mới.

V – Ví dụ cài đặt và sử dụng

VI – So sánh với một số cấu trúc dữ liệu khác

1. Mảng danh sách

Với việc phải duyệt qua toàn bộ các phần tử của mảng để tìm một phần tử
mong muốn thì lợi thế về mặt chi phí của bảng băm so với mảng là quá rõ
ràng. Tuy nhiên trong một số trường hợp thì có một yêu cầu giữ lại trình tự
chèn vào của các phần tử, một giải pháp có thể là sử dụng kết hợp mảng với
bảng băm; khi đó phép chèn vẫn có độ phức tạp là O(1) nhưng phép xóa sẽ là
O(n).
Về mặt bản chất, bảng băm cũng thực chất chỉ là một mảng, có điều các
phần tử được sắp xếp theo quy luật sao cho có thể tìm kiếm nhanh vị trí của
chúng bên trong mảng.

2. Cây tìm kiếm nhị phân cân bằng

Việc tìm kiếm một phần tử trong cây nhị phân cần O(log(n)) bước, ứng với
mỗi bước là một phép so sánh phần tử cần tìm kiếm với phần tử đang được trỏ
tới trong cây, tức là một lần truy cập tới dữ liệu tại nút cây đó. Đối với những

5
kiểu dữ liệu có kích thước lớn thì việc so sánh sẽ trở thành chi phí chủ yếu cho
việc tìm kiếm. Trong khi đó, một bảng băm được thiết kế hợp lí trong phần lớn
các trường hợp chỉ cần một lần tính toán giá trị băm của giá trị cần tìm, một
lần truy cập vào ô tương ứng với giá trị băm đó và một hay một vài lần so sánh
với các phần tử tại ô đó. Vì thế về mặt chi phí, bảng băm thường tốn ít chi phí
hơn so với cây tìm kiếm nhị phân.
Về độ thân thiện với bộ đệm, các phần tử của cây tìm kiếm nhị phân khi
được duyệt thường nằm rải rác trong bộ nhớ, mà mỗi lần tìm kiếm lại có một
con số tương đối các phần tử cần phải truy cập dữ liệu nên nhìn chung cây tìm
kiếm nhị phân không thân thiện với bộ nhớ đệm của CPU, điều này càng làm
tăng thêm chi phí về mặt thời gian để tìm kiếm phần tử. Bảng băm thưởng chỉ
truy cập một số rất ít phần tử nên ảnh hưởng của việc trượt bộ đệm nếu có là
không đáng kể.
Khi thêm phần tử mới, cây nhị phân cần phải thực hiện các bước duyệt tương
tự như khi tìm kiếm, rồi sau đó phải thực hiện di chuyển các nút để thiết lập lại
trạng thái cân bằng, thường thì không phải tất cả các nút đều bị di chuyển mà
chỉ là những nút nằm lân cận với nhánh cây chứa phần tử vừa được thêm vào
cây, tuy nhiên nếu cây được hiện thực hóa sử dụng một mảng thì khi mảng hết
chỗ, tất cả các phần tử phải được di chuyển sang mảng mới có kích thước lớn
hơn. Bảng băm chỉ cần chèn phần tử mới vào vị trí tương ứng với giá trị băm
tìm được, tuy nhiên khi hết dung lượng lưu trữ, các phần tử không chỉ phải
được di chuyển sang vị trí lưu trữ mới, mà còn phải được tính toán lại giá trị
băm để sắp xếp vào số ô lưu trữ mới với số lượng lớn hơn, trừ khi các giá trị
băm được lưu trữ cùng với các phần tử. Nhìn chung, bảng băm cũng tốn ít chi
phí hơn khi thêm phần tử so với cây tìm kiếm nhị phân.
Khi xóa phần tử, cả hai cấu trúc dữ liệu trước hết đều phải thực hiện thao tác
tìm kiếm vị trí phần tử để loại bỏ; sau đó, cây tìm kiếm nhị phân phải thực hiện
loại bỏ nút tìm được ra khỏi cây, một thao tác có thể ảnh hưởng đến các nút
khác và làm cho chúng phải di chuyển, sau đó lại còn phải tiếp tục thực hiện
thao tác cân bằng lại cây; còn bảng băm chỉ cần đơn giản xóa dữ liệu phần tử
trong ô lưu trữ đi. Như vậy, trong nhiệm vụ xóa phần tử thì bảng băm thậm chí
còn tốn ít chi phí hơn nữa so với cây tìm kiếm nhị phân.
Tuy vậy, một đặc điểm chỉ cây tìm kiếm nhị phân có mà bảng băm không có
chính là thứ tự của các phần tử có thể được khôi phục một cách dễ dàng. Khi
có nhu cầu như vậy thì về mặt thực tế độ chênh lệch chi phí khi tìm kiếm cũng
không phải là lớn, nên cũng không thực sự cần thiết phải kết hợp với bảng

6
băm.

3. Danh sách nhảy cóc ﴾skip list﴿

3 44

3 14 44 72

3 8 14 21 44 57 72 90

Hình 1. Ví dụ một danh sách nhảy cóc.

Dựa trên nguyên lí “chia để trị”, danh sách nhảy cóc gồm nhiều tầng, mỗi
tầng là một danh sách liên kết. Tầng thấp nhất chứa mọi phần tử sắp xếp theo
thứ tự tăng dần, tầng phía trên chứa một tỉ lệ nhất định các phần tử của tầng
bên dưới, phân bố sao cho vị trí các phần tử cách đều nhau, cứ như vậy cho
đến tầng cuối cùng chứa một số tuyệt đối rất ít phần tử. Khi tìm kiếm, tầng
trên cùng được duyệt qua trước, từ đó quyết định được phần nào trong danh
sách chứa phần tử cần tìm kiếm và phạm vi tìm kiếm do đó được thu hẹp, rồi
tầng tiếp theo bên dưới được duyệt bắt đầu từ vị trí tương ứng tìm được ở
tầng bên trên, cứ như vậy cho đến khi tìm thấy giá trị yêu cầu hoặc đã hết tầng
cuối cùng mà không tìm thấy.
Do phạm vi tìm kiếm được chia nhỏ theo tỉ lệ qua mỗi tầng, độ phức tạp tìm
kiếm trung bình là O(log(n)). Độ phức tạp khi thêm và xóa phần tử trung bình
cũng là O(log(n)), như vậy danh sách nhảy cóc vẫn có các chi phí hoạt động
cao hơn bảng băm. Và do được cấu tạo từ các danh sách liên kết nên cấu trúc
dữ liệu này cũng chịu các bất lợi liên quan đến bộ đệm của CPU. Tuy nhiên
danh sách nhảy cóc cũng bảo toàn thứ tự sắp xếp của các phần tử và các tính
chất của nó mang lại các thuận lợi khi thiết kế các ứng dụng đa luồng [1].

7
4. Trie

a k s

ab an ke so

and key
Hình 2. Ví dụ một trie. Các nút màu xám biểu thị phần tử tồn tại.

Đối với các giá trị có kiểu dữ liệu chuỗi, trie trở thành một lựa chọn cho việc
tìm kiếm. Trie có thể được biểu diễn bằng một cây với mỗi nút có giá trị là một
chuỗi và một cờ cho biết có phần tử ứng với chuỗi đó hay không. Nút gốc là
chuỗi rỗng, mỗi nút có các nút con có chuỗi dài hơn một mắt xích và có các
mắt xích còn lại trùng với nút cha. Thông thường các giá trị có thể của một mắt
xích là một con số không lớn nên các nút con có thể được lưu vào một mảng
có kích cỡ bằng số giá trị thông qua chỉ số bằng giá trị mắt xích cuối cùng.
Để tìm kiếm, thực hiện thăm bắt đầu từ gốc của cây, tìm kiếm trong các con
của nút hiện tại có nút nào có giá trị trùng với phần mở đầu độ dài tương ứng
của giá trị cần tìm hay không, thực chất thì chỉ cần so sánh giá trị mắt xích cuối
cùng của các con. Nếu có thì thực hiện thăm nút con tương ứng và lặp lại đến
khi không tìm thấy con nữa hoặc độ dài con bằng với độ dài giá trị cần tìm, khi
đó kiểm tra giá trị cờ của nút con để xác định xem giá trị cần tìm có tồn tại hay
không.
Số lần thăm trong trường hợp xấu nhất bằng với số mắt xích của giá trị cần
tìm, trong khi đó bảng băm luôn luôn phải duyệt toàn bộ các mắt xích để tính
toán ra giá trị băm, do đó về mặt này thì trie tỏ ra có lợi thế hơn, thể hiện rõ
khi giá trị của các phần tử có xu hướng rời rạc làm cho phần lớn các lần tìm
kiếm gặp ngõ cụt trước khi duyệt hết các mắt xích. Hơn nữa việc cài đặt một
trie không yêu cầu dùng đến một hàm băm, vừa giảm độ phức tạp khi cài đặt,
vừa giảm chi phí thực hiện hàm băm đó. Tuy nhiên do là một cây nên trie cũng
8
gặp các bất lợi về truy cập bộ nhớ do phải truy cập nhiều lần hơn và vị trí của
dữ liệu cũng rời rạc. Một mặt khác khi các giá trị có xu hướng rời rạc là một số
lượng không nhỏ các ô trong bảng tra cứu nút con của mỗi nút là các ô trống,
gây ra lãng phí bộ nhớ.
Và trie vẫn có đặc điểm là thứ tự sắp xếp của các phần tử có thể được khôi
phục thông qua việc duyệt cây theo chiều sâu, hơn thế nữa còn cho phép liệt
kê chỉ các phần tử có giá trị bắt đầu bằng một tiền tố nào đó.

5. Kết luận

Bảng băm có ưu thế về chi phí hoạt động so với phần lớn các cấu trúc dữ
liệu khác, tuy nhiên nó không có những tính năng đặc biệt mà các cấu trúc dữ
liệu khác mang lại, chủ yếu là tính năng về đảm bảo trật tự của các phần tử,
và hàm băm tốn một chi phí nhất định để thực hiện. Do vậy vẫn phải tùy vào
các yêu cầu cụ thể để chọn các cấu trúc dữ liệu phù hợp, với bảng băm thì có
thể được chọn khi dữ liệu có số lượng không quá nhỏ và các phần tử không
có yêu cầu được sắp xếp theo một trình tự nào đó.

Tham khảo

[1] Fomitchev Mikhail, Lock‐free linked lists and skip lists, York University, 2003.

You might also like