Loại bỏ khúa trờn B cõy

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

Ch−ơng 4 B cõy và bộ nhớ ngoà

4.2.5.Loại bỏ khúa trờn B cõy

4.2. B-cây

4.2.5.Loại bỏ khúa trờn B cõy

Về nguyờn tắc, việc loại bỏ nỳt trờn B-cõy hoàn toàn đơn giản nh−ng phức tạp trong chi tiết thực hiƯn. ĐĨ loại bỏ một khóa x, tr−ớc hết ta tìm kiếm trờn cõy để xỏc định vị trớ của khúa x. Việc tiến hành loại bỏ dĩ nhiờn chỉ đợc thực hiện khi thao tỏc tỡm kiếm cú kết quả. Cú 2 tỡnh huống sau:

(1) Phần tử cần loại bỏ ở nỳt lỏ: việc loại bỏ phần tử này đ−ỵc thực hiƯn khỏ dễ dàng nh loại bỏ phần tử trờn danh sỏch.

(2) Phần tử cần loại bỏ nằm ở nỳt trung gian. Lỳc này ta khụng thể loại bỏ trực tiếp phần tử trờn nỳt này, vỡ phần tử này cũn cú cỏc cõy con liờn quan. Ta phải tỡm một phần tử khỏc nằm ở nỳt lỏ làm phần tử thay thế, nghĩa là giỏ trị khúa của phần tử thay thế sẽ đợc gỏn cho giỏ trị khóa

20 7 10 15 18 26 30 35 40 20 30 7 10 15 18 22 26 35 40 A B C A B C D

http://www.ebook.edụvn

Cấu trúc dữ liƯu2 – Chơng 4. B - cõy và bộ nhớ ngồi

77 cđa phần tư cần xóa, sau đú phần tử thay thế đợc loại bỏ khỏi nút lá chứa nó. Cịng nh− trong tr−ờng hợp cõy cõn bằng, ta thấy phần tử thay thế phải là phần tử cực phải của nhỏnh cõy con trỏi hoặc nỳt cực trỏi của nhỏnh cõy con phải của phần tư cần xóạ Chúng ta sẽ quy −ớc chọn phần tử cần thay thế là phần tử cực phải trờn nhỏnh cõy con trỏ

Gọi nút chứa khúa cần xúa là Z. Ta sẽ gặp tỡnh huống ng−ỵc với tr−ờng hợp thờm khúa: khi xóa khóa ta có thĨ làm cho nút lỏ bị cạn kiệt (underflow), nghĩa là cú số khúa ít hơn mức tối thiĨụ Lỳc này ta chỉ cú cỏch giải quyết là m−ỵn một khóa từ nút lân cận. Nếu nút lân cận cũng đang ở trạng thỏi sắp cạn kiệt, nghĩa là cú đỳng m khúa thỡ ta phải đa cỏc khúa và cây con cđa nút Z vào nỳt lõn cận, sau đú xúa nỳt Z. Dĩ nhiờn để xúa nỳt Z, ta phải đ−a cả khóa t−ơng ứng cđa nó trong nỳt cha vào nỳt lõn cận. Trong trờng hợp này nếu nỳt cha sắp cạn kiệt thỡ sẽ trở nờn cạn kiệt và cú thể ta phải cõn bằng lại nỳt cha bằng cỏch vay khóa cđa nút lân cận nỳt cha hoặc lại tiến hành ghộp nố Quỏ trỡnh này có thĨ lan trun tới gốc và có thĨ nỳt gốc cũ bị loại bỏ: cõy đà thấp xuống. Đõy chớnh là cỏch duy nhất để B-cõy giảm chiều ca Ví dơ: Ta xét B cây sau đõy:

Hình 4.4. Xúa khúa 32 trờn B-cõy

Giả sử ta cần xúa khúa 32. Nỳt chứa khúa 32 khụng phải nỳt lỏ, vỡ vậy ta phải tỡm một khúa thay thế. Theo quy −ớc, nút cực phải trờn cõy con trỏi của khúa 32 là khúa 30. Ta đa giỏ trị khóa 30 vỊ vị trí cđa khóa 32 rồi xóa khóa 30 trên nút lỏ chứa nú và nhận đợc cõy mới nh− sau:

