CHƯƠNG 3
DANH SÁCH LIÊN KẾT
1
Tài Liệu Tham Khảo
Bài giảng CTDL, ĐH Công nghệ thông tin TPHCM Bài giảng CTDL, Khoa Công nghệ thông tin, ĐH
Nhập môn CTDL, Dương Anh Đức, Trần Hạnh Nhi,
KHTN TPHCM
ĐH KHTN TPHCM
2
Nội dung
Danh sách liên kết đơn
Danh sách liên kết kép
Stack
Queue
3
Con trỏ
Kiểu con trỏ dùng lưu địa chỉ của một đối tượng dữ liệu khác.
Biến thuộc kiểu con trỏ Tp là biến mà giá trị của nó là địa chỉ cuả một vùng nhớ ứng với một biến kiểu T, hoặc là giá trị NULL.
Bản thân biến con trỏ là không động
Dùng biến con trỏ để lưu giữ điạ chỉ của biến động => truy
xuất biến động thông qua biến con trỏ
4
Con trỏ
5
Con trỏ
6
Con trỏ
7
Con trỏ
8
Kiểu danh sách
Danh sách = { các phần tử có cùng kiểu}
Danh sách là một kiểu dữ liệu tuyến tính :
Mỗi phần tử có nhiều nhất 1 phần tử đứng trước
Mỗi phần tử có nhiều nhất 1 phần tử đứng sau
Là kiểu dữ liệu quen thuộc trong thực tế :
Danh sách học sinh
Danh mục sách trong thư viện
Danh bạ điện thoại
Danh sách các nhân viên trong công ty
9
…
Các hình thức tổ chức danh sách
CTDL cho mỗi phần tử ?
Thể hiện liên kết của các phần tử ?
Hai hình thức cơ bản :
Mảng
Liên kết ngầm :
Danh sách liên kết Liên kết tường minh :
10
Danh sách liên kết ngầm(mảng)
Mối liên hệ giữa các phần tử được thể hiện ngầm:
xi : phần tử thứ i trong danh sách xi , xi+1 là kế cận trong danh sách
Phải lưu trữ liên tiếp các phần tử trong bộ nhớ công thức xác định địa chỉ phần tử thứ i:
address(i) = address(1) + (i-1)*sizeof(T)
Ưu điểm : Truy xuất trực tiếp, nhanh chóng
Nhược điểm:
x0
…
xi
xi+1
Sử dụng bộ nhớ kém hiệu quả Kích thước cố định Các thao tác thêm vào , loại bỏ không hiệu quả
11
Liên kết tuờng minh(Danh sánh liên kết)
CTDL cho một phần tử
Thông tin bản thân Địa chỉ của phần tử kế trong danh sách
x2
x0
x3
x1
Mỗi phần tử là một biến động
Ưu điểm
+ Sử dụng hiệu quả bộ nhớ + Linh động về số lượng phần tử
12
Các loại danh sách liên kết
Danh sách liên kết đơn: Mỗi phần tử liên kết với phần
tử đứng sau nó trong danh sách
A
B
C
D
Danh sách liên kết kép: Mỗi phần tử liên kết với phần tử đứng trước và sau nó trong danh sách
A
B
C
D
13
Các loại danh sách liên kết
Danh sách liên Vòng: Phần tử cuối danh sách liên với
phần tử đầu danh sách
Danh sách liên kết đơn vòng
A
B
C
D
Danh sách liên kết đôi vòng
A
B
C
D
14
Cấu tạo nút trên danh sách liên kết
15
Cấu tạo nút trên danh sách liên kết
16
Cấu tạo nút trên danh sách liên kết
17
Cấu tạo nút trên danh sách liên kết
18
Cấu tạo của danh sách liên kết
19
Cấu tạo của danh sách liên kết
20
Cấu tạo của danh sách liên kết
21
Tổ Chức Của DSLK Đơn
x2
x0
x3
x1
Mỗi phần tử liên kết với phần tử đứng liền sau trong danh
Mỗi phần tử trong danh sách liên kết đơn là một cấu trúc
sách
có hai thành phần Thành phần dữ liệu: Lưu trữ thông tin về bản thân
phần tử
Thành phần liên kết: Lưu địa chỉ phần tử đứng sau
trong danh sách hoặc bằng NULL nếu là phần tử cuối danh sách.
22
CTDL của DSLK đơn
Cấu trúc dữ liệu của 1 nút trong List đơn
typedef { Data struct tagNode Info; // Lưu thông tin bản thân
pNext
struct tagNode *pNext; //Lưu địa chỉ của Node đứng sau
}Node;
Info
Cấu trúc dữ liệu của DSLK đơn
struct tagList
typedef { Node *pHead;//Lưu địa chỉ Node đầu tiên trong List
// kiểu danh sách liên kết đơn
}LIST;
Node *pTail; //Lưu địa chỉ của Node cuối cùng trong List
23
Ví dụ tổ chức DSLK đơn trong bộ nhớ
pHead
pTail
3f 4
4f 7
5f 6
4f 5f NULL
Trong ví dụ trên thành phần dữ liệu là 1 số nguyên
24
Các thao tác cơ bản trên DSLK đơn
Tạo 1 danh sách liên kết đơn rỗng
Tạo 1 nút có trường Infor bằng x
Tìm một phần tử có Info bằng x
Thêm một phần tử có khóa x vào danh sách
Kiểm tra số phần tử của danh sách
Hủy một phần tử trong danh sách
Duyệt danh sách
Sắp xếp danh sách liên kết đơn
25
Khởi tạo danh sách liên kết
Địa chỉ của nút đầu tiên, địa chỉ của nút cuối
cùng đều không có
{
void CreateList(List &l)
l.pHead=NULL;
l.pTail=NULL;
}
26
Tạo 1 phần tử mới
Hàm trả về địa chỉ phần tử mới tạo
Node* CreateNode(Data x) // trong bài học là int
{ Node *p;
p = new Node;//Cấp phát vùng nhớ cho phần tử
//gán dữa liệu cho nút
if ( p==NULL) exit(1);
p->pNext = NULL;
p ->Info = x;
return p;
}
27
Thêm 1 phần tử vào DSLK
Nguyên tắc thêm: Khi thêm 1 phần tử vào List thì có
Các vị trí cần thêm 1 phần tử vào List:
làm cho pHead, pTail thay đổi?
Thêm vào cuối List
Thêm vào đầu List đơn
Thêm vào sau 1 phần tử q trong list
28
Thuật toán thêm 1 phần tử vào đầu DSLK
Thêm nút p vào đầu danh sách liên kết đơn
Nếu List rỗng thì
Bắt đầu:
+ pHead = p;
+ pTail = pHead;
Ngược lại
+ pHead = p
+ p->pNext = pHead;
29
Minh họa thuật toán thêm vào đầu
pHead
4f
2f 3 3f 3f 4 4f
8 …
9f
10 2fN
pHead=P
P->pNext=pHead
P
30
Hàm thêm 1 phần tử vào đầu List void AddHead(LIST &l, Node* p) {
if (l.pHead==NULL) {
l.pHead = p; l.pTail = l.pHead;
} else {
p->pNext = l.pHead; l.pHead = p;
}
31
}
Thuật toán thêm vào cuối DSLK
Ta cần thêm nút p vào cuối list đơn
Bắt đầu:
Nếu List rỗng thì
+ pHead = p;
Ngược lại
+ pTail = pHead;
+ pTail->pNext=p;
+ pTail=p
32
Minh họa thuật toán thêm vào cuối
pTail
5f 3f
4 4f
5 N9f
pTail=P
4f 8 5f
pTail->pNext=P
9f
6 N
P
33
Hàm thêm 1 phần tử vào cuối DSLKD
void AddTail(LIST &l, Node *p) {
if (l.pHead==NULL) {
l.pHead = p; l.pTail = l.pHead;
} else {
}
l.pTail->Next = p; l.pTail = p;
}
34
Thuật toán phần tử p vào sau phần tử q
Ta cần thêm nút p vào sau nút q trong list đơn
Bắt đầu:
B1: p->pNext = q->pNext
Nếu (q!=NULL) thì
B2:
+ q->pNext = p
+ nếu q = pTail thì
pTail=p
35
Minh họa thuật toán
q
4f 3f
5f 5 ..
4 4f
8 5f9f
q->pNext=P
P->pNext=q->pNext
5fN 9f 7
P
36
Cài đặt thuật toán
void InsertAfterQ(List &l, Node *p, Node *q) {
if(q!=NULL) {
p->pNext=q->pNext; q->pNext=p; if(l.pTail==q) l.Tail=p;
AddHead(l,q);// thêm q vào đầu list
} else
}
37
Hủy phần tử trong DSLK đơn
Nguyên tắc: Phải cô lập phần tử cần hủy trước hủy.
Các vị trị cần hủy
Hủy phần tử đứng đầu List
Hủy phần tử có khoá bằng x
Ở phần trên, các phần tử trong DSLK đơn được cấp phát vùng nhớ động bằng hàm new, thì sẽ được giải phóng vùng nhớ bằng hàm delete.
Huỷ phần tử đứng sau q trong danh sách liên kết đơn
38
Thuật toán hủy phần tử đầu trong DSLK
Bắt đầu:
Nếu (pHead!=NULL) thì
B1: p=pHead
+ pHead = pHead->pNext
B2:
+ delete (p)
B3:
Nếu pHead==NULL thì pTail=NULL
39
Cài đặt thuật toán Hủy được hàm trả về 1, ngược lại hàm trả về 0
int RemoveHead(List &l, int &x) { Node *p;
if(l.pHead!=NULL) p=l.pHead; { x=p->Info; //lưu Data của nút cần hủy l.pHead=l.pHead->pNext; delete p; if(l.pHead==NULL) l.pTail=NULL;
} return 0;
return 1;
40
}
Minh hoạ thuật toán
pHead=pHead->pNext
pHead
2f
3f
4f
2f
6
3f
3
4f
1f 7
…8
P=pHead
P
41
Hủy phần tử sau phần tử q trong List
Bắt đầu
Nếu (q!=NULL) thì //q tồn tại trong List
B1: p=q->pNext;// p là phần tử cần hủy
B2: Nếu (p!=NULL) thì // q không phải là phần tử cuối
+ q->pNext=p->pNext;// tách p ra khỏi xâu
+ nếu (p== pTail) // nút cần hủy là nút cuối
pTail=q;
+ delete p;// hủy p
42
Cài đặt thuật toán int RemoveAfterQ(List &l, Node *q, int &x) { Node *p;
if(q!=NULL) {
p=q->pNext; //p là nút cần xoá if(p!=NULL) // q không phài là nút cuối {
if(p==l.pTail) //nút cần xoá là nút cuối cùng l.pTail=q;// cập nhật lạ pTail q->pNext=p->pNext; x=p->Info; delete p;
} else
} return 1;
43
return 0; }
Minh họa thuật toán
p-=q->pNext
p
q 2f
3f
4f
pHead
3f4f
1f 7
2f
6
3
4f
…8
q->pNext=p->pNext
44
Thuật toán hủy phần tử có khoá x Bước 1:
Tìm phần tử p có khoá bằng x, và q đứng trước p
Nếu (p!=NULL) thì //tìm thấy phần tử có khoá bằng x
Bước 2:
Hủy p ra khỏi List bằng cách hủy phần tử
đứng sau q
Ngược lại
Báo không tìm thấy phần tử có khoá x
45
Cài đặt thuật toán int RemoveX(List &l, int x) { Node *p,*q = NULL; p=l.Head;
while((p!=NULL)&&(p->Info!=x)) //tìm phần tử có khóa x và phần tử q đứng trước nó {
q=p; p=p->pNext;
} if(p==NULL) //không tìm thấy phần tử có khoá bằng x
return 0;
DeleteAfterQ(l,q,x);
if(q!=NULL)//tìm thấy phần tử có khoá bằng x
else //phần tử cần xoá nằm đầu List
RemoveHead(l,x);
46
return 1;}
Tìm 1 phần tử trong DSLK đơn
Tìm tuần tự (hàm trả về), các bước của thuật toán tìm nút có Info bằng x trong list đơn
Bước 2:
Bước 1: p=pHead;// địa chỉ của phần tử đầu trong list đơn
Trong khi p!=NULL và p->Info!=x
p=p->pNext;// xét phần tử kế
Bước 3:
Info = x
+ Nếu p!=NULL thì p lưu địa chỉ của nút có
47
+ Ngược lại : Không có phần tử cần tìm
Hàm tìm 1 phần tử trong DSLK đơn Hàm tìm phần tử có Info = x, hàm trả về địa chỉ của nút
có Info = x, ngược lại hàm trả về NULL
Node *Search(LIST l, Data x)
Node *p;
{
p = l.pHead;
while((p!= NULL)&&(p->Info != x))
p = p->pNext;
return p;
48
}
Minh họa thuật toán tìm phần tử trong DSLK
4f 5f 2f 3f
pHead
56
1f 34
3
4
8 8
P
X = 8
Tìm thấy, hàm trả về địa chỉ của nút tìm thấy là 4f
49
Duyệt danh sách
Duyệt danh sách là thao tác thường được thực hiện khi có nhu cầu cần xử lý các phần tử trong danh sách như:
Đếm các phần tử trong danh sách
Tìm tất cả các phần tử trong danh sách thỏa điều kiện
Hủy toàn bộ danh sách
50
Thuật toán duyệt danh sách
Bước 1:
p = pHead;// p lưu địa chỉ của phần tử đầu trong List
Bước 2:
Trong khi (danh sách chưa hết) thực hiện
+ p=p->pNext;// qua phần tử kế
+ xử lý phần tử p
51
Cài đặt in các phần tử trong List
void PrintList(List l)
{
Node *p;
while(p!=NULL)
p=l.pHead;
{ printf(“%d ”, p->Info);
p=p->pNext;
}
}
52
Hủy danh sách liên kết đơn Bước 1:
Trong khi (danh sách chưa hết) thực hiện
p = pHead;
• B11:
pHead = pHead->pNext;// cập nhật pHead
• B12:
Bước 2:
Hủy p
pTail = NULL;// bảo toàn tính nhất quán khi xâu rỗng
53
Cài đặt thuật toán void RemoveList(List &l)
{
while(l.pHead!=NULL)//còn phần tử trong List
Node *p;
{
p = l.pHead;
l.pHead = p->pNext;
delete p;
}
54
}
Minh họa thuật toán
pTail
pHead
2f
3f
4f
5f
3f
5f N9
2f
6
3
4f
1f 7
8
p
55
Sắp xếp danh sách
Có hai cách tiếp cận Cách 1: Thay đổi thành phần Info
pHead
pTail
5f
3f 4
4f
4f 7
5f
6
N
pHead
pTail
5f
3f 4
4f
4f 6
5f 7 N
56
Sắp xếp danh sách
Cách 2: Thay đổi thành phần pNext (thay đổi trình tự móc nối của các phần tử sao cho tạo lập nên được thứ tự mong muốn) pHead
pTail
5f
3f 4
4f 6 N
4f 7
5f
pHead
pTail
5f
4f
3f 4
5f 6 4f N7
57
Ưu, nhược điểm của 2 cách tiếp cận
Thay đổi thành phần Info (dữ liệu)
Đòi hỏi thêm vùng nhớ khi hoán vị nội dung của 2 phần tử ->
chỉ phù hợp với những xâu có kích thước Info nhỏ
Khi kích thước Info (dữ liệu) lớn chi phí cho việc hoán vị thành
phần Info lớn
Làm cho thao tác sắp xếp chậm
Thay đổi thành phần pNext
Ưu:
Kích thước của trường này không thay đổi, do đó không phụ
thuộc vào kích thước bản chất dữ liệu lưu tại mỗi nút.
Thao tác sắp xếp nhanh Nhược: Cài đặt phức tạp
Ưu: Cài đặt đơn giản, tương tự như sắp xếp mảng Nhược:
58
Dùng thuật toán SX SelectionSort để SX List void SelectionSort(LIST &l) {
Node *p,*q,*min; p=l.pHead; while(p!=l.pTail) {
min=p; q=p->pNext;
while(q!=NULL)
{
if(q->Info
}
59
Các thuật toán sắp xếp hiệu quả trên List
Các thuật toán sắp xếp xâu (List) bằng các thay đổi
thành phần pNext (thành phần liên kết) có hiệu quả cao như:
Thuật toán sắp xếp Merge Sort
Thuật toán sắp xếp Quick Sort
Thuật toán sắp xếp Radix Sort
60
Thuật toán sắp xếp Quick Sort
Bước 1:
Chọn X là phần tử đầu xâu L làm phần tử cầm canh
Loại X ra khỏi L
Bước 2:
Tách xâu L ra làm 2 xâu L1(gồm các phần tử nhỏ hơn hoặc
bằng x) và L2 (gồm các phần tử lớn hơn X) Bước 3: Nếu (L1 !=NULL) thì QuickSort(L1) Bước 4: Nếu (L2!=NULL) thì QuickSort(L2) Bước 5: Nối L1, X, L2 lại theo thứ tự ta có xâu L đã được sắp
xếp
61
Minh họa thuật toán
Cho danh sách liên kết gồm các phần tử sau:
pHead
pTail
4
6
5
1
8
2
4
X =
pTail
pHead
1
2
L1 (X)
pTail
pHead
L2 (>X)
6
5
8
62
Minh họa thuật toán (tt)
pTail
pHead
L2 (>X)
6
5
8
Sắp xếp L1 Sắp xếp L2
6
Chọn x=6 cầm canh, và tách L2 thành L21 và L22
X2 =
pTail
pHead
5
L21 (X)
pTail
pHead
L22 (>X)
8
63
Minh họa thuật toán (tt)
Nối L21, X2, L22 thành L2
pTail
pHead
L2
5
6
8
Nối L1, X, L2 thành L
pHead
pTail
1
2
4
5
6
8
64
Cài đặt thuật toán void QuickSort(List &l) { Node *p,*X;//X lưu địa chỉ của phần tử cầm canh
List l1,l2; if(l.pHead==l.pTail) return;//đã có thứ tự CreateList(l1); CreateList(l2); X=l.pHead; l.pHead=X->pNext; while(l.pHead!=NULL)//tách L = L1 va L2 {
p=l.pHead; l.pHead=p->pNext; p->pNext=NULL; if(p->Info<=X->Info)
AddHead(l1,p);
else
AddHead(l2,p);} 65
Cài đặt thuật toán (tt)
QuickSort(l1);//Gọi đệ quy sắp xếp L1 QuickSort(l2);//Gọi đệ quy sắp xếp L2 if(l1.pHead!=NULL)//nối l1, l2 va X vao l {
l.pHead=l1.pHead; l1.pTail->pNext=X;//nối X vào
} else
l.pHead=X; X->pNext=l2.pHead; if(l2.pHead!=NULL) //l2 có trên một phần tử
l.pTail=l2.pTail; else //l2 không có phần tử nào
l.pTail=X;
66
}
1.
Cài đặt hàm main() Yêu cầu: Viết chương trình thành lập 1 xâu đơn, trong đó thành phần dữ liệu của mỗi nút là 1 số nguyên dương. Liệt kê tất cả thành phần dữ liệu của tất cả các nút trong xâu
2. Tìm 1 phần tử có khoá bằng x trong xâu. 3. Xoá 1 phần tử đầu xâu 4. Xoá 1 phần tử có khoá bằng x trong xâu
5. 6. Chèn 1 phần tử vào xâu, sao cho sau khi chèn xâu
Sắp xếp xâu tăng dần theo thành phần dữ liệu (Info)
vẫn tăng dần theo trường dữ liệu
67
Cài đặt hàm main() (tt)
void main() { LIST l1; Node *p; int x;
printf(“nhap x=”); scanf(“%d”,&x); if(x>0) {
CreateList(l1); do{
p = CreateNode(x); AddHead(l1,x);
} }while(x>0); printf(“Danh sách mới thành lập là\n”); PrintList(l1); printf(“nhập x cần tìm x=”); scanf(“%d”,&x);
68
Cài đặt hàm main() (tt)
p = Search(l1,x);
if(p==NULL) printf(“không tìm thấy”);
else printf(“tìm thấy”);
RemoveHead(l1,x);
PrintList(l1);
printf(“danh sách sau khi xóa\n”);
printf(“nhập khoá cần xoá\n”);
scanf(“%d”,&x);
RemoveX(l1,x);
69
Cài đặt hàm main() (tt)
printf(“danh sách sau khi xoá”);
PrintfList(l1);
SelectionSort(l1);
printf(“Danh sách sau khi sắp xếp”);
PrintfList(l1);
RemoveList(l1);
}
70
Vài ứng dụng danh sách liên kết đơn
Dùng xâu đơn để lưu trữ danh sách các học viên trong
lớp học
Dùng xâu đơn để quản lý danh sách nhân viên trong
Dùng xâu đơn để quản lý danh sách các cuốn sách
một công ty, trong cơ quan
Dùng xâu đơn để quản lý các băng đĩa trong tiệm cho
trong thư viện
..vv
thuê đĩa.
71
Giới thiệu các loại DSLK khác
Danh sách liên kết kép (Doubly-Linked List)
Danh sách liên kết vòng (Circular-Linked List)
72
Định Nghĩa
Mỗi phần tử liên kết với phần tử đứng trước và
sau nó trong danh sách
Hình vẽ minh họa danh sách liên kết kép:
A
B
C
D
73
Danh sách liên kết kép
Xaâu ñoâi veà maët cô baûn coù tính chaát gioáng
nhö xaâu ñôn.
Tuy nhieân noù coù moät soá tính chaát khaùc xaâu
ñôn nhö sau: ◦ Xaâu ñoâi coù moái lieân keát hai chieàu neân töø moät
phaàn töû baát kyø coù theå truy xuaát moät phaàn töû baát kyø khaùc. Trong khi treân xaâu ñôn ta chæ coù theå truy xuaát ñeán caùc phaàn töû ñöùng sau moät phaàn töû cho tröôùc. Ñieàu naøy daãn ñeán vieäc ta coù theå deã daøng huûy phaàn töû cuoái xaâu ñoâi, coøn treân xaâu ñôn thao taùc naøy toàn chi phí O(n).
74
Danh sách liên kết kép
◦ Buø laïi, xaâu ñoâi toán chi phí gaáp ñoâi so vôùi xaâu
ñôn cho vieäc löu tröõ caùc moái lieân keát. Ñieàu naøy khieán vieäc caäp nhaät cuõng naëng neà hôn trong moät soá tröôøng hôïp. Nhö vaäy ta caàn caân nhaéc löïa choïn CTDL hôïp lyù khi caøi ñaët cho moät öùng duïng cuï theå.
75
DSLK kép
76
Cấu Trúc Dữ Liệu Cấu trúc dữ liệu 1 nút
typedef struct tagDnode { Data Info;
}DNode;
Cấu trúc List kép
struct tagDnode *pPre; struct tagDnode *pNext;
Typedef struct tagDList { DNode *pHead; DNode *pTail;
}DList;
77
Các Thao Tác Trên List Kép
Khởi tạo danh sách liên kết kép rỗng Tạo 1 nút có thành phần dữ liệu = x Chèn 1 phần tử vào danh sách
Tìm 1 phần tử trong danh sách Sắp xếp danh sách
◦ Chèn vào đầu ◦ Chèn sau phần tử Q ◦ Chèn vào trước phần tử Q ◦ Chèn vào cuối danh sách Huỷ 1 phần tử trong danh sách ◦ Hủy phần tử đầu danh sách ◦ Hủy phần tử cuối danh sách ◦ Hủy 1 phần tử có khoá bằng x
78
Tạo 1 Danh Sách Rỗng
void CreateDList(DList &l) {
l.DHead=NULL; l.DTail=NULL;
}
79
Tạo 1 Nút Có Thành Phần Dữ Liệu = X
DNode *CreateDNode(int x) { DNode *tam;
printf("khong con du bo nho"); exit(1);
tam=new DNode; if(tam==NULL) {
tam->Info=x; tam->pNext=NULL; tam->pPre=NULL; return tam;
} else {
}
80
}
Thêm 1 Nút Vào Đầu Danh Sách
• Minh họa hình vẽ
pHead
A
B
C
D
pTail
X
81
Cài Đặt Thêm 1 Nút Vào Đầu Danh Sách
void AddFirst(DList &l, DNode *tam) {
l.pHead=tam; l.pTail=l.pHead;
if(l.pHead==NULL)//xau rong {
} else {
tam->pNext=l.pHead; l.pHead->pPre=tam; l.pHead=tam;
}
82
}
Thêm Vào Cuối Danh Sách
Minh họa thêm 1 phần tử vào sau danh sách
pTail
pHead
A
B
C
D
X
83
Cài Đặt Thêm 1 Nút Vào Cuối Danh Sách
if(l.pHead==NULL) {
void AddEnd(DList &l,DNode *tam) {
} else {
tam->pPre=l.pTail; l.pTail->pNext=tam; tam=l.pTail;
l.pHead=tam; l.pTail=l.pHead;
}
84
}
Thêm Vào Sau Nút Q
Minh họa thêm nút X vào sau nút q
pTail
pHead
q
A
B
C
D
X
85
Cài Đặt Thêm 1 Nút Vào Sau Nút Q void AddLastQ(DList &l,DNode *tam, DNode *q) {
DNode *p; p=q->pNext; if(q!=NULL)//them vao duoc {
tam->pNext=p; tam->pPre=q; q->pNext=tam; if(p!=NULL)
p->pPre=tam;
if(q==l.pTail) //them vao sau danh sach lien ket.
l.pTail=tam;
} else
AddFirst(l,tam);
86
}
Thêm 1 Nút Vào Trước Nút Q
Minh hoạ thêm 1 nút vào trước nút q
pTail
pHead
q
A
B
C
D
X
87
Cài Đặt Thêm 1 Nút Vào Trước Nút Q
void AddBeforeQ(DList &l,DNode *tam,DNode *q) { DNode *p; p=q->pPre; if(q!=NULL) {
tam->pNext=q; q->pPre=tam; tam->pPre=p; if(p!=NULL)
p->pNext=tam;
if(q==l.pHead)
l.pHead = tam;
} else
AddEnd(l,tam);
88
}
Xoá Phần Tử Đầu Danh Sách
void DeleteFirst(DList &l) {
DNode *p; if(l.pHead!=NULL) {
}
p=l.pHead; l.pHead=l.pHead->pNext; l.pHead->pPre=NULL; delete p; if(l.pHead==NULL) l.pTail=NULL;
}
89
Xoá 1 Phần Tử Cuối Danh Sách
DNode *p; if(l.pHead!=NULL) //tuc xau co hon mot phan tu {
void DeleteEnd(DList &l ) {
p=l.pTail; l.pTail=l.pTail->Pre; l.pTail->pNext=NULL; delete p; if(l.pTail==NULL)
l.pHead=NULL;
}
}
90
Hủy 1 Nút Sau Nút Q
void DeleteLastQ(DList &l,DNode *q) {
DNode *p;//luu node dung sau node q if(q!=NULL) {
p=q->pNext; if(p!=NULL) {
q->pNext=p->pNext; if(p==l.pTail)//xoa dung nu't cuoi
else
l.pTail=q; //Nut xoa khong phai nut cuoi p->pNext->pPre=q;
delete p;
}
} else
DeleteFirst(l);
91
}
Huỷ 1 Nút Đứng Trước Nút Q
void DeleteBeforeQ(DList &l,DNode *q) {
DNode *p; if(q!=NULL) //tuc ton tai node q {
p=q->pPre; if(p!=NULL) {
q->pPre=p->pPre; if(p==l.pHead)//p la Node dau cua danh sach
l.pHead=q; else //p khong phai la node dau
p->pPre->pNext=q;
delete p;
}
} else
DeleteEnd(l);
92
}
Xoá 1 Phần Tử Có Khoá = X
int DeleteX(DList &l,int x) {
DNode *p; DNode *q; q=NULL; p=l.pHead; while(p!=NULL) {
if(p->Info==x) break;
q=p;//q la Node co truong Info = x p=p->pNext;
} if(q==NULL) return 0;//khong tim thay Node nao co truong Info =x if(q!=NULL)
DeleteLastQ(l,q);
else
DeleteFirst(l);
93
return 1;
}
Sắp Xếp
void DoiChoTrucTiep(DList &l) { DNode *p,*q; p=l.pHead; while(p!=l.pTail) {
if(p->Info>q->Info) HV(p,q);
q=p->pNext; while(q!=NULL) {
} p=p->pNext;
q=q->pNext;
94
}}
Danh sách liên kết vòng
Danh saùch lieân keát voøng (xaâu voøng) laø moät danh saùch ñôn (hoaëc ñoâi) maø phaàn töû cuoái danh saùch thay vì mang giaù trò NULL, troû tôùi phaàn töû ñaàu danh saùch.
Ñoái vôùi danh saùch voøng, coù theå xuaát phaùt töø moät phaàn töû baát kyø ñeå duyeät toaøn boä danh saùch.
95
Danh sách liên kết vòng
Ñeå bieåu dieãn, coù theå söû duïng caùc kyõ thuaät bieåu dieãn nhö danh saùch ñôn (hoaëc ñoâi).
Tail
Head
A
B
X
Z
Y
Tail
Head
D
A
B
C
96
Danh sách liên kết vòng Tìm phaàn töû treân danh saùch voøng Danh saùch voøng khoâng coù phaàn töû ñaàu danh saùch roõ reät, nhöng ta coù theå ñaùnh daáu moät phaàn töû baát kyø treân danh saùch xem nhö phaân töû ñaàu xaâu ñeå kieåm tra vieäc duyeät ñaõ qua heát caùc phaàn töû cuûa danh saùch hay chöa.
97
Danh saùch lieân keát voøng Tìm phaàn töû treân danh saùch voøng NODE* Search(LIST &l, Data x)
{ NODE *p;
p = l.pHead; do { if ( p->Info == x) return p;
p = p->pNext;
} while (p != l.pHead); // chöa ñi giaùp voøng return p;
}
98
Danh saùch lieân keát voøng Theâm phaàn töû ñaàu xaâu
Void AddHead(LIST &l, NODE *new_ele) {
if(l.pHead == NULL) //Xaâu roãng {
l.pHead = l.pTail = new_ele; l.pTail->pNext = l.pHead;
} else {
new_ele->pNext = l.pHead; l.pTail->pNext = new_ele; l.pHead = new_ele;
}
}
99
AddTail(LIST &l, NODE *new_ele)
Danh saùch lieân keát voøng Theâm phaàn töû cuoái xaâu void {
if(l.pHead == NULL) //Xaâu roãng {
l.pHead = l.pTail = new_ele; l.pTail->pNext = l.pHead;
} else {
new_ele->pNext = l.pHead; l.pTail->pNext = new_ele; l.pTail = new_ele;
}
100
}
Danh saùch lieân keát voøng Theâm phaàn töû sau nuùt q void AddAfter(LIST &l, NODE *q, NODE *new_ele) {
if(l.pHead == NULL) //Xaâu roãng {
l.pHead = l.pTail = new_ele; l.pTail->pNext = l.pHead;
} else {
new_ele->pNext = q->pNext; q->pNext = new_ele; if(q == l.pTail)
l.pTail = new_ele;
}
}
101
Danh saùch lieân keát voøng Huûy phaàn töû ñaàu xaâu
void RemoveHead(LIST &l) {
NODE *p = l.pHead; if(p == NULL) return; if (l.pHead = l.pTail) l.pHead = l.pTail = NULL; else {
l.pHead = p->Next; if(p == l.pTail)
l.pTail->pNext = l.pHead;
} delete p;
102
}
Danh saùch lieân keát voøng Huûy phaàn töû ñöùng sau nuùt q
void RemoveAfter(LIST &l, NODE *q) { NODE *p;
if(q != NULL) {
p = q ->Next ; if ( p == q) l.pHead = l.pTail = NULL; else {
q->Next = p->Next; if(p == l.pTail) l.pTail = q;
} delete p;
103
} }
So sánh Mảng và DSLK
104
So sánh DSLK và Mảng
105
So sánh DSLK đơn và kép
106
So sánh DSLK tuyến tính và vòng
107
Các cấu trúc đặc biệt của danh sách đơn
Stack (ngăn xếp): Là 1 vật chứa các đối tượng làm việc theo cơ chế LIFO (Last In First Out), từ việc thêm 1 đối tượng vào Stack hoặc lấy 1 đối tượng ra khỏi Stack được thực hiện theo cơ chế “vào sau ra trước”
Queue (hàng đợi): Là 1 vật chứa các đối tượng làm việc theo cơ chế FIFO (First In First Out), tức việc thêm 1 đối tượng vào hàng đợi hay lấy 1 đối tượng ra khỏi hàng đợi thực hiện theo cơ chế “vào trước ra trước”.
108
Ngăn xếp (Stack)
Một số ví dụ
109
Ngăn xếp (Stack)
110
Định nghĩa
111
Các thao tác trên Stack
Push(o): Thêm đối tượng o vào Stack
Pop(): Lấy đối tượng từ Stack
isEmpty(): Kiểm tra Stack có rỗng hay không
Top(): Trả về giá trị của phần tử nằm đầu Stack
mà không hủy nó khỏi Stack.
112
Minh họa thao tác Push
113
Minh họa thao tác Pop
114
Minh họa thao tác Stack Top
115
Cài đặt Stack
Dùng mảng 1 chiều
Data S [N]; int
t;
Dùng danh sách liên kết đơn S
4
6
5
1
8
2
List S
116
Thêm và hủy cùng phía
Cài Stack bằng mảng 1 chiều
Cấu trúc dữ liệu của Stack
int a[max]; int t; }Stack;
Khởi tạo Stack:
typedef struct tagStack {
void CreateStack(Stack &s) {
s.t=-1;
}
117
Kiểm tra tính rỗng và đầy của Stack
if(s.t==-1)
int IsEmpty(Stack s)//Stack có rỗng hay không {
return 1;
else
} int IsFull(Stack s) //Kiểm tra Stack có đầy hay không {
if(s.t>=max) return 1;
return 0;
else
return 0;}
118
Thêm 1 phần tử vào Stack
int Push(Stack &s, int x) {
if(IsFull(s)==0) {
s.t++; s.a[s.t]=x; return 1;
} else
return 0;
}
119
Lấy 1 phần tử từ Stack
int Pop(Stack &s, int &x) {
if(IsEmpty(s)==0) {
x=s.a[s.t]; s.t--; return 1;
} else
return 0;
120
}
Stack (hàm main)
void main() {
Stack s; int x, tv,i; CreateStack(s); for(i=2;i<=5;i++)
Push(s,i);
tv=Pop(s,x); if(tv==1) printf(“gia tri lay duoc tu Stack
%d”, x);
}
121
Cài Stack bằng danh sách liên kết
Kiểm tra tính rỗng của Stack
int IsEmpty(List &s) {
if(s.pHead==NULL)//Stack rong
return 1;
else
return 0;
}
122
Thêm 1 phần tử vào Stack
void Push(List &s, Node *Tam) {
if(s.pHead==NULL) {
s.pHead=Tam; s.pTail=Tam;
} else {
Tam->pNext=s.pHead; s.pHead=Tam;
}
}
123
Lấy 1 phần tử từ Stack
int Pop(List &s,int &trave) { Node *p;
if(IsEmpty(s)!=1) {
p=s.pHead; trave=p->Info; s.pHead=s.pHead->Next; if(s.pHead==NULL)
s.pTail=NULL;
delete p; return 1;
} return 0;
124
}
Stack (hàm main)
void main() {
Stack s; int tv,i; Node *p; CreateStack(s); for(i=2;i<=5;i++) {
p=CreateNode(i); Push(s,p);
} tv=Pop(s,x); if(tv==1)
printf(“gia tri lay duoc tu Stack %d”, x);
}
125
Ứng dụng của Stack
126
ÖÙng duïng cuûa Stack
Stack thích hôïp löu tröõ caùc loaïi döõ lieäu maø trình töï truy xuaát ngöôïc vôùi trình töï löu tröõ
Moät soá öùng duïng cuûa Stack:
◦ Trong trình bieân dòch (thoâng dòch), khi thöïc hieän
◦ Löu döõ lieäu khi giaûi moät soá baøi toaùn cuûa lyù thuyeát
caùc thuû tuïc, Stack ñöôïc söû duïng ñeå löu moâi tröôøng cuûa caùc thuû tuïc.
ñoà thò (nhö tìm ñöôøng ñi)
◦ Khöû ñeä qui ◦ ...
127
ÖÙng duïng cuûa Stack
Ví du:ï thuû tuïc Quick_Sort duøng Stack ñeå khöû ñeä qui: 1. l:=1; r:=n; 2. Choïn phaàn töû giöõa x:=a[(l+r) div 2]; 3. Phaân hoaïch (l,r) thaønh (l1,r1) vaø (l2,r2) baèng caùch
xeùt: ◦ y thuoäc (l1,r1) neáu yx; ◦ y thuoäc (l2,r2) ngöôïc laïi;
128
ÖÙng duïng cuûa Stack
4. Neáu phaân hoaïch (l2,r2) coù nhieàu hôn 1 phaàn töû thöïc
l=l1; r=r1; Goto 2; ◦ Ngöôïc laïi
Laáy (l,r) ra khoûi Stack neáu Stack khaùc roãng vaø Goto 2; Neáu khoâng döøng;
hieän: ◦ Caát (l2,r2) vaøo Stack; ◦ Neáu (l1,r1) coù nhieàu hôn 1 phaàn töû thöïc hieän:
129
Đổi cơ số (10 sang x)
57 = ???2
130
16/03/2015 Nhập môn CTDL và Thuật toán
130
Đổi cơ số
2
57 = 1110012
57 1 28 2
0 14 2
0 7 2
1 3 2
1 1 2
1 0
131
16/03/2015 Nhập môn CTDL và Thuật toán
131
Đổi cơ số void main() {
stack s; int coso, so, sodu; init(s); while (so != 0)
{
sodu = so % coso; push(s, sodu); // push so du vao stack so = so / coso;
} printf(“Kết quả: "); while(!empty(s))
cout< } 16/03/2015 Nhập môn CTDL và Thuật toán 132 Thuật toán Ba Lan ngược
(Reverse Polish Notation - RPN) Định nghĩa RPN :
Biểu thức toán học trong đó các toán tử
được viết sau toán hạng và không dùng dấu
ngoặc Phát minh bởi Jan Lukasiewics một nhà
khoa học Ba Lan vào nhưng năm 1950 16/03/2015 Nhập môn CTDL và Thuật toán 133 toán tử viết giữa toán hạng
toán tử viết sau toán hạng
toán tử viết trước toán hạng Infix
Postfix (RPN):
:
Prefix 16/03/2015 Nhập môn CTDL và Thuật toán 134 Thuật toán tính giá trị
1. Khởi tạo Stack rỗng (chứa hằng hoặc biến).
2. Lặp cho đến khi kết thúc biểu thức: Đọc 01 phần tử của biếu thức (hằng, biến, phép toán).
Nếu phần tử là hằng hay biến: đưa vào Stack.
Ngược lại: Lấy ra 02 phần tử của Stack.
Áp dụng phép toán cho 02 phần tử vừa lấy ra.
Đưa kết quả vào Stack.
3. Giá trị của biểu thức chính là phần tử cuối cùng
của Stack. 16/03/2015 Nhập môn CTDL và Thuật toán 135 Example: 2 3 4 + 5 6 - - * 2*((3+4)-(5-6)) Push 2
Push 3
Push 4
Read + Pop 4, Pop 3, Push 7
Push 5
Push 6
Read - Pop 6, Pop 5, Push -1
Read - Pop -1, Pop 7, Push 8
Read * Pop 8, Pop 2, Push 16 16/03/2015 Nhập môn CTDL và Thuật toán 136 Chuyển infix thành postfix 1. Khởi tạo Stack rỗng (chứa các phép toán).
2. Lặp cho đến khi kết thúc biểu thức: Đọc 01 phần tử của biếu thức (01 phần tử có thể là hằng, biến,phép toán, “)” hay “(” ). Nếu phần tử là:
2.1 “(”: đưa vào Stack.
2.2 “)”: lấy các phần tử của Stack ra cho đến khi gặp “(” trong Stack. 16/03/2015 Nhập môn CTDL và Thuật toán 137 Chuyển infix thành postfix
2.3 Một phép toán: + - * / Nếu Stack rỗng: đưa vào Stack.
Nếu Stack khác rỗng và phép
toán có độ ưu tiên cao hơn phần tử ở đầu Stack: đưa vào Stack.
Nếu Stack khác rỗng và phép toán có độ ưu tiên thấp hơn hoặc
bằng phần tử ở đầu Stack: - lấy phần tử từ Stack ra;
- sau đó lặp lại việc so sánh với
phần tử ở đầu Stack. 16/03/2015 Nhập môn CTDL và Thuật toán 138 Chuyển infix thành postfix 2.4 Hằng hoặc biến: đưa vào kết quả.
3. Lấy hết tất cả các phần tử của Stack ra. 16/03/2015 Nhập môn CTDL và Thuật toán 139 Độ ưu tiên + , _
*, /
^ 1
2
3 16/03/2015 Nhập môn CTDL và Thuật toán 140 Pop *, Display *,
Pop +, Display +, Pop ( Push /
Push (
Display D
Push -
Push (
Display E
Push -
Display F
Read ) Pop -, Display -, Pop ( Read ) Pop -, Display -, Pop ( Pop /, Display / 16/03/2015 Nhập môn CTDL và Thuật toán 141 Hàng đợi (Queue) Các thao tác trên Queue EnQueue(O): Thêm đối tượng O vào cuối hàng đợi. DeQueue(): Lấy đối tượng ở đầu hàng đợi
isEmpty(): Kiểm tra xem hàng đợi có rỗng hay không? Front(): Trả về giá trị của phần tử nằm đầu hàng đợi mà không hủy nó. Các thao tác trên Queue Thao tác EnQueue Thao tác DeQueue Thao tác QueueFront Thao tác QueueRear Cài đặt hàng đợi Cài đặt hàng đợi bằng con trỏ Cài đặt hàng đợi bằng mảng Cài đặt hàng đợi bằng mảng Cài đặt Queue • Dùng mảng 1 chiều • Dùng danh sách liên kết đơn * Thêm và hủy Khác phía Cài đặt Queue bằng mảng 1 chiều
Cấu trúc dữ liệu: typedef struct tagQueue
{ int a[max];
int Front; //chỉ số của phần tử đầu trong Queue
int Rear; //chỉ số của phần tử cuối trong Queue Khởi tạo Queue rỗng }Queue; void CreateQueue(Queue &q)
{ q.Front=-1;
q.Rear=-1;} Lấy 1 phần tử từ Queue
int DeQueue(Queue &q,int &x)
{ //queue khong rong if(q.Front!=-1)
{ x=q.a[q.Front];
q.Front++;
if(q.Front>q.Rear)//truong hop co mot phan tu
{ q.Front=-1;
q.Rear=-1; }
return 1; }
else //queue trong
{ printf("Queue rong");
return 0; }} Thêm 1 phần tử vào Queue int EnQueue(Queue &q,int x)
{ int i; int f,r;
if(q.Rear-q.Front+1==max)//queue bi day khong the
them vao duoc nua return 0; else
{ q.Front=0;
q.Rear=-1; if(q.Front==-1)
{ } Thêm 1 phần tử vào Queue (tt) f=q.Front;
r=q.Rear;
for(i=f;i<=r;i++) if(q.Rear==max-1)//Queue đầy ảo
{ q.Front=0;
q.Rear=r-f; q.a[i-f]=q.a[i]; }
q.Rear++;
q.a[q.Rear] = x;
return 1; } } Queue (hàm main())
void main()
{ Queue q;
int tv,i,x;
CreateQueue(q);
for(i=2;i<=5;i++) EnQueue(q,i); tv=DeQueue(q,x);
if(tv==1) printf(“gia tri lay duoc tu Stack %d”,x); } Cài đặt Queue bằng List Kiểm tra Queue có rỗng? int IsEmpty(List &Q)
{ if(Q.pHead==NULL)//Queue rỗng return 1; else return 0; } Thêm 1 phần tử vào Queue void EnQueue(List &Q, Node *Tam)
{ if(Q.pHead==NULL)
{ }
else
{ Q.pHead=Tam;
Q.pTail=Tam; Q.pTail->Next=tam;
Q.pTail=tam; } } Lấy 1 phần tử từ Queue int DeQueue(List &Q,int &X)
{ Node *p; if(IsEmpty(Q)!=1)
{ p=Q.pHead;
X=p->Info;
Q.pHead=Q.pHead->Next;
if(Q.pHead==NULL)
Q.pTail=NULL; delete p;
return 1; }
return 0; } Queue (hàm main()) void main()
{ Queue q;
int tv,i; Node *p;
CreateQueue(q);
for(i=2;i<=5;i++)
{ }
tv=DeQueue(q,x);
if(tv==1) printf(“gia tri lay duoc tu Queue %d”, x); p=CreateNode(i);
EnQueue(q,p); } Ứng dụng của hàng đợi Ứng dụng của hàng đợi Haøng ñôïi coù theå ñöôïc söû duïng trong moät soá baøi toaùn:
◦ Baøi toaùn ‘saûn xuaát vaø tieâu thuï’ (öùng duïng trong caùc heä ñieàu haønh song song). ◦ Boä ñeäm (ví duï: Nhaán phím Boä ñeäm CPU xöû lyù). ◦ Xöû lyù caùc leänh trong maùy tính (öùng duïng trong HÑH, trình bieân dòch), haøng ñôïi caùc tieán trình chôø
ñöôïc xöû lyù, ….132
133
RPN
:
RPN (POSTFIX)
PREFIX
+ A B
Examples:
INFIX
A + B
A * B + C
A * (B + C)
A - (B - (C - D))
A - B - C - D
A B +
A B * C +
A B C + *
A B C D - - -
A B - C - D -
+ * A B C
* A + B C
- A - B - C D
- - - A B C D
134
135
4
3
2
3 + 4 = 7
6
5
7
2
5 - 6 = -1
-1
7
2
7 - -1 = 8
8
2
2 * 8 = 16
16
136
137
138
139
140
(A+B*C)/(D-(E-F))
(A+B*C)/(D-(E-F))
(A+B*C)/(D-(E-F))
(A+B*C)/(D-(E-F))
(A+B*C)/(D-(E-F))
(A+B*C)/(D-(E-F))
(A+B*C)/(D-(E-F))
(A+B*C)/(D-(E-F))
(A+B*C)/(D-(E-F))
(A+B*C)/(D-(E-F))
(A+B*C)/(D-(E-F))
(A+B*C)/(D-(E-F))
(A+B*C)/(D-(E-F))
(A+B*C)/(D-(E-F))
(A+B*C)/(D-(E-F))
(A+B*C)/(D-(E-F))
(A+B*C)/(D-(E-F))
(A+B*C)/(D-(E-F))
Output
A
AB
*
+
(
ABC
Example:
Push (
Display A
Push +
Display B
Push *
Display C
Read )
+
(
(
ABC*
ABC*+
ABC*+D
ABC*+DE
-
(
-
(
/
ABC*+DEF
ABC*+DEF-
-
(
/
/
ABC*+DEF--
ABC*+DEF--/
141
142
143
144
145
146
147
148
149
150
151
152
Data S [N];
int
f,r;
4
6
5
1
8
2
List Q
pHead
pTail
153
154
155
156
157
158
159
160
161
162
163
164

