Dựng danh sỏch liờn kết

Một phần của tài liệu Tài liệu hỗ trợ môn cấu trúc dữ liệu 2 (Trang 30)

Ch−ơng 1 Sắp xếp ngoạị

2.2. Các phơng phỏp trỏnh đụng độ

2.2.1. Dựng danh sỏch liờn kết

Cũng giống nh− trong phần cấu trỳc danh sỏch chỳng ta đà núi đến, danh sỏch liờn kết là một tập hợp cỏc bản ghi đ−ợc sắp tuyến tớnh; mỗi bản ghi cú 2 phần: phần chứa thụng tin và phần chứa địa chỉ của bản ghi tiếp the Thụng th−ờng bản ghi cuối cựng chỉ đến bản ghi rỗng tức là NULL.

ạ Ph−ơng phỏp liờn kết ngoài

Cỏc thành phần của H chứa địa chỉ đầu của cỏc danh sỏch liờn kết. Cỏc nỳt bị xung đột tại vị trí i sẽ đ−ỵc kết nối thành danh sỏch liờn kết do con trỏ tại i trỏ tớị

Khi thêm một nỳt mới vào bảng băm, hàm băm h(k) sẽ xỏc định vị trớ i trong khoảng từ 0 đến m-1, sau đú nỳt này sẽ đợc thờm vào danh sỏch Thớ dụ nếu hàm băm là hàm lấy phần d− khi chia k cho m thỡ nỳt i sẽ trỏ đến danh sỏch liờn kết chứa cỏc bản ghi cú khúa k chia cho m d− Hỡnh 2.1 sau đõy minh họa bảng băm với hàm băm chia d− và m = 3

Hỡnh 2.1 Bảng băm liờn kết ngoà

ở đõy cỏc ụ 0 đ−ợc hiểu là địa chỉ nỳt đầu của danh sỏch cỏc khúa đồng d− với 0 mod 3, ô 1 đ−ợc hiểu là địa chỉ nỳt đầu của danh sỏch cỏc khúa đồng d với 1 mod 3.

b. Ph−ơng phỏp liờn kết trong

Ta vẫn dùng danh sách liờn kết để trỏnh đụng độ, nh−ng ta chỉ dùng cỏc nỳt trong bảng H mà thụ Mỗi nỳt của H bây giờ có 2 tr−ờng: tr−ờng thứ nhất Pkey chỉ đến bản ghi chứa dữ liệu, còn tr−ờng thứ 2 là tr−ờng next chỉ đến nỳt tiếp theo trong danh sỏch cỏc nỳt cú giỏ trị khúa bị đụng độ. Khi khởi động cỏc con trỏ Pkey chỉ tới NULL, các tr−ờng next = -1 để ỏm chỉ là khụng cú nỳt nào liờn kết với nỳt đú. Khi thờm một nỳt có khóa k tr−ớc hết ta tính hàm i = h(k). Nếu vị trí i ch−a có nút, nghĩa là tr−ờng Pkey chỉ đến NULL thỡ ta đặt nỳt mới vào vị trớ Nếu nỳt đú đà bị chiếm thỡ ta phõn biệt 2 tr−ờng hỵp:

Nếu giỏ trị khúa trong bản ghi do H[i].Pkey chỉ tới (vớ dụ khúa này là x) cùng họ với k (tức là h(x)=h(k)) ta lần theo tr−ờng next để đi tới bản ghi cuối cựng trong danh sách con; sau đú ta tỡm một nỳt trống cuối bảng để đặt nỳt mới vào đú. Đặt con trỏ next của phần tử cuối cùng tr−ớc đó trỏ tới nỳt này và đặt con trỏ next của nút này bằng -1 và kết thúc.

b. Nếu giỏ trị khúa trong bản ghi do H[i].Pkey chỉ tới khụng cựng họ với k (tức là h(x)≠h(k)) ta tỡm một nỳt trống cuối bảng để đặt nỳt mới vào đú. Đặt con trỏ next cđa nút này bằng -1 và kết thức.