Hỡnh 4.5. B - cõy sau khi xúa khóa 32 Ta nhận thấy nút F ở trong tình trạng cạn kiệt. Ta sẽ nối nỳt F vào nỳt E, đồng thời chun khóa 26 xuống nút E rồi xóa nút F. Ta đ−ợc kết quả sau

30 20 26 38 44 B 7 10 15 18 22 24 28 34 36 40 42 46 48 A C F D E I J K 32 20 26 38 44 B 7 10 15 18 22 24 28 30 34 36 40 42 46 48 A C F D E I J K 30 20 38 44 B 7 10 15 18 34 36 40 42 46 48 A C F D E I J K 22 24 26 28

Cấu trúc dữ liƯu2 – Ch−ơng 4. B - cõy và bộ nhớ ngoài

Hình 4.6. Cân bằng lại nút

F

Bây giờ nỳt B lại trở thành cạn kiệt và ta phải cõn bằng lại bằng cỏch ghộp với nỳt C , đồng thời lấy khóa cđa nút gốc xng. Nút gốc vốn chỉ cú một khúa, sau khi lấy khúa này đi thỡ thành rỗng. ta xúa nỳt gốc này và lấy nút B làm gốc mớị

Hình 4.7. Cân bằng lại nút

B

Nh− vậy việc xúa nỳt 32 đà tạo ra một "phản ứng dõy chuyền". Ta phải cõn bằng lại tất cả cỏc nỳt từ nút có khóa thay thế cho đến tận nút gốc và kết quả là cõy đà bị thấp xuống. Tuy nhiên trong thực tế khụng phải lỳc nào ta cũng phải cõn bằng lại tất cả cỏc nỳt từ nỳt lỏ (chứa khúa cần xoỏ hoặc khúa thay thế) cho đến nỳt gốc. Quỏ trỡnh cõn bằng đ−ỵc thực hiƯn từ nút lá trở vỊ gốc và dừng lại khi gặp một nỳt cân bằng. Tr−ờng hợp đơn giản nhất là nỳt lỏ có nhiỊu hơn M khóạ Khi đú sau khi xúa khúa trờn nỳt này thỡ nỳt vẫn cõn bằng và ta khụng phải cõn bằng lạ

4.2.6. Phân tích cỏc thuật toỏn trờn B - cõy

Các thao tác tỡm kiếm hay chốn trờn B-cõy cấp M, tức là số khúa trờn một nỳt trong khoảng từ M đến 2*M cú độ phức tạp tớnh toỏn khoảng O(logM n) trong đú n là số nỳt của câỵ

4.3. Cài đặt B - cõy

Chỳng ta sẽ cài đặt cõy B - cõy theo kiểu liờn kết dựng biến động. Sau đõy chỳng tụi giải thớch một số tác vơ chính cđa chơng trỡnh. Cỏc tỏc vụ này khụng đợc liệt kờ lại trong chơng trỡnh mà chỉ cú dũng chỳ thích. Khi gõ ch−ơng trỡnh để chạy thử bạn đọc cần gừ lại chi tiết.

Khai báo B - cây

Sau cỏc lệnh #include để đa vào cỏc tệp header, các lƯnh #define M 2

#define N 2*M #define N1 2*M+1

sẽ định nghĩa cấp và bậc của cõ M là cấp của cõy và là số khúa tối thiểu mà một nỳt trờn cõy cần cú (trừ nỳt gốc). M phải là hằng số vỡ ta sẽ dựng mảng tĩnh để khai bỏo cỏc khúa và cỏc nỳt trờn cõ Trong ch−ơng trỡnh ta đặt M = 2, tức là cây cấp 2 hay cây bậc 5, số khóa tối đa có thĨ có trờn cõy là 4. Bạn đọc cú thể đặt lại giỏ trị M nếu muốn định nghĩa cõy cấp cao hơn.

Tiếp theo là khai bỏo cấu trỳc của một nỳt thụng tin trờn cõ Mỗi nỳt trờn cõy là một cấu trúc chứa các tr−ờng sau:

- Tr−ờng keynum : là số khóa hiƯn có trên nút

- Tr−ờng key: là mảng chứa cỏc khúa của nỳt. Thực ra số khúa tối đa là N = 2*M, nh− vậy chỉ cần một mảng cú N phần tử nguyờn là cú thể chứa đợc số khúa cần thiết. Tuy nhiên, nh− ta sẽ (adsbygoogle = window.adsbygoogle || []).push({});

