[Cây] Một số phép toán trên cây nhị phân tìm kiếm
[qads]
Cây nhị phân tìm kiếm (CNPTK) là cây nhị phân trong đó tại mỗi nút, khóa của nút đang xét lớn hơn khóa của tất cả các nút thuộc cây con trái và nhỏ hơn khóa của tất cả các nút thuộc cây con phải.
Dưới đây là một ví dụ về cây nhị phân tìm kiếm:
Nội dung
Cấu trúc cây
Thêm phần tử vào cây
Nhập cây
Duyệt cây
Tìm một node trong cây
Xóa một node trong cây
Code hoàn chỉnh tham khảo
Nhờ ràng buộc về khóa trên CNPTK, việc tìm kiếm trở nên có định hướng. Hơn nữa, do cấu trúc cây việc tìm kiếm trở nên nhanh đáng kể. Nếu số nút trên cây là N thì chi phí tìm kiếm trung bình chỉ khoảng log2N.
Cấu trúc cây:
typedef int item; //kieu item la kieu nguyen struct Node { item key; //truong key cua du lieu Node *Left, *Right; //con trai va con phai }; typedef Node *Tree; //cay
Vậy là ta đã có cấu trúc cây Tree. Giờ chúng ta sẽ thực hiện một số thao tác.
Thêm 1 phần tử vào cây
Việc thêm một phần tử X vào cây phải bảo đảm điều kiện ràng buộc của CNPTK. Ta có thể thêm vào nhiều chỗ khác nhau trên cây, nhưng nếu thêm vào một nút lá sẽ là tiện lợi nhất do ta có thể thực hiên quá trình tương tự thao tác tìm kiếm. Khi chấm dứt quá trình tìm kiếm cũng chính là lúc tìm được chỗ cần thêm.
int insertNode(Tree &T, item x) // chen 1 Node vao cay { if (T != NULL) { if (T->key == x) return -1; // Node nay da co if (T->key > x) return insertNode(T->Left, x); // chen vao Node trai else if (T->key < x) return insertNode(T->Right, x); // chen vao Node phai } T = (Node *) malloc(sizeof(Node)); if (T == NULL) return 0; // khong du bo nho T->key = x; T->Left = T->Right = NULL; return 1; // ok }
Nhập cây
Việc nhập cây là việc lặp đi lặp lại chèn 1 phần tử vào cây đến một điều kiện nào đó thì dừng. Ở code dưới đây điều kiện dừng là nhập vào phần tử = 0.
void CreateTree(Tree &T) // nhap cay { int x; while (1) { printf("Nhap vao Node: "); scanf("%d", &x); if (x == 0) break; // x = 0 thi thoat int check = insertNode(T, x); if (check == -1) printf("Node da ton tai!"); else if (check == 0) printf("Khong du bo nho"); } }
Việc nhập đã xong, tuy thế thôi nhưng nhiều người khi code không biết nhập cây kiểu gì :3 (mình đã từng bị như vậy)
Duyệt cây
Thao tác duyệt cây trên cây nhị phân tìm kiếm hoàn toàn giống như trên cây nhị phân. Đặc biệt khi duyệt theo thứ tự giữa, trình tự các nút duyệt qua sẽ cho ta một dãy các nút theo thứ tự tăng dần của khóa. (1, 2, 3, 4, 5, 6, 7, 8, 9, 12, 20, 23)
void LNR(Tree T) { if(T!=NULL) { LNR(T->Left); printf("%d ",T->key); LNR(T->Right); } }
Việc duyệt theo thứ tự trước hoặc sau các bạn làm tương tự.
Tìm một Node có key = x trong cây
Node* searchKey(Tree T, item x) // tim nut co key x { if (T!=NULL) { if (T->key == x) { Node *P = T; return P;} if (T->key > x) return searchKey(T->Left, x); if (T->key < x) return searchKey(T->Right, x); } return NULL; }
Việc tìm kiếm như trên mình đã nêu là rất đơn giản và nhanh đáng kể.
Xóa Node có key = x trong cây
Việc hủy một phần tử X ra khỏi cây phải bảo đảm điều kiện ràng buộc của CNPTK. Có 3 trường hợp khi hủy nút X có thể xảy ra:
– X là nút lá: Đơn giản chỉ cần hủy X vì nó không móc nối đến phần tử nào khác.
– X chỉ có 1 con (trái hoặc phải): Trước khi hủy X ta móc nối cha của X với con duy nhất của nó.
– X có đủ cả 2 con: ta không thể hủy trực tiếp do X có đủ 2 con. Ta sẽ hủy gián tiếp. Thay vì hủy X, ta sẽ tìm một phần tử thế mạng Q. Phần tử này có tối đa một con. Thông tin lưu tại Q sẽ được chuyển lên lưu tại X. Sau đó, nút bị hủy thật sự sẽ là Q giống như 2 trường hợp đầu. Vấn đề là phải chọn Q sao cho khi lưu Q vào vị trí của X, cây vẫn là CNPTK.
Có 2 phần tử thỏa mãn yêu cầu:
+ Phần tử nhỏ nhất (trái nhất) trên cây con phải.
+ Phần tử lớn nhất (phải nhất) trên cây con trái.
Việc chọn lựa phần tử nào là phần tử thế mạng hoàn toàn phụ thuộc vào ý thích của người lập trình. Trong code này tôi chọn phần tử phải nhất.
int delKey(Tree &T, item x) // xoa nut co key x { if (T==NULL) return 0; else if (T->key > x) return delKey(T->Left, x); else if (T->key < x) return delKey(T->Right, x); else // T->key == x { if (T->Left == NULL) T = T->Right; // Node chi co cay con phai else if (T->Right == NULL) T = T->Left; // Node chi co cay con trai else // Node co ca 2 con { Node *Q = T->Left; while (Q->Right != NULL) { Q = Q->Right; } T->key = Q->key; delKey(T->Left, Q->key); } } return 1; }
Code tham khảo:
#include<stdlib.h> #include<stdio.h> typedef int item; //kieu item la kieu nguyen struct Node { item key; //truong key cua du lieu Node *Left, *Right; //con trai va con phai }; typedef Node *Tree; //cay int insertNode(Tree &T, item x) // chen 1 Node vao cay { if (T != NULL) { if (T->key == x) return -1; // Node nay da co if (T->key > x) return insertNode(T->Left, x); // chen vao Node trai else if (T->key < x) return insertNode(T->Right, x); // chen vao Node phai } T = (Node *) malloc(sizeof(Node)); if (T == NULL) return 0; // khong du bo nho T->key = x; T->Left = T->Right = NULL; return 1; // ok } void CreateTree(Tree &T) // nhap cay { int x; while (1) { printf("Nhap vao Node: "); scanf("%d", &x); if (x == 0) break; // x = 0 thi thoat int check = insertNode(T, x); if (check == -1) printf("Node da ton tai!"); else if (check == 0) printf("Khong du bo nho"); } } // Duyet theo LNR void LNR(Tree T) { if(T!=NULL) { LNR(T->Left); printf("%d ",T->key); LNR(T->Right); } } Node* searchKey(Tree T, item x) // tim nut co key x { if (T!=NULL) { if (T->key == x) { Node *P = T; return P;} if (T->key > x) return searchKey(T->Left, x); if (T->key < x) return searchKey(T->Right, x); } return NULL; } int delKey(Tree &T, item x) // xoa nut co key x { if (T==NULL) return 0; else if (T->key > x) return delKey(T->Left, x); else if (T->key < x) return delKey(T->Right, x); else // T->key == x { if (T->Left == NULL) T = T->Right; // Node chi co cay con phai else if (T->Right == NULL) T = T->Left; // Node chi co cay con trai else // Node co ca 2 con { Node *Q = T->Left; while (Q->Right != NULL) { Q = Q->Right; } T->key = Q->key; delKey(T->Left, Q->key); } } return 1; } int main() { Tree T; T=NULL; //Tao cay rong CreateTree(T); //Nhap cay //duyet cay printf("Duyet cay theo LNR: \n"); LNR(T); printf("\n"); Node *P; item x; printf("Nhap vao key can tim: "); scanf("%d", &x); P = searchKey(T, x); if (P != NULL) printf("Tim thay key %d\n", P->key); else printf("Key %d khong co trong cay\n", x); if (delKey(T, x)) printf("Xoa thanh cong\n"); else printf("Khong tim thay key %d can xoan", x); printf("Duyet cay theo LNR: \n"); LNR(T); printf("\n"); return 0; }
Đọc thêm: Chuyển cây nhị phân sang cây nhị phân tìm kiếm
Good blog! I really love how it is easy on my eyes and the data are well written. I am wondering how I could be notified whenever a new post has been made. I have subscribed to your feed which must do the trick! Have a nice day! efdecfbbekke
Thank you very much. You take care my blog. If you have a wordpress account and want follow me, you can click button “Follow” at top:
or you can back Home page and see at bottom. Enter your email and click button “Theo dõi”. I will send to your email whenever a new post has been made:
anh cho em hỏi là em code bằng java.nhưng em không hiểu 1 chỗ là T = (Node *) malloc(sizeof(Node)); có nghĩ là gì a?
em rùng TNode root và ;
class TNode{
int data;
TNode left,right;
TNode(int x){
data=x;
left=right=null;
}
TNode(int x,TNode ll,TNode rr){
data=x;
left=ll;
right=rr;
}
}
public class Demo {
TNode root;
int insertNode(TNode T,int x){
if(T!=null){
if(T.data==x)
return -1;
else{
if(T.data<x)
return insertNode(T.right,x);
else
return insertNode(T.left,x);
}
}
if (T == null) return 0; // khong du bo nho
T.data = x;
T.left = T.right = null;
return 1; // ok
}
/*int insert(int x){
return insertNode(root,x);
}*/
void nhapcay(TNode T){
int x;
Scanner kb = new Scanner(System.in);
while(true){
System.out.print("x= ");
x=kb.nextInt();
if(x==0)
break;
int check=insertNode(T,x);
if (check == -1) System.out.println("Node da ton tai!");
else if (check == 0) System.out.println("Khong du bo nho");
}
}
void nhap(){
nhapcay(root);
}
void xuat(TNode T){
xuat(T.left);
System.out.print(T.data);
xuat(T.right);
}
void duyet(){
xuat(root);
}
public static void main(String[] agrs){
Demo a = new Demo();
a.nhap();
a.duyet();
}
}
Có nghĩa là cấp phát bộ nhớ cho Con trỏ T nhé.
đấy là cấp phát bộ nhớ cho node T có kích thước bằng kích thước bằng kích thước của struct node nhé bạn !!
sao anh không làm 1 bài sinh số nhị phân bằng C nhỉ anh..!!
hay một bài chỉnh hợp tổ hợp chẳng hạn:
em đang có 1 bài định tối ưu nhưng không biết tối ưu như nào.?
=)) Làm đi. thời gian có hạn, tớ ko viết được mọi thứ.
Dạ..!!
tình hình là thế này:
đây là code in ra chỉnh hợp chập k của n, nhìn hơi cùi bắp..^^
và vấn đề là hai vòng FOR () em đã đánh dấu k biết làm cách nào để tối ưu hóa..!
Đã ngưỡng mộ võ công tiền bối lâu nay, mong cao nhân chỉ giáo phương cách, nên dùng cái gì để cải tiến….!!
int k,n;
int a[200];
void ChinhHop ( int j){
int dem =0;
if (j== k){
// for (int i = 0 ; i < k – 1; i++){
// for(int j = i + 1; j < k; j++)
if ( a[i] == a[j] )
dem++;
}
if (dem == 0 ){
for (int i=0 ; i<k ; i++){
printf ("%d ", a[i]) ;
}
printf("\n");
}
dem=0;
}else{
for (int i=1 ; i<=n ; i++ ){
a[j]=i;
ChinhHop (j+1);
}
}
}
int main (){
scanf ("%d%d",&k,&n);
ChinhHop ( 0 );
return 0;
}
Chào a!
Thứ nhất e cảm ơn a về những chia sẻ của a trong blog của mình.
Thứ hai, a có thể chia sẻ thêm cho về phương pháp học tập lập trình, cách thức học như thế nào để tiến bộ được không ạ? Cho e hỏi, trong ngành lập trình, cái gì là quan trọng nhất ạ? E không thông minh cho lắm, liệu có học tốt để trở thành lập trình viên được không ạ? 🙁
Thứ 3, e mong a chia sẻ một số tài liệu học mà a cho là hay và bổ ích.
E cảm ơn a ạ! 🙂
Cách học lập trình thì mỗi người có một cách, tùy vào khả năng, sở thích… thường thì xem người khác code (hoặc xem code người khác) rồi code lại, code lại xong chạy ok thì sửa thành code của mình bằng cách tự đặt bài tập tương tự…
Trong lập trình thì cũng tùy. Quan trọng nhất là luôn học & làm cái mới. Một số lĩnh vực như lập trình ứng dụng thì cần thêm thuật toán.
Không thông minh như thế nào thì khi học mới biết được…
Tài liệu của mình toàn google… chả bao giờ đọc sách luôn…
ok
cho em hỏi sao cứ nhập X mãi thế anh… :3
Cái này là nhập đến khi nào nhập số 0 thì dừng lại mà.
🙂 @@@… em mới vào nghề nên không hiểu lắm.. hi… thank anh 😛
e cảm ơn anh vì bài viết rất bổ ích với e. nhưng khi e chạy chương trình của anh, trong trường hợp Node có cả con trái và phải thì khi duyệt cây để xem thì lại lỗi. anh giải đáp giúp e được ko ạ.
Cái đó bạn thử xem lại xem. Mình test chạy ok rồi mới cho lên mà.
anh cho em hỏi , trong trường hợp nhập số 0 là dừng , nhưng em muốn trong cây phải có phần tử bằng 0 thì làm thế nào ạ ? thanks anh
Thì bạn cho nhập một số bất kỳ nào đó. Ví dụ noa rất to hoặc rất nhỏ. 999999999 chẳng hạn.
thưa , anh ý em là vd : em muốn cái cây nhập là 2 3 4 0 5 6 7;
thì nếu theo code của anh thì nó chỉ duyệt 2 3 4 thôi ạ , mong anh giúp đỡ !
Đúng rồi thì vì khi nhập số 0 là dừng lại mà. Bạn đổi điều kiện dừng lại là ok
ok , cảm ơn anh nhé !!!
cho e hỏi a ơi,,ở chỗ InsertNode á a,,giờ mình không dùng đệ quy nữa mà dùng dạng lặp,,a thấy sao? và code dạng lặp là như thế nào?
Cái này chắc là được nhưng mình chưa làm bao giờ nên cũng không rõ nữa bạn.
Anh xem lại phần xóa node đi anh. Giả sử cây như hình vẽ. Nếu em xóa node(12) thì
P -> node(12);
S-> node(12);
Q-> node(9);
Sau đó P->key = 9;
S->right (node(23)) = Q->left (NULL)
Vậy là mất node(23) và node(20) r a
Cảm ơn bạn. Mình đã sửa lỗi nhé.
Tại hàm xóa Node trên cây nếu chương trình chỉ nhập bộ 3 số bất kì (ví dụ như: 3, 4, 5) thì chương trình sẽ bị lỗi.
Cảm ơn bạn. Mình đã sửa lỗi nhé.
Hàm xóa Node *S = T, *Q = S->Left;
// S la cha cua Q, Q la Node phai nhat cua cay con trai cua P
while (Q->Right != NULL)
{
S = Q;
Q = Q->Right;
}
P->key = Q->key;
S->Right = Q->Left;
delete Q;
có vẫn đề. lỗi chạy
Cảm ơn bạn. Mình đã sửa lỗi nhé.
Nhớ em hướng dẫn giúp chị bt này
Cho cây nhị phân tìm kiếm Tiền mỗi nút là một số nguyên
a. Đêm xem trong cây có bao nhiêu nút ma giá trị của nó cùng với giá trị của hai cây con trực tiếp của nó cũng là sô nguyên tố
b.tim cây con T có tổng lớn nhất ( chị tìm một cây thỏa ) hẩy chị biết giá trị tổng tìm đuợc
code phần delete key cua anh nếu xét trường hợp cây con bên trái trỏ đến phải là NULL thì bị sai, anh xem có đúng không. Thank anh!
Cảm ơn bạn nhiều nhé. Mình sẽ kiểm tra lại 😉
Xin chào. Mình muốn hỏi ở hàm insertNode.
Khi T==NULL thì tạo node T rồi gán các giá trị T->key, T->left và T->right, chứ mình không thấy có lệnh móc nối node này vào node khác cả. Bạn có thể giải thích được không?
Khi T == NULL tức là cây rỗng, mà cây đang rỗng thì chỉ cần chèn giá trị vào chính T thôi vì nó là gốc. Khi đó con trái và con phải sẽ chưa có gì nhận giá trị NULL.
Cảm ơn bạn. Bạn có thể giải thích rõ hơn được không? Dùng thử một ví dụ cụ thể chẳng hạn.
Như vậy là rõ nhất rồi bạn. Cây ban đầu rỗng thì khi chèn sẽ chèn vào gốc ấy. Con trái, con phải sẽ nhận NUll.
Nhưng trong code mình không thấy sự móc nối giữa node này với node khác (ý là hai node khác NULL) nhưng vẫn chạy được. Bạn có thể giải thích thêm được không?
Ban đầu cây luôn rỗng nhé, tức là T = NULL. Sau đó chèn phần tử đầu tiên x1 vào thì vì T = NULL nên T nhận giá trị là x1, T->left = T-right = NULL. Lần sau chèn vào thì T != NULL rồi, do vậy tùy vào giá trị của x2 tiếp theo mà chèn vào cây L->left (nếu x2 < x1), hay T->right (x2 > x1). Khi đó gọi lại đệ quy insertNote(T->left) (giả sử chèn vào left), khi đó nó lại coi T->left là 1 cái T mới và làm như vậy.
Anh ơi, code tính số nút, chiều cao của cây NPTK thì ntn ạ? Có giống của cây nhị phân k a?
Giống em nhé. 🙂
Cảm ơn tác giả,bài viết rất bổ ích đối với tôi!
Cho mình hỏi tại sao phần InsertNode khai báo là:
int insertNode(Tree &T, item x)
mà không phải là:
int insertNode(Tree T, item x)
cách 2 có dùng được không,2 cách khác nhau như thế nào?
Xin cảm ơn!
Có &T là để lưu lại giá trị của cây T sau khi ra khỏi hàm. nếu như cách 2 thì sau khi ra khỏi hàm cây T sẽ vẫn như lúc chưa chèn item vào.
Hi anh,
Trong hàm delKey(Tree &T, item x) {} tại dòng `S->Right = Q->Left;` có ý nghĩa gì ạ. Lúc này
Q là node phải nhất của cây con trái, và S đang là node cha của Q.
Cảm ơn bạn. Mình đã sửa lỗi nhé.
trong đoạn code xóa một node, nếu x==(node cuối cùng của cây) thì node cuối sẽ trỏ đến NULL, mà a quên giải phóng vùng nhớ chỗ đó mất rồi.
có cách nào xuất các nút trên cùng 1 hàng ko anh.
có rất nhiều blog nhưng mình toàn tự học trên blog của bạn vì viết rất dễ hiểu, dễ tiếp thu
Cảm ơn bạn nhé.