Khi cần tỡm kiếm khúa x trờn bảng băm, tr−ớc hết ta đặt i = h(x). Nếu tr−ờng Pkey trong vị trớ này chỉ đến nỳt cú khúa = x việc tỡm kiếm thành cụng và kết thỳc; Cũn nếu khụng nh− vậy thì ta xem khóa do ụ này trỏ tới cú cựng họ với x khụng. Nếu cú thỡ ta lần theo tr−ờng next , cho đến khi gặp khúa x thỡ việc tỡm kiếm thành cụng kết thỳc; nếu gặp next = -1 mà vẫn khụng tỡm

0 1 2 6 15 18 7 5 23

http://www.ebook.edụvn

Cấu trúc dữ liƯu 2 – Ch−ơng 2. Bảng băm

31 thấy thỡ việc tỡm kiếm khụng thành cụng. Nếu ụ thứ i khụng cựng họ với x thỡ ta bắt đầu xuất phỏt từ đỏy của bảng, tức là vị trớ max-1 đi dần lờn phớa trờn để tỡm và dừng lại khi gặp ụ đầu tiờn cũn trống. Kết quả tỡm kiếm phụ thuộc vào việc tỡm thấy x hay khụng.

Hỡnh sau biểu diễn cỏch thờm cỏc nỳt 35, 15,45 vào bảng chia d− có m=10.

i pkey next 0 -1 1 -1 2 -1 3 -1 4 -1 5 35 9 6 -1 7 -1 8 45 -1 9 15 8

Hỡnh 2.2 Bảng băm liờn kết trong.

ở đõy ta ghi giỏ trị khúa thay cho địa chỉ của cỏc nỳt thơng tin chứa khóạ Nh− vậy số 35 sẽ đợc hiểu là địa chỉ cđa khóa 35.

2.2.2. Dùng danh sỏch kề ngồi

Phơng phỏp này về cơ bản giống với phơng phỏp liờn kết ngoà Ta cũng dựng một mảng H, nh−ng cỏc thành phần của H bõy giờ là cỏc danh sỏch kề cài đặt bằng mảng động. Cỏc danh sỏch con này tuy cú cỡ cố định nh−ng ta có thể dựng tỏc vụ grow để tăng lờn khi cần thiết. Nh− vậy khi khai bỏo, để khỏi lÃng phớ bộ nhớ ta chỉ cần đặt cỡ của danh sỏch khỏ nhỏ; khi cần thiết tác vơ grow sẽ tự động tăng cỡ của danh sỏch lờn.

Cú thể mụ tả bảng băm dùng danh sách kỊ với kích thớc và cỏc khúa nh− ở hình 2.1. nh− sau:

Hình 2.3 Bảng băm dùng danh sách kỊ

ngồị

Khi dựng danh sỏch liờn kết ngoài hoặc danh sỏch kề ngoài ta thấy rằng nếu cỡ của bảng băm là M thỡ ta cần M danh sỏch. Kinh nghiệm cho thấy rằng nờn chọn M đủ lớn sao cho độ dài cỏc danh sỏch chỉ khoảng 10 phần tư. Nh− vậy M bằng khoảng 1/10 số khóa sư dơng.

2.2.3. Ph−ơng phỏp dũ tuyến tớnh (linear probing method)

Bản chất cđa phơng phỏp này là nờu ra quy tắc để dựng cỏc nỳt cũn trống trong bảng để chứa cỏc nỳt bị đụng độ. Với một khúa k bất kỳ ta tớnh hàm băm i = h(k) và thực hiƯn các b−ớc sau: - Nếu nỳt i trống thỡ thờm nỳt mới tại địa chỉ nà

- Nếu bị xung đột thỡ dựng hàm băm lại h1(k) = (h(k)+1)%m - Nếu vẫn bị xung đột thỡ dựng hàm băm lại h2(k) = (h(k)+2)%m

0 1 2 6 15 18 7 5 23

Cấu trúc dữ liệu 2 – Ch−ơng 2. Bảng băm