B 7 10 15 18 34 36 40 42 46 48 F D E I J K 22 24 26 28 20 30 38 44

http://www.ebook.edụvn

Cấu trúc dữ liƯu2 – Ch−ơng 4. B - cõy và bộ nhớ ngoài

79 thấy trong phần sau cđa chơng trỡnh, việc thờm một vị trớ cho mảng để chứa phần tử thờm vào trong tr−ờng hợp nỳt bị đầy sẽ làm cho việc lập trỡnh đơn giản hơn. Trong thực tế ứng dụng bạn đọc cần cõn nhắc xem việc thờm một vị trớ cho mỗi nỳt cú ảnh h−ởng đến việc khai thỏc tài nguyờn bộ nhớ khụng, và sẽ quyết định chấp nhận thờm một nỳt để việc lập trỡnh đơn giản hơn hay triƯt đĨ tiết kiƯm nguồn tài nguyờn và lập trỡnh cú phức tạp hơn chút ít.

- Tr−ờng son: là mảng cỏc con trỏ trỏ tới địa chỉ cỏc cõy con của nỳt. Mảng này cũng đợc thờm một vị trớ để việc lập trỡnh đơn giản hơn.

Nh− chỳng tụi đà mụ tả trong phần tr−ớc, viƯc cân bằng lại cõy cú thể phải thực hiện từ vị trớ thờm hoặc xóa nút cho đến gốc. Nh− vậy ta cần một Stack đĨ l−u lại địa chỉ cỏc nỳt từ gốc cho đến nỳt cú phần tư đ−ợc thờm hoặc loại bỏ. Cỏc phần tử của Stack sẽ là một cấu trúc Snode gồm 2 thành phần: con trỏ chỉ đến cỏc nỳt trờn cõy và biến nguyờn k chỉ nhỏnh cõy con cần đi tiếp trờn đ−ờng tới nỳt cú khúa cần thờm hoặc loại bỏ.

#define M 2 // cap cua cay, tuc la so khoa it nhat cua mot nut #define N 2*M // So khoa toi da trong mot nut

#define N1 2*M+1 // So con toi da trong mot nut, tuc la bac cua cay // Khai bao cau truc cua mot nut

struct Node

