3.1. Khái niệm bảng băm
Như chúng ta đã biết, để lưu trữ danh sách các phần tử dữ liệu ta có thể dùng mảng. Ưu điểm của việc dùng mảng là ta có thể ngẫu nhiên truy cập vào bất kì phần tử nào của mảng thông qua chỉ số của phần tử. Từ chỉ số của phần tử có thể tính toán ra địa chỉ của vùng bộ nhớ lưu trữ phần tử đó để truy cập vào phần tử một cách nhanh chóng với thời gian không phụ thuộc vào kích thước của mảng, hay ta nói thời gian truy cập là O(1). Tuy nhiên, hạn chế của việc sử dụng mảng là phải trù số phần tử tối đa của mảng thường lớn hơn rất nhiều so với số phần tử thực tế được sử dụng dẫn đến lãng phí bộ nhớ.
Để dễ hiểu hơn ta xem xét ví dụ sau. Giả sử ta có 100 sinh viên mỗi sinh viên có một mã số là các số nguyên nằm có giá trị nằm trong đoạn từ 0 tới 999. Để có thể truy cập ngẫu nhiên với thời gian O(1) vào từng sinh viên thông qua mã số ta lưu trữ 100 sinh viên trên vào một mảng có 1000 phần tử, sinh viên có mã số là i sẽ được lưu trữ vào phần tử có chỉ số i của mảng. Theo cách này thời gian truy cập nhanh được đánh đổi bằng việc lãng phí bộ nhớ do ta chỉ sử dụng 1/10 không gian bộ nhớ cấp phát cho mảng.
Vấn đề đặt ra là làm thế nào vẫn sử dụng mảng lưu trữ 100 sinh viên trên để có thể truy cập nhanh với thời gian O(1) nhưng số phần tử của mảng ít hơn nhiều so với 1000 phần tử như trên, lý tưởng là chỉ dùng 100 phần tử? Để giải quyết vấn đề này người ta nghiên cứu và xây dựng một hàm số để ánh xạ mỗi một mã số sinh viên gọi là khóa với chỉ số của một phần tử của mảng. Trường hợp lý tưởng là có một hàm số để ánh xạ toàn bộ 100 mã số sinh viên nằm trong đoạn 0 tới 999 với 100 chỉ số khác nhau (có giá trị trong đoạn từ 0 tới 99) của 100 phần tử của mảng. Khi đó mỗi sinh viên với một khóa được lưu trữ vào một phần tử của mảng có chỉ số tương ứng với giá trị khóa đó. Hàm số này gọi là hàm băm (Hash Function). Kỹ thuật này gọi là kỹ thuật băm. Giá trị của hàm băm tính trên một khóa gọi là mã băm của khóa đó. Mảng các phần tử lưu trữ 100 sinh viên dựa trên một hàm băm gọi là bảng băm
(Hash Table). Hàm băm phải là một hàm số đơn giản để khi tìm kiếm, truy cập một sinh viên theo khóa thì việc tính toán từ khóa ra chỉ số của phần tử lưu trữ trong mảng nhanh chóng. Chẳng hạn một hàm băm được định nghĩa
H(k) = k mod m
Trong đó H(k) là hàm băm, từ giá trị khóa k tính toán ra mã băm bằng k chia lấy dư cho m
là số phần tử của bảng băm (m được chọn trước là số nguyên tố lớn hơn số phần tử cần lưu trữ)
Như trình bày ở trên, Trường hợp xây dựng được một hàm số biến đổi từ 100 giá trị khóa là các số nguyên ngẫu nhiêu trong đoạn 0 tới 999 thành 100 giá trị chỉ số khác nhau từ 0 tới 99 chỉ là trường hợp lý tưởng. Trên thực tế, sử dụng các hàm băm hai khóa khác nhau có thể có mã băm giống nhau. Nếu tham chiếu vào ví dụ trên thì có nghĩa là hai sinh viên với hai mã số sinh viên khác nhau qua hàm băm được cùng một mã băm. Nếu lấy luôn mã băm làm chỉ số của phần tử lưu trữ trong bảng băm như ví dụ trên thì rõ ràng không thể vì hai sinh viên không thể lưu trữ trong cùng một phần tử. Hiện tượng này gọi là đụng độ. Lấy ví dụ hàm băm ta sử dụng là hàm chia lấy dư ở trên với số phần tử của bảng băm là m = 101 thì
Sinh viên có khóa k=104, H(104) = 104 mod 101 = 3 Sinh viên có khóa k=306, H(306) = 306 mod 101 = 3
Mã băm của hai khóa 104 và 306 đều là 3. Rõ ràng hai sinh viên không thể lưu cùng vào phần tử có chỉ số bằng 3 được mà ta phải tổ chức lưu trữ theo cách khác.
Trong kỹ thuật băm, người ta phải tổ chức lưu trữ bảng băm làm sao để hạn chế tối đa sự đụng độ và có thể dễ dàng xử lý đụng độ bằng các thuật toán nhanh, gọn. Với cách lưu trữ và các thuật toán xử lý đụng độ có thể xác định chính xác vị trí lưu trữ của một phần tử theo giá trị khóa cho dù mã băm của khóa này có thể trùng lặp với khóa khác. Khóa của các phần tử không chỉ giới hạn là kiểu số mà có thể là các kiểu dữ liệu khác như chuỗi ký tự
Trong C#, Bảng băm (Hashtable) là một lớp ứng dụng và cài đặt các kỹ thuật băm cho phép tạo lập, lưu trữ và xử lý các tập hợp gồm các phần tử là các cặp (<khóa>,<giá trị>). Mỗi một phần tử có một khóa riêng biệt được lưu trữ trong một phần tử của bảng băm với chỉ số là mã băm được tự động sinh ra bằng việc áp dụng các hàm băm thông dụng. Theo đó, khi sử dụng đối tượng Hashtable ta có thể tìm kiếm, truy cập vào một phần tử theo khóa của phần tử đó với thời gian O(1) không phụ thuộc vào số lượng phần tử
3.2. Khởi tạo bảng băm
Để sử dụng bảng băm trước hết ta phải tạo đối tượng. Bảng băm có nhiều phương thức khởi tạo khác nhau, sau đây là một số phương thức khởi tạo hay dùng
Hàm tạo Mô tả
Hashtable(IDictionary) Nhận tham số vào kiểu IDictionary và khởi tạo đối tượng Hashtable bằng cách sao chép tất cả phần tử thuộc IDictionary. ArrayList(int) Khởi tạo đối tượng bảng băm với không gian là số phần tử bằng
với tham số truyền vào
Bảng (VI.6) Các phƣơng thức khởi tạo bảng băm (Hashtable) 3.3. Thuộc tính và phƣơng thức
Một số thuộc tính hay dùng của bảng băm Tên thuộc tính Mô tả
Count Số phần tử hiện tại được lưu trong danh sách Item Cập nhật giá trị của phần tử có khóa cho trước
Keys Trả ra một ICollection lưu trữ các khóa của bảng băm Values Trả ra một ICollection lưu trữ các giá trị của bảng băm
Bảng (VI.7) Các thuộc tính của bảng băm
Một số phương thức hay dùng của bảng băm Tên phương thức Mô tả
Add Thêm một phần tử vào cuối với khóa và giá trị Remove Xóa một phần tử có khóa xác định
CopyTo Sao chép các phần tử trong bảng băm sang mảng
ContainsKey Kiểm tra trong bảng băm có khóa được chỉ định hay không ContainsValue Kiểm tra trong bảng băm có giá trị được chỉ định hay không ToString Trả về chuỗi giá trị cho phần tử được chỉ định
Equals So sánh bảng băm hiện thời với bảng băm được chỉ định trong tham số có bằng nhau không
Bảng (VI.8) Các phƣơng thức của bảng băm Ví dụ (VI.3) Sử dụng bảng băm (Hashtable)
Bước 1: Thêm một dự án console với tên „HashtableDemo‟ vào solution Session_VI
Bước 2: Thiết lập „HashtableDemo‟ là dự án mặc định bằng cách click chuột phải vào tên dự án và chọn Set As Statup Project
Bước 3: Sửa lớp Program.cs theo mã lệnh bên dưới. Lưu ý trước khi thêm mã lệnh bên dưới vào lớp Program phải khai báo sử dụng không gian tên System.Collection bằng từ khóa using using System.Collections; namespace HashtableDemo { class Program {
static void Main(string[] args) {
Hashtable Htable = new Hashtable(); Htable.Add(101, "John");
Htable.Add(203, "Carter"); Htable.Add("name", "Thomas"); Htable.Add(113, "Marry");
Console.WriteLine("Count: {0}", Htable.Count);
//access element by key
Console.WriteLine("Access element by key");
Console.WriteLine("Key: {0}, value: {1}", 101, Htable[101]);
Console.WriteLine("Key: {0}, value: {1}", "name", Htable["name"]); //access all elements
Console.WriteLine("Access all element");
foreach (DictionaryEntry de in Htable)
Console.WriteLine("Key: {0}, value: {1}", de.Key, de.Value);
Console.ReadLine();
} } } }
Bước 4: Dịch và chạy chương trình
Hình (VI.4) Màn hình hiển thị Ví dụ (VI.3)
Trong Ví dụ (VI.3) cho thấy, khi dùng bảng băm ta có thể gọi phương thức Add để thêm các phần từ là các cặp (<khóa>,<giá trị>) vào trong bảng băm. Khóa không chỉ có thể là số mà còn là các kiểu dữ liệu khác như chuỗi. Để truy cập các phần tử ta có thể dùng khóa. Để truy cập tất cả các phần tử của bảng băm ta dùng vòng lặp foreach