Cứ nh− vậy đến b−ớc thứ j ta dựng hàm băm lại hj(k) = (h(k)+j) % m cho đến khi tỡm đợc vị trớ trống. L−u ý là nếu dũ đến cuối bảng thỡ ta lại quay lại từ đầu (bản thõn hàm hj(k) đã bao hàm điỊu này).

Nhận xột bảng băm dựng phơng phỏp dũ tuyến tính:

- Khi tìm một nút có khóa k, tr−ớc hết ta tính i = h(k) để xỏc định vị trớ xuất phỏt trong bảng băm. Sau đú ta xột từng nỳt trong khối đặc chứa cỏc nỳt xuất phỏt từ i, nỳt nào cú khúa trựng với k là nỳt cần tỡm.

- Tác vơ xóa nút khá phức tạp.

- Bảng băm cài đặt kiểu này khi chỉ cú cỏc nỳt th−a thớt trờn bảng. Trờng hợp xấu nhất là bảng băm gần đầy, lỳc này hỡnh thành một khối đặc cú n nỳt nờn tốc độ truy xuất có bậc O(n).

2.2.4. Phơng phỏp dị bậc hai (quadratic probing method)

Bảng băm dũ tuyến tớnh bị hạn chế là cỏc nỳt rải khụng đề Ngời ta dựng hàm băm lại bậc 2 với hy vọng rải cỏc nỳt đều hơn. Cỏch thực hiện nh− sau: Khi cần thờm nỳt cú khúa k vào bảng, tr−ớc hết ta tớnh i = h(k), là một giỏ trị trong khoảng từ 0 đến m-1

- Nếu vị trí i cũn trống thỡ đặt nỳt cần thờm vào vị trớ

- Nếu bị xung đột thỡ lần lợt tỡm tỡm ở cỏc vị trớ i+12 , i+22 , i+32 ,..., i+j2 với l−u ý là đến cuối bảng ta trở về dũ từ đầu bảng, thực chất là dũ ở vị trớ (i+j2) % m. Khi thấy vị trớ trống đầu tiờn thỡ ta đặt nỳt mới vào vị trớ đú. Khi tỡm một khúa thỡ tr−ớc hết ta tỡm ở vị trớ i = h(k), nếu khụng thấy thỡ tỡm ở cỏc vị trí hj(k) = (h(k)+j) % m , j=1,2,... Khi rơi vào vị trớ trống thỡ cú nghĩa là khụng tỡm thấ

Chú ý: Bảng băm với phơng phỏp dũ bậc 2 nờn chọn m là số nguyờn tố.

2.3. Cài đặt bảng băm

Sau đõy chỳng tụi giới thiệu 3 ch−ơng trỡnh: cài đặt bảng băm dựng danh sỏch liờn kết ngoài, bảng băm dựng danh sỏch kề ngoài và bảng băm dựng liờn kết trong. Phộp băm dựng đến trong cài đặt là phộp chia d. Bạn đọc cú thể tự cài đặt cỏc loại bảng băm khỏc.

2.3.1. Cài đặt bảng băm dựng danh sỏch liờn kết ngoài

Chúng ta sẽ tận dụng cài đặt danh sỏch liờn kết trong phần tr−ớc và dựng lệnh #include để chốn vào đầu tệp chơng trỡnh nguồn cài đặt bảng băm. Bảng băm giờ đõy đơn giản là một mảng mà mỗi phần tử là một cấu trỳc danh sỏch.

Khai báo và định nghĩa cấu trỳc danh sỏch bằng ph−ơng phỏp liờn kết:

Chỳng ta sẽ khai bỏo và định nghĩa một lớp danh sỏch và l−u trong tƯp LIST_H.CPP. Các phần tử của danh sỏch là cỏc nỳt thụng tin cú cấu trỳc đợc định nghĩa trong ch−ơng trỡnh cài đặt bảng băm, tr−ớc khi có lƯnh # include đĨ đ−a tệp LIST_H.CPP vào ch−ơng trỡnh. Cấu trỳc nỳt thụng tin cú dạng:

struct node {int key; node* next; }

