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

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 37)

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

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

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 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 kỊ:

Chúng ta sẽ khai bỏo và định nghĩa một lớp danh sỏch và l−u trong tƯp LISTKE_H.CPP. Các phần tử của danh sỏch cú kiểu nguyờn, cựng kiểu với khú Trong cỏc bài toỏn thực tế, cỏc phần tử này cú thể cú cấu trỳc phức tạp hơn.

Chỉ cú một số tỏc vụ cơ bản đ−ợc định nghĩa trờn danh sỏch nh− empty, full, display, dele, append, traverse, sort, sorted, bsearch, insert, clear. Bạn đọc cú thể tỡm hiểu trực tiếp từ ch−ơng trình.

//LISTKE_H.CPP

//Danh sach cai dat bang mang dong (contiguous list). class List

{public:

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

int max; //Starting size of storage arraỵ.. int delta; //Increment size of storage arraỵ.. int count; //number of items in list...

int current; //currently indicated item (for search etc...) int grow(); //Function to increase list sizẹ

List(int); //Khoi tao mang chua danh sach co co size ~List(); //Huy khoi tao

int empty();//Kiem tra neu danh sach rong thi tra ve true int full(); //Kiem tra neu danh sach day thi tra ve gia tri true void display();

void dele();

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

void sort(); //Sap xep danh sach

int sorted(); //Kiem tra neu danh sach da sap xep thi tra ve true int search(int);//Tim kiem tuyen tinh tren danh sach

int bsearch(int);//Tim kiem nhi phan tren danh sach void insert(int);//Chen phan tu vao danh sach da sap xep void clear(); //Xoa danh sach, giai phong bo nho }; //--------------------------- List::List(int size=10) {max=size; delta=max/2; count=0; a=new int[max]; current=EOF; }; //--------------------------- List::~List() {delete [] a;} //--------------------------- void List::clear() {delete [] a;} //--------------------------- int List::empty() {return count==0; }; //--------------------------- int List::full() {return count==max; }; //---------------------------

int List::grow() //Increase the size of the list by deltạ {int newmax=max+delta;

int* newa=new int [newmax];

if(!newa) return false; //Allocation failed. //Copy original list contents to new spacẹ..

http://www.ebook.edụvn

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

39 for(int i=0;i<max;i++) newa[i]=a[i];

delete [] a; //Get rid of old storagẹ

a=newa; //Pointer to new list now in datạ max=newmax; return true; } //--------------------------- void List::display() {if(current==EOF) {cout<<"EOF";return;}; cout<<endl<<"a["<<current<<"] = "<<a[current]; }; //--------------------------- void List::dele() {if(empty()) return; if(current==EOF) {cout<<"EOF";return;}

for(int i=current;i<count-2;i++) a[i]=a[i+1];count--; };

//--------------------------- void List::append(int x)

{if(full() && !grow()) {cout<<"Danh sach bi day";return;} a[count++]=x;

};

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

//Duyet danh sach va cho hien tren man hinh. void List::traverse()

{int i;

for(i=0;i<count;i++) cout<<a[i]<<" "; }

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

//Kiem tra xem day da sap xep chua void List::sort() {int i,j,k,t; for(i=0;i<count-1;i++) {k=i; for(j=i+1;j<count;j++) if(a[j]<a[k]) k = j; if(k!=i) {t=a[i];a[i]=a[k];a[k]=t;} } } //---------------------------

//Kiem tra xem day da sap xep chua int List::sorted() {int i,k; for(i=0;i<count-1;i++) if(a[i]>a[i+1]) return(false); return true; } //---------------------------

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

/*Tim thay thi dat current=vi tri tim thay va tra ve true, khong tim thay tra ve false*/ int List::search(int x) {int i; for(i=0;i<count;i++) if(a[i]==x) {current=i;return(true);} return(false); }; //---------------------------

/*Tim kiem tuyen nhi phan khoa x.

/*Tim thay thi dat current=vi tri tim thay va tra ve true, khong tim thay tra ve false*/ int List::bsearch(int x)

{//Kiem tra xem danh sach da sap xep chua

if(!sorted()) {cout<<"Danh sach chua sap xep";return false;} int bottom,mid,top;

bottom=0,top=count-1; while(bottom<=top) {mid=(bottom+top)/2;

if(a[mid]==x) {current=mid;return true;}; if(x<a[mid]) top=mid-1; else bottom=mid+1; }

return false; };

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

//Chen gia tri x vao danh sach da duoc sap xep. void List::insert(int x)

{int i,j;

if(full() && !grow()) {cout<<"Danh sach bi day";return;} //Kiem tra xem danh sach da sap xep chua

if(!sorted()) {cout<<"Danh sach chua sap xep";return;} for(i=0;x>a[i] && i<count;i++);

for(j=count;j>i;j--) {a[j]=a[j-1];} //Dich cac thanh phan tu i len mot vi tri a[i]=x;count++;

}

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 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 kỊ 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.

#include "listke_h.cpp" class HashTab

