Cu
trúc
d
liu
gii
thut
Chương 1: Danh sách ln kết đơn đôi
I. Danh ch liên kết đơn
// Khai báo cu trúc Node List
typedef struct Node {
int data;
struct Node *pNext;
} Node;
typedef struct {
Node *pHead;
Node *pTail;
} List;
// Khi to danh sách rng
void init(List *l) {
l->pHead = l->pTail = NULL;
}
// Kim tra danh ch rng
int Empty(List l) {
return ((l.pHead == NULL) && (l.pTail == NULL)) ? 1 : 0;
}
// Thêm mt phn t o đầu danh ch
void addHead(List *l, Node *pNew) {
if (Empty(*l)) {
l->pHead = l->pTail = pNew;
} else {
}
}
pNew->pNext = l->pHead;
l->pHead = pNew;
// Thêm mt phn t o cui danh sách
void addTail(List *l, Node *pNew) {
if (Empty(*l)) {
l->pHead = l->pTail = pNew;
} else {
}
}
l->pTail->pNext = pNew; l->pTail = pNew;
// To mt nút mi
Node* createNode(int x) {
Node *p;
p = (Node *)malloc(sizeof(Node));
if (p == NULL) {
printf("Không đủ b nh để cp phát.\n");
} else {
}
p->data = x;
p->pNext = NULL;
return p;
}
// Nhp danh sách t người dùng
void input(List *l) {
init(l);
int x;
Node *pNew;
do {
printf("Nhp phn t (0 để dng): ");
scanf("%d", &x);
if (x == 0)
break;
pNew = createNode(x);
if (pNew != NULL)
addHead(l, pNew);
} while (1);
}
// Xut danh ch
void output(List l) {
if (Empty(l)) {
printf("Danh ch rng.\n");
} else {
Node *p = l.pHead;
while (p != NULL) {
printf("%d\t", p->data);
p = p->pNext;
}
printf("\n");
}
}
// Đếm s ng phn t trong danh ch
int DemSL(List l) {
if (Empty(l)) {
return 0;
} else {
int dem = 0;
Node *p = l.pHead;
while (p) {
dem++;
p = p->pNext;
}
return dem;
}
}
// Xóa mt nút trong danh ch
void deleteNode(List *l, int x) {
if (Empty(*l)) {
printf("Danh ch rng, không th xóa.\n");
return;
}
Node *pDel = findNode(*l, x);
if (pDel == NULL) {
printf("Không tim thy phn t %d trong danh ch.\n", x);
return;
}
if (pDel == l->pHead) {
// Xóa phn t đầu tiên
l->pHead = l->pHead->pNext;
if (l->pHead == NULL) {
l->pTail = NULL; // Nếu danh ch ch 1 phn t
} else {
}
// Xóa phn t gia hoc cui
Node *pPre = findNodePre(*l, pDel);
pPre->pNext = pDel->pNext;
if (pDel == l->pTail) {
l->pTail = pPre; // Nếu xóa phn t cui
}
}
free(pDel); // Gii phóng b nh
}
// m mt phn t trong danh ch
Node* findNode(List l, int x) {
Node *p = l.pHead;
while (p) {
if (p->data == x)
break;
p = p->pNext;
}
return p;
}
// m phn t đứng trước mt nút
Node* findNodePre(List l, Node *p) {
if (p == l.pHead)
return NULL;
else {
Node *pPre = l.pHead;
while (pPre->pNext != p) {
pPre = pPre->pNext;
}
return pPre;
}
}
// Sp xếp danh ch theo th t ng dn
void sortList(List *l) {
if (Empty(*l)) {
printf("Danh ch rng, không th sp xếp.\n");
return;
}
Node *p, *q;
int temp;
for (p = l->pHead; p != NULL; p = p->pNext) {
for (q = p->pNext; q != NULL; q = q->pNext) {
if (p->data > q->data) {
// Hoán đổi giá tr ca 2 nút
temp = p->data;
p->data = q->data;
q->data = temp;
}
}
}
}
II. Danh ch liên kết đôi
#include <stdio.h>
#include <stdlib.h>
// Cu tc Node cho DSLK đôi
typedef struct Node {
int data;
struct Node *prev; // Con tr đến nút trước
struct Node *next; // Con tr đến nút sau
} Node;
// Cu tc List
typedef struct {
Node *pHead; // Con tr ti nút đầu tiên
Node *pTail; // Con tr ti nút cui cùng
} List;
// Khi to danh ch rng
void init(List *l) {
l->pHead = l->pTail = NULL;
}
// Kim tra danh ch rng
int Empty(List l) {
return ((l.pHead == NULL) && (l.pTail == NULL)) ? 1 : 0;
}
// To mt nút mi
Node* createNode(int x) {
Node *p = (Node *)malloc(sizeof(Node));
if (p == NULL) {
printf("Không đủ b nh để cp phát.\n");
} else {
p->data = x;
p->prev = NULL;
p->next = NULL;
}
return p;
}
// Thêm mt phn t o đầu danh ch
void addHead(List *l, Node *pNew) {
if (Empty(*l)) {
l->pHead = l->pTail = pNew;
} else {
pNew->next = l->pHead;
l->pHead->prev = pNew;
l->pHead = pNew;
}
}
// Thêm mt phn t o cui danh sách
void addTail(List *l, Node *pNew) {
if (Empty(*l)) {
l->pHead = l->pTail = pNew;
} else {
pNew->prev = l->pTail;
l->pTail->next = pNew;
l->pTail = pNew;
}
}
// Nhp danh sách t người dùng
void input(List *l) {
init(l);
int x;
Node *pNew;
do {
printf("Nhp phn t (0 để dng): ");
scanf("%d", &x);
if (x == 0)
break;
pNew = createNode(x);
if (pNew != NULL)
addHead(l, pNew); // Thêm o đầu danh ch
} while (1);
}
// Xut danh ch t đầu đến cui
void output(List l) {
if (Empty(l)) {
printf("Danh ch rng.\n");
} else {
Node *p = l.pHead;
while (p != NULL) {
printf("%d\t", p->data);
p = p->next;
}
printf("\n");
}
}
// Xóa mt nút trong danh ch
void deleteNode(List *l, int x) {
if (Empty(*l)) {
printf("Danh ch rng, không th xóa.\n");
return;
}
Node *pDel = l->pHead;
while (pDel != NULL && pDel->data != x) {
pDel = pDel->next;
}
if (pDel == NULL) {
printf("Không tim thy phn t %d trong danh ch.\n", x);
return;
}
if (pDel == l->pHead) {
l->pHead = l->pHead->next;
if (l->pHead != NULL) {
l->pHead->prev = NULL;
} else {
l->pTail = NULL; // Danh ch ch 1 phn t
}
} else if (pDel == l->pTail) {
l->pTail = l->pTail->prev;
l->pTail->next = NULL;
} else {
pDel->prev->next = pDel->next;
pDel->next->prev = pDel->prev;
}
free(pDel); // Gii phóng b nh
}
// m mt phn t trong danh ch
Node* findNode(List l, int x) {
Node *p = l.pHead;
while (p) {
if (p->data == x)
return p;
p = p->next;
}
return NULL;
}
// Sp xếp danh ch tăng dn
void sortList(List *l) {
if (Empty(*l)) {
printf("Danh ch rng, không th sp xếp.\n");
return;
}
Node *p, *q;
int temp;
for (p = l->pHead; p != NULL; p = p->next) {
for (q = p->next; q != NULL; q = q->next) {
if (p->data > q->data) {
// Hoán đổi giá tr ca 2 nút
temp = p->data;
p->data = q->data;
q->data = temp;
}
}
}
}
Chương 2: Ngăn xếp - hàng đợi
( Stack Queue )
1. Quy tc chuyn đổi Infix sang Postfix
Đ chuyn biu thc t Infix (trung t) sang Postfix (hu t), ta thc hin theo các c sau:
1. Duyt tng t trong biu thc Infix t ti sang phi:
o Nếu toán hng (ch cái hoc s), đưa trc tiếp vào kết qu Postfix.
o Nếu du ngoc m (, đy vào Stack.
o Nếu du ngoc đóng ), ly các toán t t Stack ra cho đến khi gp du ngoc
m (.
o Nếu toán t (+, -, *, /, ^):
Ly các toán t trong Stack độ ưu tiên cao n hoc bng toán t
hin ti ra đưa o Postfix.
Sau đó, đẩy toán t hin ti vào Stack.
2. Sau khi duyt hết biu thc Infix, ly tn b các toán t còn li trong Stack ra thêm
vào Postfix.
2. Đ ưu tiên ca toán t
^: Cao nht (lũy tha).
*, /: Cao hơn cng/tr.
+, -: Thp nht.
Du ngoc:
o (: Không độ ưu tiên, ch dùng để đánh du.
3. d minh ha
d: a + b * (c ^ d - e) ^ (f + g * h) i
2. Quy tc chuyn đổi Infix sang Prefix
Quy trình thc hin:
1. Đảo ngược biu thc Infix:
o Đổi toàn b biu thc t trái sang phi thành t phi sang trái.
o Đổi du ngoc m ( thành đóng ) ngược li.
2. Áp dng quy tc chuyn đổi t Infix sang Postfix:
o Sau khi đảo ngược biu thc, áp dng y nguyên các c chuyn đổi Infix
Postfix:
Nếu toán hng, thêm vào kết qu Prefix.
Nếu toán t, x độ ưu tiên ging như trong Postfix.
S dng Stack để qun các toán t du ngoc.
3.
Đảo ngược kết qu Postfix thu đưc đ tr thành Prefix.
2. So sánh gia Postfix Prefix
Quy tc Infix Postfix Infix Prefix
Biu thc Duyt t trái sang phi
Duyt t phi sang trái sau khi đảo
ngược
Toán hng Thêm trc tiếp vào kết qu Thêm trc tiếp vào kết qu
Toán t X độ ưu tiên trong Stack X độ ưu tiên trong Stack
Kết qu Không cn đảo ngược sau khi duyt Đo ngược kết qu sau khi duyt
3. Đ ưu tiên ca toán t
Toán t Độ ưu tiên
^ Cao nht
*, / Cao n cng/tr
+, - Thp nht
(, ) Ch để đánh du không độ ưu tiên
4. d minh ha
d: i - (f + h * g) ^ (e - d ^ c) * b + a
Chương 3: Cây
Kiến thc bn v cây
Định nghĩa cây:
Mt cây tp hp các nút trong đó có:
o Mt nút gc.
o c nút con đưc t chc theo quan h phân cp.
c khái nim bn:
o Nút gc (Root): nút cao nht, không nút cha.
o Nút (Leaf): nút không con.
o Nút nhánh (Internal Node): nút không phi không phi gc.
o Mc ca nút (Level): Độ sâu tính t gc đến nút đó.
o Độ cao ca cây (Height): mc ln nht trong cây.
o Đưng đi (Path): Chui các cnh ni gia 2 nút.
o Độ dài đưng đi tng (Path Length): Tng các đưng đi t gc đến tt c các
nút.
I. Cây nh phân (Binary Tree)
Định nghĩa:
cây mi nút ti đa 2 cây con (trái phi).
c kiu cây nh phân:
o y nh phân đầy đ: Tt c các nút đều 2 con hoc không con.
o y nh phân hoàn chnh: Tt c các mc đều đầy đủ ngoi tr mc cui, các
nút mc cui nm v phía ti.
Duyt cây nh phân:
o Duyt trước (Preorder - NLR): Gc Trái Phi.
o Duyt gia (Inorder - LNR): Ti Gc Phi.
o Duyt sau (Postorder - LRN): Ti Phi Gc.
II. Cây nh phân tim kiếm (Binary Search Tree - BST)
Định nghĩa:
cây nh phân thêm ng buc:
o Khóa ti mi nút ln hơn tt c các nút trong cây con ti.
o Khóa ti mi nút nh hơn tt c các nút trong cây con phi.
Tính cht:
o Duyt gia (LNR) cho kết qu dãy s theo th t ng dn.
o Chi phí tim kiếm, thêm, xóa trung bình O(logn)O(logn), nhưng trong trường hp
xu nht (cây suy biến) O(n)O(n).
Thao tác trên BST:
o Tìm kiếm: Da trên tính cht so sánh ti/phi ca khóa.
o Thêm: Chèn vào v t phù hp để gi nguyên tính cht BST.
o Xóa:
Nút : Xóa trc tiếp.
Nút 1 con: Ni cha ca nút cn xóa vi con ca nó.
Nút 2 con: Thay thế bng nút thế mng (ln nht bên trái hoc nh
nht bên phi), sau đó xóa nút thế mng.
III. Cây nh phân tim kiếm cân bng (AVL Tree)
Định nghĩa:
cây nh phân tim kiếm vi điu kin cân bng:
o Hiu chiu cao gia cây con ti cây con phi ca mi nút không t quá 1.
Ch s cân bng (Balance Factor):
o Balance Factor = h
R
h
L
, trong đó h
R
, h
L
chiu cao ca cây con phi cây con
trái.
o Balance Factor th -1, 0, hoc 1.
Thao tác trên cây AVL:
o Thêm: Ging BST, nhưng cn kim tra cân bng li khi cây b mt cân bng.
o Xóa: Ging BST, nhưng cn cân bng li sau khi a.
o n bng li cây:
Quay đơn (Single Rotation):
Left-Left (LL): Xy ra khi cây con trái lch ti.
Right-Right (RR): Xy ra khi cây con phi lch phi.
Quay kép (Double Rotation):
Left-Right (LR): Xy ra khi cây con trái lch phi.
Right-Left (RL): Xy ra khi cây con phi lch ti.
Li ích:
o Đm bo chiu cao cây luôn O(logn).
o c thao tác tim kiếm, thêm, xóa đều chi phí O(logn).
c bài tp quan trng
1. y nh phân:
o Xây dng cây t cu trúc cho trước.
o Duyt cây theo các th t (NLR, LNR, LRN).
2. y nh phân tim kiếm:
o To cây t danh ch khóa.
o Thc hin thêm, xóa, tim kiếm, duyt cây.
3. y AVL:
o Xây dng cây t danh ch khóa.
o Thêm xóa vi các trường hp phi cân bng li cây.

Preview text:

Cấu trúc dữ liệu và giải thuật
Chương 1: Danh sách liên kết đơn và đôi I. Danh sách liên kết đơn
// Khai báo cấu trúc Node và List typedef struct Node { int data; struct Node *pNext; } Node; typedef struct { Node *pHead; Node *pTail; } List;
// Khởi tạo danh sách rỗng void init(List *l) {
l->pHead = l->pTail = NULL; }
// Kiểm tra danh sách rỗng int Empty(List l) {
return ((l.pHead == NULL) && (l.pTail == NULL)) ? 1 : 0; }
// Thêm một phần tử vào đầu danh sách
void addHead(List *l, Node *pNew) { if (Empty(*l)) {
l->pHead = l->pTail = pNew; } else { pNew->pNext = l->pHead; l->pHead = pNew; } }
// Thêm một phần tử vào cuối danh sách
void addTail(List *l, Node *pNew) { if (Empty(*l)) {
l->pHead = l->pTail = pNew; } else {
l->pTail->pNext = pNew; l->pTail = pNew; } } // Tạo một nút mới Node* createNode(int x) { Node *p;
p = (Node *)malloc(sizeof(Node)); if (p == NULL) {
printf("Không đủ bộ nhớ để cấp phát.\n"); } else { p->data = x; p->pNext = NULL; } return p; }
// Nhập danh sách từ người dùng void input(List *l) { init(l); int x; Node *pNew; do {
printf("Nhập phần tử (0 để dừng): "); scanf("%d", &x); if (x == 0) break; pNew = createNode(x); if (pNew != NULL) addHead(l, pNew); } while (1); } // Xuất danh sách void output(List l) { if (Empty(l)) {
printf("Danh sách rỗng.\n"); } else { Node *p = l.pHead; while (p != NULL) { printf("%d\t", p->data); p = p->pNext; } printf("\n"); } }
// Đếm số lượng phần tử trong danh sách int DemSL(List l) { if (Empty(l)) { return 0; } else { int dem = 0; Node *p = l.pHead; while (p) { dem++; p = p->pNext; } return dem; } }
// Xóa một nút trong danh sách
void deleteNode(List *l, int x) { if (Empty(*l)) {
printf("Danh sách rỗng, không thể xóa.\n"); return; } Node *pDel = findNode(*l, x); if (pDel == NULL) {
printf("Không tim thấy phần tử %d trong danh sách.\n", x); return; } if (pDel == l->pHead) {
// Xóa phần tử đầu tiên
l->pHead = l->pHead->pNext; if (l->pHead == NULL) {
l->pTail = NULL; // Nếu danh sách chỉ có 1 phần tử } } else {
// Xóa phần tử ở giữa hoặc cuối
Node *pPre = findNodePre(*l, pDel);
pPre->pNext = pDel->pNext; if (pDel == l->pTail) {
l->pTail = pPre; // Nếu xóa phần tử cuối } }
free(pDel); // Giải phóng bộ nhớ }
// Tìm một phần tử trong danh sách
Node* findNode(List l, int x) { Node *p = l.pHead; while (p) { if (p->data == x) break; p = p->pNext; } return p; }
// Tìm phần tử đứng trước một nút
Node* findNodePre(List l, Node *p) { if (p == l.pHead) return NULL; else { Node *pPre = l.pHead; while (pPre->pNext != p) { pPre = pPre->pNext; } return pPre; } }
// Sắp xếp danh sách theo thứ tự tăng dần void sortList(List *l) { if (Empty(*l)) {
printf("Danh sách rỗng, không thể sắp xếp.\n"); return; } Node *p, *q; int temp;
for (p = l->pHead; p != NULL; p = p->pNext) {
for (q = p->pNext; q != NULL; q = q->pNext) {
if (p->data > q->data) {
// Hoán đổi giá trị của 2 nút temp = p->data; p->data = q->data; q->data = temp; } } } } II. Danh sách liên kết đôi #include #include
// Cấu trúc Node cho DSLK đôi typedef struct Node { int data;
struct Node *prev; // Con trỏ đến nút trước
struct Node *next; // Con trỏ đến nút sau } Node; // Cấu trúc List typedef struct {
Node *pHead; // Con trỏ tới nút đầu tiên
Node *pTail; // Con trỏ tới nút cuối cùng } List;
// Khởi tạo danh sách rỗng void init(List *l) {
l->pHead = l->pTail = NULL; }
// Kiểm tra danh sách rỗng int Empty(List l) {
return ((l.pHead == NULL) && (l.pTail == NULL)) ? 1 : 0; } // Tạo một nút mới Node* createNode(int x) {
Node *p = (Node *)malloc(sizeof(Node)); if (p == NULL) {
printf("Không đủ bộ nhớ để cấp phát.\n"); } else { p->data = x; p->prev = NULL; p->next = NULL; } return p; }
// Thêm một phần tử vào đầu danh sách
void addHead(List *l, Node *pNew) { if (Empty(*l)) {
l->pHead = l->pTail = pNew; } else { pNew->next = l->pHead; l->pHead->prev = pNew; l->pHead = pNew; } }
// Thêm một phần tử vào cuối danh sách
void addTail(List *l, Node *pNew) { if (Empty(*l)) {
l->pHead = l->pTail = pNew; } else { pNew->prev = l->pTail; l->pTail->next = pNew; l->pTail = pNew; } }
// Nhập danh sách từ người dùng void input(List *l) { init(l); int x; Node *pNew; do {
printf("Nhập phần tử (0 để dừng): "); scanf("%d", &x); if (x == 0) break; pNew = createNode(x); if (pNew != NULL)
addHead(l, pNew); // Thêm vào đầu danh sách } while (1); }
// Xuất danh sách từ đầu đến cuối void output(List l) { if (Empty(l)) {
printf("Danh sách rỗng.\n"); } else { Node *p = l.pHead; while (p != NULL) { printf("%d\t", p->data); p = p->next; } printf("\n"); } }
// Xóa một nút trong danh sách
void deleteNode(List *l, int x) { if (Empty(*l)) {
printf("Danh sách rỗng, không thể xóa.\n"); return; } Node *pDel = l->pHead;
while (pDel != NULL && pDel->data != x) { pDel = pDel->next; } if (pDel == NULL) {
printf("Không tim thấy phần tử %d trong danh sách.\n", x); return; } if (pDel == l->pHead) {
l->pHead = l->pHead->next; if (l->pHead != NULL) { l->pHead->prev = NULL; } else {
l->pTail = NULL; // Danh sách chỉ có 1 phần tử }
} else if (pDel == l->pTail) {
l->pTail = l->pTail->prev; l->pTail->next = NULL; } else {
pDel->prev->next = pDel->next;
pDel->next->prev = pDel->prev; }
free(pDel); // Giải phóng bộ nhớ }
// Tìm một phần tử trong danh sách
Node* findNode(List l, int x) { Node *p = l.pHead; while (p) { if (p->data == x) return p; p = p->next; } return NULL; }
// Sắp xếp danh sách tăng dần void sortList(List *l) { if (Empty(*l)) {
printf("Danh sách rỗng, không thể sắp xếp.\n"); return; } Node *p, *q; int temp;
for (p = l->pHead; p != NULL; p = p->next) {
for (q = p->next; q != NULL; q = q->next) {
if (p->data > q->data) {
// Hoán đổi giá trị của 2 nút temp = p->data; p->data = q->data; q->data = temp; } } } }
Chương 2: Ngăn xếp - hàng đợi ( Stack – Queue )
1. Quy tắc chuyển đổi Infix sang Postfix
Để chuyển biểu thức từ Infix (trung tố) sang Postfix (hậu tố), ta thực hiện theo các bước sau:
1. Duyệt từng tự trong biểu thức Infix từ trái sang phải:
o Nếu là toán hạng (chữ cái hoặc số), đưa trực tiếp vào kết quả Postfix.
o Nếu là dấu ngoặc mở (, đẩy vào Stack.
o Nếu là dấu ngoặc đóng ), lấy các toán tử từ Stack ra cho đến khi gặp dấu ngoặc mở (.
o Nếu là toán tử (+, -, *, /, ^):
▪ Lấy các toán tử trong Stack có độ ưu tiên cao hơn hoặc bằng toán tử
hiện tại ra và đưa vào Postfix.
▪ Sau đó, đẩy toán tử hiện tại vào Stack.
2. Sau khi duyệt hết biểu thức Infix, lấy toàn bộ các toán tử còn lại trong Stack ra và thêm vào Postfix.
2. Độ ưu tiên của toán tử
• ^: Cao nhất (lũy thừa).
• *, /: Cao hơn cộng/trừ. • +, -: Thấp nhất.
Dấu ngoặc:
o (: Không có độ ưu tiên, chỉ dùng để đánh dấu.
3. Ví dụ minh họa
Ví dụ: a + b * (c ^ d - e) ^ (f + g * h) – i
2. Quy tắc chuyển đổi Infix sang Prefix
Quy trình thực hiện:
1. Đảo ngược biểu thức Infix:
o Đổi toàn bộ biểu thức từ trái sang phải thành từ phải sang trái.
o Đổi
dấu ngoặc mở ( thành đóng ) ngược lại.
2. Áp dụng quy tắc chuyển đổi từ Infix sang Postfix:
o Sau khi đảo ngược biểu thức, áp dụng y nguyên các bước chuyển đổi Infix Postfix:
▪ Nếu toán hạng, thêm vào kết quả Prefix.
▪ Nếu
toán tử, xử độ ưu tiên giống như trong Postfix.
▪ Sử
dụng Stack để quản các toán tử dấu ngoặc.
3. Đảo ngược kết quả Postfix thu được để trở thành Prefix.
2. So sánh giữa Postfix Prefix Quy tắc
Infix Postfix
Infix Prefix
Duyệt
từ phải sang trái sau khi đảo Biểu thức
Duyệt từ trái sang phải ngược Toán hạng
Thêm trực tiếp vào kết quả
Thêm trực tiếp vào kết quả Toán tử
Xử độ ưu tiên trong Stack
Xử độ ưu tiên trong Stack Kết quả
Không cần đảo ngược sau khi duyệt Đảo ngược kết quả sau khi duyệt
3. Độ ưu tiên của toán tử
Toán tử Độ ưu tiên ^ Cao nhất *, /
Cao hơn cộng/trừ +, - Thấp nhất (, )
Chỉ để đánh dấu không độ ưu tiên
4. Ví dụ minh họa
dụ: i - (f + h * g) ^ (e - d ^ c) * b + a Chương 3: Cây
Kiến thức bản về cây
Định nghĩa cây:
Một cây là tập hợp các nút trong đó có: o Một nút gốc.
o Các nút con được tổ chức theo quan hệ phân cấp.
Các khái niệm bản:
o Nút gốc (Root): Là nút cao nhất, không có nút cha.
o Nút (Leaf): Là nút không có con.
o Nút nhánh (Internal Node): Là nút không phải lá và không phải gốc.
o Mức của nút (Level): Độ sâu tính từ gốc đến nút đó.
o Độ cao của cây (Height): Là mức lớn nhất trong cây.
o Đường đi (Path): Chuỗi các cạnh nối giữa 2 nút.
o Độ dài đường đi tổng (Path Length): Tổng các đường đi từ gốc đến tất cả các nút. I.
Cây nhị phân (Binary Tree)
Định nghĩa:
Là cây mà mỗi nút có tối đa 2 cây con (trái và phải).
Các kiểu cây nhị phân:
o Cây nhị phân đầy đủ: Tất cả các nút đều có 2 con hoặc không có con.
o Cây nhị phân hoàn chỉnh: Tất cả các mức đều đầy đủ ngoại trừ mức cuối, và các
nút ở mức cuối nằm về phía trái.
Duyệt cây nhị phân:
o Duyệt trước (Preorder - NLR): Gốc → Trái → Phải.
o Duyệt giữa (Inorder - LNR): Trái → Gốc → Phải.
o Duyệt sau (Postorder - LRN): Trái → Phải → Gốc. II.
Cây nhị phân tim kiếm (Binary Search Tree - BST)
Định nghĩa:
Là cây nhị phân có thêm ràng buộc:
o Khóa tại mỗi nút lớn hơn tất cả các nút trong cây con trái.
o Khóa tại mỗi nút nhỏ hơn tất cả các nút trong cây con phải.
Tính chất:
o Duyệt giữa (LNR) cho kết quả dãy số theo thứ tự tăng dần.
o Chi phí tim kiếm, thêm, xóa trung bình O(logn)O(logn), nhưng trong trường hợp
xấu nhất (cây suy biến) là O(n)O(n).
Thao tác trên BST:
o Tìm kiếm: Dựa trên tính chất so sánh trái/phải của khóa.
o Thêm: Chèn vào vị trí phù hợp để giữ nguyên tính chất BST. o Xóa:
Nút : Xóa trực tiếp.
Nút 1 con: Nối cha của nút cần xóa với con của nó.
Nút 2 con: Thay thế bằng nút thế mạng (lớn nhất bên trái hoặc nhỏ
nhất bên phải), sau đó xóa nút thế mạng. III.
Cây nhị phân tim kiếm cân bằng (AVL Tree)
Định nghĩa:
Là cây nhị phân tim kiếm với điều kiện cân bằng:
o Hiệu chiều cao giữa cây con trái và cây con phải của mỗi nút không vượt quá 1.
Chỉ số cân bằng (Balance Factor):
o Balance Factor = hR − hL , trong đó hR, hL là chiều cao của cây con phải và cây con trái.
o Balance Factor có thể là -1, 0, hoặc 1.
Thao tác trên cây AVL:
o Thêm: Giống BST, nhưng cần kiểm tra và cân bằng lại khi cây bị mất cân bằng.
o Xóa: Giống BST, nhưng cần cân bằng lại sau khi xóa.
o Cân bằng lại cây:
Quay đơn (Single Rotation):
▪ Left-Left (LL): Xảy ra khi cây con trái lệch trái.
▪ Right-Right (RR): Xảy ra khi cây con phải lệch phải.
Quay kép (Double Rotation):
▪ Left-Right (LR): Xảy ra khi cây con trái lệch phải.
▪ Right-Left (RL): Xảy ra khi cây con phải lệch trái. • Lợi ích:
o Đảm bảo chiều cao cây luôn O(logn).
o Các thao tác tim kiếm, thêm, xóa đều có chi phí O(logn).
Các bài tập quan trọng
1. Cây nhị phân:
o Xây dựng cây từ cấu trúc cho trước.
o Duyệt cây theo các thứ tự (NLR, LNR, LRN).
2. Cây nhị phân tim kiếm:
o Tạo cây từ danh sách khóa.
o Thực hiện thêm, xóa, tim kiếm, và duyệt cây. 3. Cây AVL:
o Xây dựng cây từ danh sách khóa.
o Thêm và xóa với các trường hợp phải cân bằng lại cây.