trong đó tr−ờng key chứa thụng tin, cũn trờng next là địa chỉ cđa nút tiếp theo trong danh sách. Chỉ cú một số tỏc vụ cơ bản đ−ợc định nghĩa trờn danh sỏch nh− empty, display, dele, append, traverse, search, clear. Bạn đọc cú thể tỡm hiểu trực tiếp từ ch−ơng trỡnh.

http://www.ebook.edụvn

Cấu trúc dữ liƯu 2 – Ch−ơng 2. Bảng băm

33 //LIST_H.CPP

//Danh sach lien ket thuan class List

{public:

node* phead; //A pointer to the first item. node* pcurrent; //A pointer to the current item. int count; //So phan tu

List();//Khoi tao ~List();//Huy khoi tao

int empty();//Kiem tra danh sanh neu rong thi tra ve 1 void display();//Hien khoa hien thoi

void dele();//Xoa ban ghi hien thoi

void append(int);//Them mot phan tu cuoi danh sach co khoa da cho void traverse();//Xem toan bo danh sach

int search(int);//Tim kiem khoa x

void clear();//Xoa danh sach, giai phong bo nho }; //--------------------------- List::List() {phead=NULL;pcurrent=NULL; count=0; }; //--------------------------- List::~List() {node *p1,*p; p=phead; while(p) {p1=p; p=p->next; delete p1; } phead=NULL; } //--------------------------- int List::empty() {return phead==NULL; } //--------------------------- void List::display() {if(pcurrent) cout<<endl<<pcurrent->key<<" "; }; //--------------------------- void List::dele() {if(empty()) return; node *p,*p1; p=phead;

while(p && p!=pcurrent) {p1=p;

Cấu trúc dữ liƯu 2 – Ch−ơng 2. Bảng băm

p=p->next; }

if(p==pcurrent) //p la nut hien thoi, p1 la nut truoc nut ht {p1->next=p->next;

if(p->next) pcurrent=p->next;else pcurrent=p1; delete p; count--; }; return; }; //--------------------------- void List::append(int x) {node *p,*p1,*pp; p=phead; pp = new node; pp->key=x;pp->next=NULL; if(empty()) {phead=pp;pcurrent=pp;count++;return;} p1=NULL; p = phead; while(p!=NULL) {p1=p; p=p->next; }

//Sau vong lap thi p1 la nut cuoi cung p1->next = pp;

pcurrent=pp; count++; };

//---------------------------

//Hien toan bo danh sach len man hinh. void List::traverse() {node* p=phead; while(p) {cout<<p->key<<" "; p=p->next; } } //---------------------------

//Tim con tro co noi dung x trong danh sach int List::search(int x) {node* p=phead; while(p) {if(p->key==x) {pcurrent=p;return(true);} p=p->next; } return(false); }; //---------------------------

http://www.ebook.edụvn

Cấu trúc dữ liƯu 2 – Chơng 2. Bảng băm

35 void List::clear() {node *p1,*p; p=phead; while(p!=NULL) {p1=p; p=p->next; delete p1; } phead=NULL; } Phần cài đặt bảng băm:

ƒ Khai bỏo bảng băm

Chỳng ta dựng mảng động H để cài đặt bảng băm. Mỗi phần tử của mảng bõy giờ khụng phải là số nguyờn, ký tự hay nút thông tin nh− th−ờng lƯ mà là một đối tợng danh sỏch. Biến max là cỡ của bảng băm, cơ sở cđa phép chia d−. Nh− vậy một khóa x bất kỳ sẽ đợc chốn vào phần tử thứ i = x%max của bảng băm, tức là chốn vào danh sỏch liờn kết thứ Con trỏ pcurrent là địa chỉ cđa nút hiƯn thời, là địa chỉ quy −ớc dùng trong các tác vụ tỡm kiếm. Cỏc thao tỏc trờn bảng băm khỏ đơn giản, vỡ chủ yếu cỏc thao tỏc này gọi đến cỏc tỏc vụ trờn danh sách.

struct node {int key; node* next; }; //--------------------------- #include "List_h.cpp" //--------------------------- class HashTab {private: List* H;

int max; //size of Hash table

node* pcurrent; //current node of the hash table int count; //So phan tu trong bang bam