{private:

http://www.ebook.edụvn

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

41 int max; //size of Hash table

int* pcurrent; //current a of the hash table int count; 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à 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 nhị phõn 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ụ tỡm kiếm nhị phõn bsearch 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ỉ địa chỉ của phầ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].bsearch(x)) {pcurrent=&H[i].a[H[i].current]; return true; } return false;

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

}

ƒ 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].insert(x); pcurrent=&H[i].current; } ƒ Duyệt bảng băm

Theo cỏch chốn cỏc phần tử mới vào bảng băm trờn đõy ta cú đợc cỏc danh sỏch đã đ−ợc sắp thứ tự. Để cú thể duyệt cỏc phần tử bảng băm theo thứ tự tăng dần ta ỏp dụng phơng phỏp sắp xếp trộn (mergesort) nh− sau:

Tr−ớc hết ta tạo ra một mảng cỏc số nguyờn p[i], khởi đầu ta đặt tất cả cỏc giỏ trị của p[i] bằng 0. B−ớc tiếp theo ta bắt đầu so sỏnh cỏc giỏ trị H[i].a[p[i]], i = 0,1,2,..., max-1, tức là những phần tử đứng đầu cỏc phần tử ch−a đ−ợc xột trong từng danh sỏch (là những phần tư nhỏ nhất trong các phần tư ch−a đ−ợc xột trong từng danh sỏch), chọn phần tử nhỏ nhất trong cỏc phần tử đú và cho hiện trờn màn hỡnh. Nếu trong bớc vừa rồi ta đã chọn p[i] thỡ ta tăng p[i] lờn 1. Chỉ cú những danh sỏch cú số phần tử đà xột nhỏ hơn tổng số cỏc phần tử của nú mới đợc xột đến, thớ dụ nếu đối với danh sỏch i mà p[i]>h[i].count-1 thỡ khụng xột dÃy i nữ Việc chọn phần tử nhỏ nhất bắt đầu bằng việc chọn một danh sỏch đầu tiờn đang cũn phần tử, và phần tử đầu tiờn của danh sỏch đú đ−ợc chọn là giỏ trị nhỏ nhất tạm thờ Sau đú ta duyệt tất cả danh sỏch cũn phần tử sau đú và thay thế bằng phần tử nhỏ hơn nếu cú.

//Duyet bang bam theo thu tu tang dan void HashTab::traverse()

{int i,j,k,r,*p,*ConPhanTu;int t; p=new int[max];

ConPhanTu = new int[max]; k=0;

for(i=0;i<max;i++) p[i]=0;//p[i] la so phan tu da doc o ds i for(i=0;i<max;i++)

if(H[i].count==0) ConPhanTu[i]=0;else ConPhanTu[i]=1; cout<<endl<<endl;

while(k<count) {r=0;

while(!ConPhanTu[r]) r++;

if(r==max) break;//Da duyet het bang bam t=H[r].a[p[r]];j=r;

for(i=r+1;i<max;i++)

if(p[i]<H[i].count && H[i].a[p[i]]<t) {t=H[i].a[p[i]];j=i;}

cout<<H[j].a[p[j]]<<" ";p[j]++;k++; if(p[j]>=H[j].count) ConPhanTu[j]=0; }

http://www.ebook.edụvn

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

43

2.3.3. Cài đặt bảng băm dùng liên kết trong

ƒ Khai bỏo bảng băm liờn kết trong

Chỳng ta sẽ dựng một mảng động để cài đặt bảng băm. Cỡ của mảng đợc đặt ngầm định là 10, và cú thể thay đổi khi khai bỏo một biến cú kiểu bảng băm. Mỗi phần tử của mảng là một nút thơng tin có cấu trúc:

struct node {int* pkey; int next; };

trong đú pkey là con trỏ chỉ đến biến nguyờn mang thụng tin trong bộ nhớ; next là tr−ờng có kiểu nguyờn chỉ đến ụ tiếp theo trong tr−ờng hỵp có đơng độ.

Biến max là cỡ của bảng băm, là cơ sở cho phộp chia d− khi cần thờm khúa vào bảng. Biến count chỉ số phần tử chứa thụng tin trong bảng băm, biến current chỉ vị trớ nỳt hiện thờ Bảng băm cú một số tỏc vụ mà chỳng tụi sẽ giải thớch rõ hơn từng tr−ờng hỵp.

struct node {int* pkey; int next; }; class Hash {public: node* H; int max; int count; int current; Hash(int); ~Hash(); int empty(); int full(); void display(); void traverse(); int search(int); void insert(int); };

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

Ban đầu tất cả cỏc ụ cú trờng pkey chỉ đến NULL để chứng tỏ rằng bảng cũn rỗng. Tr−ờng next = -1 có nghĩa là ụ khụng cú phần tử kế tiếp.

Hash::Hash(int size=10) {max=size; H = new node[max]; for(int i=0;i<max;i++) {H[i].pkey=NULL;H[i].next=-1; } count=0; };

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

ƒ Hđy khởi tạo

Khi kết thúc viƯc sư dơng một biến có kiĨu bảng băm ta lần lợt giải phúng phần bộ nhớ đà cấp cho cỏc ụ của bảng.

Hash::~Hash()

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

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

Khi cần thờm một phần tử x vào bảng băm, tr−ớc hết ta kiểm tra xem bảng băm cú đầy khụng, nếu đầy thỡ khụng thờm nỳt đợc và kết thỳc. Nếu bảng khụng đầy thỡ ta lấy k = x%max. Nếu ụ thứ k cũn trống, tức là tr−ờng pkey chỉ đến NULL, thỡ ta cấp phỏt bộ nhớ cho biến này, gỏn *H[k].pkey = x và kết thỳc; Cũn nếu ụ này đà bị chiếm chỗ thỡ ta xem nội dung trong ụ này cú cựng họ với x khụng, nghĩa là *H[k].pkey%max cú bằng x%max khụng. Ta phõn biệt 2 tr−ờng hỵp nh− sau:

ạ Nếu ô k cùng họ với x có thì ta đặt i = k lần theo đ−ờng đi i = H[i]. next , cho đến khi gỈp H[i].next = -1. Tiếp đến 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 một ụ j đầu tiờn cũn trống. Ta cấp phát bộ nhớ cho tr−ờng pkey của ụ này, gỏn *H[j].pkey = x, H[j].next = -1. Trở lại ụ thứ i núi trờn, ta gỏn H[i].next = j và kết thỳc.

b. Nếu ô k không cùng họ với x thỡ ta xuất phỏt từ vị trí max-1 lần ng−ợc về phớa đầu bảng tỡm vị trớ đầu tiờn cũn trống. Nếu vị trớ này là j thỡ ta cấp phát bộ nhớ cho tr−ờng pkey của ụ này, gỏn *H[j].pkey = x, H[j].next = -1 và kết thúc.

void Hash::insert(int x)

{if(full()) {cout<<endl<<"Bang bam day, khong chen duoc";return;} if(search(x)) {cout<<endl<<"Nut da co, khong chen duoc";return;} int k,i,j;

k=x%max;

if( H[k].pkey==NULL)

{ H[k].pkey=new int;* H[k].pkey=x;count++; return;} if(* H[k].pkey%max==k) //Cung lop co phan du la d {i=k;

while( H[i].next!=-1) i= H[i].next; for(j=max-1; H[j].pkey!=NULL;j--); H[i].next=j; H[j].pkey=new int; * H[j].pkey=x; H[j].next=-1; count++; return; }

//Truong hop x khong cung so du voi i, ta tim mot vi tri trong de chen for(j=max-1;j>0 && H[j].pkey!=NULL;j--);

H[j].pkey=new int; * H[j].pkey=x; H[j].next=-1;

http://www.ebook.edụvn

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

45 count++;

return; }