{int keynum; // so khoa cua mot nut

int key[N+1]; //moi nut co nhieu nhat N khoa

Node *son[N1+1]; //cac con tro chi cac nut con cua mot nut };

//Them 1 vi tri vao key[] va son[] de tien thao tac

struct Snode {Node *pnode;int k;};//k la di tiep tren son[k]; #include "stack_h.cpp"

class Btree {public:

Node* proot; //A pointer to the root of the treẹ long count; BTree(); ~BTree(); void Initialize(); int Empty(); void Clear(Node*&); Node* NewNode(); void Traverse(Node*); int NodeSearch(Node*&,int); Node* Search(Node*,int &,int); void Split(Node*&,Node*&,int); void Insert(int);

void Merge(Node*&,Node*&,int); void Remove(int);

Cấu trúc dữ liƯu2 – Ch−ơng 4. B - cõy và bộ nhớ ngồi

Tỏc vụ tỏch nỳt khi nỳt bị tràn

Tỏc vụ này đ−ỵc gọi bởi tác vơ insert. Khi một nút p bị tràn, tức là cú 2*M+1 khúa thỡ nó đ−ợc tỏch làm 2 nỳt. Cỏc khúa và cõy con kốm theo từ vị trớ 0 đến vị trớ M-1 đợc giữ lại trong nút p. Các nỳt và cõy con kốm theo từ vị trớ M+1 đến 2*M đợc chuyển vào nỳt mới p2. Sau đó nút mới p2 cùng với khóa ở vị trí M đ−ợc đa lờn nỳt cha ở vị trớ thớch hợp. Vị trớ này chớnh là vị trớ k nhận đ−ỵc từ Stack.

void BTree::Split(Node* &p,Node* &fp, int k) {if(p->keynum<=N) return;

int i;

Node *p2=NewNode(); /*Tach nut p lam 2 phan:

Tu vi tri 0->M-1 giu lai trong p, tu M+1 -> N chuyen sang p2 khoa p->key[M] chuyen len vi tri fp->key[k]; nut p2 duoc gan vao fp->son[k]

Dich chuyen cac nhanh cay con va cac khoa ben phai vi tri k sang phai mot vi tri trong nut fp de lay vi tri fp->key[k] va fp->son[k] chen khoa va con moi*/

for(i=fp->keynum;i>k;i--) {fp->son[i+1]=fp->son[i]; fp->key[i] = fp->key[i-1]; }

fp->keynum++; //Bat dau tach nut p; int newkey=p->key[M];

//copy tu vi tri khoa M+1 sang p2 for(i=0;i<M;i++)

{p2->key[i]=p->key[M+i+1]; p2->son[i]=p->son[M+i+1]; }

p2->son[M]=p->son[N1];//con cuoi cung p->keynum=p2->keynum=M; (adsbygoogle = window.adsbygoogle || []).push({});

fp->key[k]=newkey; fp->son[k]=p; fp->son[k+1]=p2; }

Tỏc vụ tỡm khúa x trờn nỳt p và trả về vị trớ của khúa x. Nếu khụng tỡm thấy thỡ trả về giỏ trị k, và ta cú thể tỡm khúa x trờn nhỏnh cây p->son[k]

/* Tac vu Nodesearch: tim trong nut vi tri cua khoa bat dau >= x. Truong hop x lon hon tat ca cac khoa trong nut thi tra ve vi tri p->keynum, tuc la vi tri sau khoa cuoi cung

Input: Khoa x va con tro toi nut p

http://www.ebook.edụvn

Cấu trúc dữ liƯu2 – Ch−ơng 4. B - cõy và bộ nhớ ngoài

81 int BTree::NodeSearch(Node* &p, int x)

{int i;

for(i=0;i<p->keynum && p->key[i]<x; i++); return(i);

//Neu co khoa key[i] thi ta co key[i-1] < x <= key[i] }

Tỏc vụ thờm khúa x vào cõy

Tỏc vụ này thờm một nỳt cú khúa x vào B - cõy và thực hiện cỏc thao tỏc cõn bằng lại cõy sao cõy vẫn là B - cõ

Input: B - cõy và khúa cần chốn vào câỵ

Output: B - cõy trong đú đà có nút x.

Quỏ trỡnh thờm nỳt vào cõy đợc tiến hành qua cỏc b−ớc sau: B−ớc 1: Kiểm tra nếu cõy rỗng thỡ tạo nỳt gốc cú khúa x và kết thúc. B−ớc 2: Tạo một Stack để l−u lại cỏc nỳt đi qu

Đặt fp = NULL, p = proot.

B−ớc 3: Dùng tỏc vụ NodeSearch để xỏc định xem khúa x cú trờn nỳt p khụng, nếu cú thỡ thụng bỏo là nỳt đà cú, khụng thờm nữa và kết thỳc. Căn cứ vào giỏ trị k trả về của hàm NodeSearch ta đặt fp = p ,p = p->son[k]. L−u fp và k vào Stack rồi chun sang b−ớc 4.

B−ớc 4: Nếu p khác NULL ta trở lại b−ớc 3.

Nếu nút p = NULL, ta lấy phần tử từ đỉnh Stack r Thành phần con trỏ cđa phần tư này chớnh là nỳt fp cuối cựng và giỏ trị k là vị trớ ta cú thể chốn khúa x vàọ Sau khi chèn khóa x vào nút fp nếu nút fp cha bị tràn (overflow) thỡ kết thúc. Ng−ợc lại ta chun sang bc 5.

B−ớc 5: Nếu fp bị tràn thỡ ta xột 2 tr−ờng hỵp:

(a) Nếu Stack rỗng, tức là nỳt fp chớnh là nỳt gốc thỡ ta tỏch nỳt p thành 2 nỳt, mỗi nỳt cú đỳng M khú Tạo nỳt gốc mới, đ−a khóa fp->key[M] lờn nỳt gốc mới này và kết thúc.

(b) Nếu Stack khụng rỗng, ta gỏn p = fp, rồi thực hiện tỏc vụ Pop của Stack để lấy phần tử từ trờn đỉnh Stack, gỏn phần con trỏ cho fp. Tỏch nỳt p làm 2 nút p và p2, chèn khúa ở giữa p->key[M] vào vị trí khóa thứ k của fp, và chốn cõy p2 vào vị trớ fp- >son[k+1].

Chuyển sang b−ớc 6.

B−ớc 6: Nếu fp khụng bị tràn thi kết thỳc. Ngợc lại chun vỊ b−ớc 5. DƠ thấy rằng tht toán trờn đõy sẽ dừng sau hữu hạn b−ớc

/*Them khoa x vao cay: xuat phat tu goc di theo hanh trinh thich hop den nut la co the them x. Trong qua trinh den nut la dua vao Stack tu tao cac nut di qua de dung cho viec can doi lai sau naỵ

Cấu trúc dữ liƯu2 – Ch−ơng 4. B - cõy và bộ nhớ ngoài */ void BTree::Insert(int x) {Node *p,*p1,*fp; int i,j,k; if(Empty()) {int i; proot = NewNode(); proot->keynum=1;

proot->key[0]=x;//Nut goc co 1 khoa return;

}

//Bat dau tu goc, tim vi tri de chen x, tren duong di dua cac nut vao Stack Snode tmp;

Stack<Snode> st(20); //Bat dau tu nut goc fp = NULL; (adsbygoogle = window.adsbygoogle || []).push({});

p = proot;

while(p != NULL) {k=NodeSearch(p,x);

if(k<p->keynum && x==p->key[k]) // tim thay

{cout<<endl<<"Nut da co khong them duoc";delay(100);return;} tmp.pnode=p;tmp.k=k;

st.push(tmp); fp = p;

p = p->son[k]; }

//fp chinh la nut la co the chen x vaọ tmp=st.pop();

fp=tmp.pnode;k=tmp.k;

/*dich chuyen cac nhanh cay con va cac khoa ben phai vi tri k sang phai mot vi tri */

for(i=fp->keynum;i>k;i--) fp->key[i] = fp->key[i-1]; fp->key[k]=x;

fp->keynum++;

//Neu nut chua tran thi ket thuc if(fp->keynum<=N) return; if(st.empty()) //Nut p la nut goc {proot=NewNode();

Split(fp,proot,0); return;

http://www.ebook.edụvn

Cấu trúc dữ liƯu2 – Ch−ơng 4. B - cõy và bộ nhớ ngoài

83 while(!st.empty())

{p=fp;

tmp=st.pop();

fp=tmp.pnode;k=tmp.k;

Split(p,fp,k);//Tach p1 lam 2 roi chen vao p if(fp->keynum<=N) return;//Nut p chua tran if(st.empty()) //Nut p la nut goc

{proot=NewNode(); Split(fp,proot,0); return; } } }

Tỏc vụ cõn bằng B - cây khi xóa khóạ

Khi ta xóa một khóa trờn nỳt nào đú thỡ cú thể nỳt đú trở thành cạn kiệt, ta phải m−ỵn khóa ở nỳt lõn cận hoặc nỳt cha, hoặc hợp nhất với nỳt khỏc để nỳt trở nờn cõn bằng (khụng cạn kiệt nữa). Tỏc vụ sau đõy thực hiện việc cõn bằng một nút. Nh− vậy khi xóa một khóa có thể ta phải gọi nhiều lần tỏc vụ này để cõn bằng lại cỏc nỳt từ vị trớ khúa bị xúa về đến nỳt gốc.

Input: Nút con p, nút cha fp mà fp->son[k]=p. Nỳt p bị cạn kiệt.

Output: Nỳt p đà trở nờn cõn bằng, nh−ng nỳt fp cú thể bị cạn kiệt.

/*Gia su nut p bi thieu 1 phan tu, tuc la keynum=M-1 tac vu nay duoc goi boi tac vu Removẹ

Neu nut anh em lan can cua p co so nut nhieu hon M ta se muon 1 nut sang p, di nhien la ta phai so sanh voi khoa tren nut cha sao cho hop ly */

void BTree::Merge(Node* &p,Node* &fp, int k) {if(p->keynum>=M) return;

/*De don gian, neu anh em trai pl cua p khac rong thi ta xet pl, fp->key[k-1] va p.

*/ (adsbygoogle = window.adsbygoogle || []).push({});

Node *pl,*pr;int i,j; if(k>0)

{pl= fp->son[k-1];

if(pl->keynum>M) //pl co nhieu hon M khoa {for(i=p->keynum;i>0;i--)

{p->son[i+1]=p->son[i]; p->key[i] = p->key[i-1];

}; //Don cac phan tu key[0],son[1],key[1],son[2],... ra phia sau p->son[1]=p->son[0];//Don son[0] sang son[1]

p->key[0]=fp->key[k-1];

p->son[0]=pl->son[pl->keynum];

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