public: HashTab(int); ~HashTab(); void display(); int empty(); int search(int x); void insert(int x); void InsertMany(); void traverse(); };

ƒ Khởi tạo bảng băm

Tỏc vụ khởi tạo bảng băm cú tham số m, đ−ợc đặt ngầm định là 10, là cỡ của bảng băm. Nh vậy khi khai bỏo một biến cú kiểu bảng băm mà ta khụng chỉ rừ cỡ thỡ cỡ của bảng sẽ đợc lấy là

Cấu trúc dữ liƯu 2 – Chơng 2. Bảng băm

10. Trong tỏc vụ này một mảng động gồm max danh sỏch đợc cấp phỏt bộ nhớ. Tuy nhiờn lỳc đầu cỏc danh sỏch cũn rỗng, nờn thực ra max danh sỏch cũng chỉ là max con trỏ.

void HashTab::HashTab(int m = 10) {max=m;

H = new List [max];pcurrent=NULL;; return;

}

ƒ Hủy khởi tạo bảng băm

Khi kết thỳc sử dụng bảng băm ta giải phúng vựng bộ nhớ đà cấp cho bảng băm. Lần lợt đi từ đầu bảng đến cuối bảng, ta gọi tỏc vụ clear của danh sỏch để giải phúng vựng bộ nhớ t−ơng ứng.

void HashTab::~HashTab()

{for(int i=0;i<max;i++) H[i].clear(); delete [] H;

return; }

ƒ Tỡm kiếm trờn bảng băm

Để tỡm kiếm một khúa x trờn bảng băm, đầu tiờn ta xỏc định danh sỏch mà trờn đú cú thể có x. Danh sách cần xỏc định chớnh là danh sỏch thứ i = x%max trờn bảng băm. Ta dựng tỏc vụ search cđa danh sách này để tỡm kiếm khúa x. Nếu tỡm thấy thỡ tỏc vụ trả về giỏ trị true, con trỏ pcurrent chỉ nút tìm thấy trờn danh sỏch; Nếu khụng tỡm thấy thỡ tỏc vụ trả về giỏ trị fals

int HashTab::search(int x) {int i=x%max; if(H[i].search(x)) {pcurrent=H[i].pcurrent; return true; } return false; }

ƒ Chèn phần tư x vào bảng băm

Để chốn một phần tử vào bảng băm, tr−ớc hết ta tớnh i = x%max, sau đú phần tư x đ−ỵc chèn vào danh sỏch thứ i theo giải thuật insert của danh sỏch nà

void HashTab::insert(int x) {int i=x%max; H[i].append(x); pcurrent=H[i].pcurrent; count++; }

ƒ Chốn nhiều phần tử vào bảng băm

void HashTab::InsertMany() {clrscr();int m,i,x;

printf("\Nhap du lieu vao bang bam:"); printf("\n1. Nhap truc tiep");

http://www.ebook.edụvn

Cấu trúc dữ liệu 2 – Ch−ơng 2. Bảng băm

37 printf("\n2. Tao ngau nhien");

printf("\n\n Hay chon 1 hoac 2: "); fflush(stdin);

char ch=getch();

printf("\nCho biet so phan tu can dua vao bang bam: "); fflush(stdin);

scanf("%d",&m); if(ch=='1')

{printf("\nHay nhap %d so: ",m); for(i=0;i<m;i++) {scanf("%d",&x); insert(x); } } else {randomize(); for(i=0;i<m;i++) {x=random(5*m); insert(x); } } }; ƒ Duyệt bảng băm

Để duyệt bảng băm, ta đi từ đầu bảng đến cuối bảng và duyệt từng danh sỏch một. void HashTab::traverse()

{for(int i=0;i<max;i++)

{cout<<endl<<i<<": ";H[i].traverse();} }

2.3.2. Cài đặt bảng băm dựng danh sỏch kề ngoài

Chỳng ta sẽ tận dụng cài đặt danh sỏch kề trong phần trớc và dựng lệnh #include để chốn

Một phần của tài liệu Tài liệu hỗ trợ môn cấu trúc dữ liệu 2 (Trang 30)