ƒ Tỡm thụng tin x trờn bảng băm

Khi cần tỡm kiếm ụ cú nội dung x trờn bảng băm, tr−ớc hết ta đặt k = x%max. Nếu *H[k].pkey = x thỡ tỏc vụ trả về giỏ trị true, đặt current = k và kết thỳc; Cũn nếu khụng nh− vậy thì ta xem nội dung trong ơ này có cùng họ với x khơng, nghĩa là *H[k].pkey%max có bằng x%max khụng. Nếu cú thỡ ta đặt k = i và lần theo đ−ờng đi i = H[i]. next , cho đến khi gặp *H[i].next = x thỡ tỏc vụ trả về giỏ trị true, đặt current = i và kết thỳc ; hoặc khụng tỡm thấy thỡ ta dừng lại khi H[i].next = -1, tỏc vụ trả về giỏ trị fals Nếu ô thứ k 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 cho đến ụ đầu tiờn cũn trống. Nếu gặp ụ j mà *H[j].pkey = x thỡ ta đặt current = j và tỏc vụ trả về giỏ trị true, nếu khụng thỡ trả về giỏ trị fals

int Hash::search(int x) {int k,i,j;

k=x%max;

if(* H[k].pkey%max==k) //Cung lop co phan du la d {i=k;

while( H[i].pkey!=NULL)

{if(* H[i].pkey==x) {current=i;return true;} if( H[i].next==-1) break; else i= H[i].next; }

return false; }

for(j=max-1; H[j].pkey!=NULL;j--)

if(* H[j].pkey==x) {current=j;return true;} return false;

};

Thao tác xóa nút trong trờng hợp này khỏ phức tạp. Chỳng tụi xin dành lại vấn đề này nh− một bài tập cho cỏc bạn sinh viờn học lực khỏ trở lờn.

2.3.4. Vài nhận xột về bảng băm

Mục đớch của cấu trỳc dữ liệu bảng băm là muốn cú thời gian truy xuất tức thờ Điều này có thĨ thực hiƯn đ−ợc nếu cơ sở dữ liệu của ta cú số phần tử khụng lớn hơn kớch th−ớc bảng nhiỊu quỏ, và cỏc khúa đợc phõn bố khỏ đều trờn bảng. Rất khú đ−a ra một đánh giá chung vỊ độ phức tạp tớnh toỏn của cỏc phộp toỏn trờn bảng băm, mà ta chỉ cú thể tớnh toỏn đ−ỵc trong những

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 37)