Professional Documents
Culture Documents
CHƯƠNG 3
DANH SÁCH
Danh sách là một dãy hữu hạn các phần tử cùng loại được sắp xếp theo một thứ
tự tuyến tính.
Danh sách L gồm các phần tử a1, a2,..., an được kí hiệu: L = (a1, a2,..., an).
Trong đó:
Một tính chất quan trọng của danh sách là các phần tử được sắp xếp tuyến tính
theo vị trí của chúng trong danh sách. Với n>1, i = 1, 2,..., n-1, phần tử ai là phần tử
ngay trước phần tử ai+1 và ai+1 là phần tử ngay sau ai.
Cho L = (a1, a2,..., an) là một danh sách và i, j là các vị trí trong danh sách
(1ijn). Danh sách L’ = (b1, b2,..., bj-1+1) trong đó b1 = ai, b2 = ai+1,..., bj-i+1 được
gọi là danh sách con của danh sách L.
Dãy con
Danh sách L’ được tạo thành từ danh sách L bằng cách bỏ đi một số phần tử
của danh sách L nhưng vẫn giữ nguyên thứ tự các phần tử được gọi là dãy con của
danh sách L.
2
Ví dụ:
L = (3, 1, 7, 5, 3, 4, 7, 5).
Trong thực tế có rất nhiều dữ liệu được tổ chức dưới dạng danh sách, như: danh
sách sinh viên, danh sách môn học, danh bạ điện thoại,...
Tùy thuộc từng loại danh sách sẽ có các thao tác đặc trưng riêng, bên cạnh đó,
trên các danh sách thường thực hiện các thao tác cơ bản sau:
- ...
3.1.2. Biểu diễn danh sách tuyến tính trên máy tính
Việc cài đặt một danh sách trong máy tính là tìm một cấu trúc dữ liệu cụ thể
mà máy tính hiểu được để lưu trữ các phần tử của danh sách, đồng thời viết các đoạn
chương trình con mô tả các thao tác cần thiết đối với danh sách đó.
Có 2 phương pháp biểu diễn danh sách tuyến tính trên máy tính, đó là:
- Mảng
Mảng là một cấu trúc dữ liệu cơ bản, thường dùng và được các ngôn ngữ lập
trình cấp cao hỗ trợ. Mảng là một dãy cố định các ô nhớ chứa các phần tử cùng kiểu.
Mỗi ô nhớ của mảng được xác định bởi chỉ số.
Với cách cài đặt này, ta phải ước lượng số phần tử của danh sách để khai báo
số phần tử của mảng cho thích hợp. Dễ thấy rằng số phần tử của mảng phải được
khai báo không ít hơn số phần tử của danh sách.
Cho danh sách L = (a1, a2,..., an) với mỗi phần tử ai có kiểu ElementType. Tổ
chức dữ liệu kiểu mảng để lưu danh sách L gồm 2 thành phần:
- Thành phần element: là mảng lưu các phần tử của danh sách.
- Thành phần count: là vị trí của ô nhớ lưu phần tử cuối cùng của danh sách,
cũng là số phần tử hiện có của danh sách.
1 a1
Các ô nhớ trống Các phần tử của danh sách
2 a2
... ...
i ai
... ...
count an
.
.
.
MaxLength .
Chỉ số Mảng
typedef elementType ...; //Định nghĩa kiểu phần tử của danh sách
4
struct ListArr {
elementType element[MaxLength];
int count;
};
ListArr L;
Ví dụ: Khai báo danh sách sinh viên gồm các thông tin: mã SV, họ tên, lớp.
const int MaxLength = 50;
typedef struct student{ //Định nghĩa kiểu phần tử của danh sách
};
student element[MaxLength];
int count;
};
Danh sách rỗng có độ dài bằng 0. Biến count chỉ vị trí cuối cùng, cũng là độ
dài của danh sách, vì vậy, để khởi tạo danh sách rỗng ta gán count = 0.
void InitList(L) {
L.count = 0;
}
5
Lấy giá trị phần tử tại vị trí p trong danh sách: Value(p,L)
Trường hợp p<1 hoặc p>count báo “Lỗi: Không có vị trí này trong danh sách”.
Giá trị của phần tử tại vị trí p là element[p].
elementType Value(p,L) {
if ((p<1)||(p>L.count))
else
return L.element[p];
Thêm một phần tử có giá trị x tại vị trí p của danh sách: InsertList (x,p,L)
Các khả năng có thể xảy ra khi thêm một phần tử vào danh sách:
- p<1, p>count+1: vị trí không tồn tại trong danh sách, không thực hiện được.
o Dời các phần tử từ vị trí p tới cuối danh sách xuống 1 vị trí.
void InsertList(x,p,L) {
else {
L.count = L.count + 1;
L.element[i] = L.element[i-1];
L.element[p] = x;
}
6
Xóa một phần tử tại vị trí p của danh sách: DeleteList (p,L)
Các khả năng có thể xảy ra khi xóa một phần tử trong danh sách:
- p<1, p>count: vị trí không tồn tại trong danh sách, không thực hiện được.
o Dời các phần tử từ vị trí p tới cuối danh sách lên 1 vị trí.
void DeleteList(p,L) {
if ((p<1)||(p>count))
else {
L.count = L.count - 1;
Định vị một phần tử trong danh sách có giá trị x: Locate (x,L)
Trả về 0 nếu không có phần tử nào có giá trị x. Trả về vị trí p của phần tử đầu
tiên trong danh sách có giá trị x.
int Locate(x,L) {
p = 1;
else return 0;
}
7
Ví dụ: Với danh sách sinh viên xây dựng ở trên, viết các chương trình con thực
hiện các yêu cầu sau:
1. Cài đặt các thao tác cơ bản trên danh sách sinh viên.
5. Hiển thị ra màn hình danh sách sinh viên của lớp mclass.
Hướng dẫn:
const int MaxLength = 50;
typedef
struct student{
char id[10];
char name[50];
char class[8];
};
struct ListST{
student element[MaxLength];
int count;
};
ListST L;
//Hiển thị thông tin của một sinh viên ra màn hình
void PrintStudent(s) {
printf(“\n %s,%s,%s”,s.id,s.name,s.class);
void PrintList(L) {
PrintStudent(L.element[p]);
8
}
int LocateID(mid,L) {
p = 1;
if (p<=L.count) return p;
else return 0;
void DeleteID(mid,L) {
p = LocateID(mid,L);
if (p==0)
else {
L.count = L.count - 1;
L.element[i] = L.element[i+1];
//Lọc danh sách sinh viên của lớp mclass vào danh sách L1
void FilterClass(mclass,L1,L) {
InitList(L1); q = 1;
if(strcmp(L.element[p].class,mclass)!=0) {
L1.element[q] = L.element[p];
q++;
L1.count = q – 1;
}
9
//Hiển thị danh sách sinh viên của lớp mclass
void PrintClass(mclass,L) {
FilterClass(mclass,L1,L);
PrintList(L1);
Yêu cầu về nhà: Sử dụng ngôn ngữ lập trình C cài đặt hoàn chỉnh danh sách
sinh viên và các thao tác cơ bản trên danh sách sinh viên.
Tổ chức lưu trữ móc nối (con trỏ tự trỏ) được thực hiện theo nguyên tắc:
- Mỗi phần tử của danh sách được lưu trữ trong một biến động (biến con trỏ),
còn gọi là node.
- Mỗi node có nhiều trường để lưu trữ thông tin (dữ liệu), ta gọi chung là
thành phần info.
- Mỗi node có một trường kiểu con trỏ lưu trữ địa chỉ của nút kế tiếp nó (nút
liền sau nó), ta gọi là thành phần next.
- Để cho đơn giản và thuận tiện trong biểu diễn giải thuật, ta nói nút có 2
trường là info và next. Thực tế, thành phần info có thể có nhiều trường mang
nhiều dữ liệu khác nhau.
3.3.2. Định nghĩa và sơ đồ cấu tạo danh sách liên kết đơn
Danh sách liên kết đơn là tập hợp các node, mỗi node là một biến động có hai
thành phần được kí hiệu là info và next. Thành phần info dùng để lưu thông tin (dữ
liệu) , thành phần next là trường kiểu con trỏ lưa địa chỉ của node đứng liền sau nó
(còn gọi là con trỏ trỏ đến node liền sau nó).
10
Node đầu tiên được quản lý bởi con trỏ F và node cuối cùng không có node
đứng liền sau nên giá trị trường next là NULL. Khi đó danh sách được gọi tên là
danh sách liên kết đơn L.
Quy ước danh sách F không có phần tử nào là danh sách rỗng, khi đó F =
NULL.
F Minh họa: p
NULL
Vì mỗi node là một biến động nên ta có thể xin cấp phát khi cần và giải phóng
khi không cần đến nó.
typedef elementType...;
elementType info;
element *next;
List F;
Kiểu bản ghi element gồm hai thành phần info và next. Thành phần info có thể
có nhiều hơn một trường dữ liệu lưu trữ nhiều thông tin khác nhau. Thành phần next
là một con trỏ kiểu element.
Biến con trỏ F luôn luôn trỏ tới phần tử đầu tiên trong danh sách liên kết.
Ví dụ: Cài đặt danh sách liên kết quản lý sinh viên. Thành phần info gồm có 3
trường: id, name, class.
};
List F;
3.3.4. Các thao tác cơ bản trên danh sách liên kết đơn
F = NULL;
List p;
p = F;
while (p!=NULL) {
printf((*p).info); p = (*p).next;
Thêm một node có giá trị x vào đầu danh sách: InsertFirst (x,F)
void InsertFirst(x,F) {
List q;
F = q;
Thêm một node có giá trị x vào sau nút trỏ bởi p: Insert (x,p,F)
void Insert(x,p,F) {
List q;
q = new element;
12
(*q).info = x;
(*q).next = (*p).next;
(*p).next = q;
Xóa một phần tử tại vị trí p của danh sách: DeleteList (p,F)
Chỉ thực hiện xóa khi danh sách khác rỗng (F!=NULL) và nút p có ở trong danh
sách.
- p là nút đầu danh sách (p==F): thay đổi vị trí nút đầu danh sách F =
(*F).next.
- p không phải là nút đầu: tìm tới nút q là nút trước nút p và thực hiện phép
gán (*q).next = (*p).next.
List q;
if (F!=NULL) {
if(F==p) F = (*F).next;
else{
q = F;
while((q!=NULL)&&((*q).next!=p)) q = (*q).next;
delete p;
int c = 0;
List p;
13
p = F;
while(p!=NULL) {
c++;
p = (*p).next;
return c;
Hàm trả về địa chỉ nút đầu tiên trong danh sách có giá trị x. Nếu không tìm thấy
hàm trả về NULL
List Locate(x,F) {
List p;
p = F;
while((p!=NULL)&&((*p).info!=x)) p = (*p).next;
return p;
Thêm một nút có giá trị x vào danh sách đã sắp xếp: InsertSort (x,F)
Giả sử danh sách F sắp xếp theo thứ tự tăng dần của trường info.
Tìm vị trí nút đầu tiên có giá trị lớn hơn hoặc bằng x (nút q) và nút trước nút q
(nút p).
List p,q;
q = F;
while((q!=NULL)&&(*q).info<x) {
p = q; q = (*q).next;
}
14
if(q==F) InsertFirst(x,F);
else Insert(x,p,F);
3.3.5. Ví dụ
Ví dụ 1. Cài đặt danh sách liên kết quản lý sinh viên. Thành phần info gồm
có 3 trường: id, name, class và các thao tác sau:
student *next;
};
List F;
List LocateID(mid,F) {
List p;
p = F;
while((p!=NULL)&&(strcmp((*p).id,mid)!=0)) p = (*p).next;
return p;
List q,r;
15
r = F;
while((r!=NULL)&&(r!=p)) {
q = r; r = (*r).next;
if(r!=NULL) {
if(F==p) F = (*F).next;
delete p;
List p;
p = LocateID(mid,F)
DeleteList(p,F);
List p;
p = F;
while (p!=NULL) {
PrintStudent(p);
p = (*p).next;
void PrintClass(mclass,F) {
16
List p;
p = F;
while(p!=NULL) {
if(strcmp((*p).class,mclass)==0)) PrintStudent(p);
p = (*p).next;
int Count(F) {
int c = 0;
List p;
p = F;
while(p!=NULL) {
c++;
p = (*p).next;
return c;
Yêu cầu về nhà: Sử dụng ngôn ngữ lập trình C cài đặt hoàn chỉnh danh sách
sinh viên và các thao tác cơ bản trên danh sách sinh viên.
Cho đa thức P(x) = anxn+ an-1xn-1+...+ a1x + a0. Xây dựng cấu trúc dữ liệu thích
hợp để biểu diễn đa thức và cài đặt một số thao tác xử lý trên đa thức như sau:
Hướng dẫn:
17
Lưu trữ đa thức dưới dạng một danh sách liên kết đơn có nút đầu danh sách trỏ
bởi P, mỗi nút của danh sách lưu một đơn thức (chỉ lưu các đơn thức có hệ số khác
0), có 3 trường như sau:
Trong đó:
- Biến sum = 0
- Duyệt qua các nút của đa thức và thực hiện các phép tính: sum = sum +
coef*pow(x,exp).
Các đơn thức trong các nút được lưu theo dạng lũy thừa lùi (đơn thức có bậc
giảm dần). Giả sử đã có 2 đa thức P(x) và Q(x) được lưu trong hai danh sách móc
nối đơn P và Q theo cách trên, đa thức tổng R(x) = P(x) + Q(x) và đa thức tổng được
lưu trong danh sách móc nối đơn R.
Chẳng hạn:
- Chừng nào p và q còn khác NULL thì thực hiện một trong 3 cách sau:
18
o Nếu bậc của nút trỏ bởi p lớn hơn bậc của nút trỏ bởi q
((*p).exp>(*q).exp) thì: bổ sung một nút mới vào cuối danh sách R,
chép dữ liệu từ nút trỏ bởi p vào nút này, di chuyển p tới nút tiếp theo.
o Nếu bậc của nút trỏ bởi p nhỏ hơn bậc của nút trỏ bởi q
((*p).exp<(*q).exp) thì: bổ sung một nút mới vào cuối danh sách R,
chép dữ liệu từ nút trỏ bởi q vào nút này, di chuyển q tới nút tiếp theo.
o Nếu bậc của nút trỏ bởi p bằng hơn bậc của nút trỏ bởi q
((*p).exp=(*q).exp) thì: tính tổng hệ số trên nút trỏ bởi p và q, nếu
tổng này khác không thì bổ sung một nút mới vào cuối danh sách R
với hệ số là tổng hệ số và bậc là bậc của nút p (hoặc q). Di chuyển p
và q tới nút tiếp theo.
- Cuối cùng, nếu đa thức nào còn đơn thức chưa xử lý thì sao chép chúng vào
cuối danh sách R.
double coef;
int exp;
polynomical *next;
};
List P, Q, R;
void Insert(c,e,p,P) {
List q;
q = new polynomical;
(*q).coef = c; (*q).exp = e;
}
19
//Tính P(x) khi biết x
double Px(x,P) {
double sum = 0;
List p;
p = P;
while(p!=NULL) {
p = (*p).next;
return sum;
List Totalize(P,Q) {
List p,q,r;
double c = 0;
int e;
R = NULL; p = P; q = Q; r = R;
while((p!=NULL)||(q!=NULL)) {
if((*p).exp>(*q).exp) {
c = (*p).coef; e = (*p).exp;
p = (*p).next;
}else if((*p).exp<(*q).exp) {
c = (*q).coef; e = (*q).exp;
q = (*q).next;
p = (*p).next; q = (*q).next;
if(c!=0) {
Insert(c,e,r,R);
20
if(r==NULL) r = R; else r = (*r).next;
while(p!=NULL) {
Insert((*p).coef,(*p).exp,r,R);
while(q!=NULL) {
Insert((*q).coef,(*q).exp,r,R);
return R;
Từ một danh sách móc nối đơn F ta cải tiến lại như sau: trường next của nút
cuối cùng đang trỏ tới NULL bây giờ cho lưu địa chỉ của nút đầu tiên của danh sách
(nút trỏ bởi F). Danh sách được cải tiến như vậy được gọi là danh sách nối vòng.
Dưới đây là các giải thuật cơ bản trên danh sách móc nối vòng:
Danh sách chỉ được hiển thị khi khác NULL. Hiển thị danh sách cho tới nút
cuối cùng (có trường next == F).
void PrintList(F) {
List p;
21
if(F!=NULL) {
p = F;
do{
printf((*p).info);
p = (*p).next;
} while (p != F)
Thêm một node có giá trị x vào đầu danh sách: InsertFirst (x,F)
void InsertFirst(x,F) {
List q,p;
q = new element;
(*q).info = x;
if(F==NULL) {
F = q;
(*q).next = F;
}else{ p = F
while((*p).next!=F) p = (*p).next;
(*p).next = q;
(*q).next = F;
F = q;
Thêm một node có giá trị x vào sau nút trỏ bởi p: Insert (x,p,F)
void Insert(x,p,F) {
List q;
q = new element;
}
22
Xóa một phần tử tại vị trí p của danh sách: DeleteList (p,F)
Chỉ thực hiện xóa khi danh sách khác rỗng (F!=NULL) và nút p có ở trong danh
sách.
o F có hơn 1 nút: thay đổi vị trí nút đầu danh sách F = (*F).next và nút
cuối danh sách trỏ tới F mới.
- p không phải là nút đầu: tìm tới nút q là nút trước nút p và thực hiện phép
gán (*q).next = (*p).next.
void DeleteList(p,F) {
List q;
if (F!=NULL) {
if(F==p)
if(F==(*F).next) F = NULL;
else{ q = F;
while((*q).next != F) q = (*q).next;
(*q).next = (*F).next;
F = (*F).next;
else{
q = F;
while(((*q).next!=F)&&((*q).next!=p)) q = (*q).next;
delete p;
}
23
Đếm số phần tử của danh sách: Count (F)
int Count(F) {
int c = 0;
List p;
p = F;
if(F != NULL){
do {
c++;
p = (*p).next;
return c;
Hàm trả về địa chỉ nút đầu tiên trong danh sách có giá trị x. Nếu không tìm thấy
hàm trả về NULL
List Locate(x,F) {
List p;
p = F;
if (F!=NULL)
do
if((*p).info!=x) p = (*p).next;
while((p!=L)&&((*p).info!=x));
return p;
Danh sách liên kết đôi là tập hợp các nút, mỗi nút là một biến động có tối thiểu
3 thành phần được kí hiệu là info, pre, next. Trong đó:
24
- info lưu trữ thông tin.
Nút đầu tiên được quản lý bởi con trỏ F (First), không có nút đứng trước: pre =
NULL.
Nút cuối cùng được quản lý bởi con trỏ L (Last), không có nút đứng sau: next
= NULL.
L
F
NULL
NULL
Cài đặt danh sách liên kết đôi và các thao tác trên danh sách liên kết đôi:
typedef elementType...;
struct element{
elementType info;
List F,L;
F = NULL; L = NULL;
List p;
p = F;
25
while (p!=NULL) {
printf((*p).info);
p = (*p).next;
Thêm một node có giá trị x vào đầu danh sách: InsertFirst (x,F,L)
void InsertFirst(x,F,L) {
List q;
q = new element;
(*q).info = x;
(*q).pre = NULL;
(*q).next = F;
(*F).pre = q;
F = q;
Thêm một node có giá trị x vào cuối danh sách: InsertLast (x,F,L)
void InsertLast(x,F,L) {
List q;
q = new element;
(*q).info = x;
(*q).next = NULL;
(*q).pre = L;
(*L).pre = q;
L = q;
Thêm một node có giá trị x vào sau nút trỏ bởi p: InsertFirst (x,p,F,L)
void Insert(x,p,F,L) {
List q;
q = new element;
26
(*q).info = x;
(*q).next = (*p).next;
(*q).pre = p;
(*(*p).next).pre = q;
(*p).next = q;
Xóa một phần tử tại vị trí p của danh sách: DeleteList (p,F,L)
Chỉ thực hiện xóa khi danh sách khác rỗng (F!=NULL)&&(L!=NULL) và nút
p có ở trong danh sách. Các trường hợp có thể xảy ra:
- p nằm giữa danh sách (có nút trước và nút sau p):
List q;
if ((F!=NULL)&&(L!=NULL)&&(p!=NULL)) {
if(F==p)
else if(p==L) {
else{
q = F;
while((q!=NULL)&&((*q).next!=p)) q = (*q).next;
27
if(q!=NULL) { //p có trong danh sách
(*q).next = (*p).next;
(*(*p).next).pre = q;s
delete p;
int c = 0;
List p;
p = F;
return c;
Hàm trả về địa chỉ nút đầu tiên trong danh sách có giá trị x. Nếu không tìm thấy
hàm trả về NULL
List Locate(x,F,L) {
List p;
p = F;
while((p!=NULL)&&((*p).info!=x)) p = (*p).next;
return p;
Hai trong các danh sách đặc biệt theo một quan điểm riêng về bổ sung và loại
bỏ ứng là ngăn xếp (stack) và hàng đợi (queue). Ngăn xếp được thực hiện bổ sung
và loại bỏ theo nguyên tắc vào sau ra trước (LIFO – Last In First Out). Hàng đợi
thực hiện bổ sung và loại bỏ theo nguyên tắc vào trước ra trước (FIFO – First In First
Out).
Như vậy, ngăn xếp và hàng đợi đều là cách danh sách tuyến tính mà trên đó
chúng ta tạo ra các cơ chế hoạt động riêng cho chúng trong việc bổ sung và loại bỏ.
3.5.1. Ngăn xếp (Stack) và các thao tác cơ bản trên các ngăn xếp
Stack là một cấu trúc dữ liệu trên đó đã được trang bị cấu trúc dữ liệu tuyến
tính, hoạt động theo nguyên tắc:
- Mỗi lần nộp vào (bổ sung) hoặc lấy ra (loại bỏ) chỉ thực hiện với một phần
tử.
- Phần tử nào nộp vào sau khi lấy ra sẽ được lấy ra trước.
Để đảm bảo cơ chế đó thì các phần tử của stack tốt nhất được tổ chức theo cấu
trúc danh sách tuyến tính. Vị trí nộp vào và lấy ra sẽ được chọn là vị trí đầu tiên
(hoặc vị trí cuối cùng) của danh sách, vị trí đó được gọi là đỉnh của stack, phía ngược
lại được gọi là đáy của stack.
Stack rỗng: khi không có phần tử nào lưu trữ trong stack.
Stack đầy: khi không còn chỗ nộp thêm phần tử mới vào stack.
Chúng ta có thể sử dụng danh sách đặc (mảng một chiều) hoặc danh sách liên
kết để tổ chức stack.
typedef
struct Stack {
elementType element[MaxLength+1];
int top;
};
Stack L;
- Kiểm tra xem stack có đầy không (top = MaxLength). Nếu không đầy thì
dịch chuyển top lên một vị trí và nộp x vào vị trí này.
void InsertStack(x,S) {
- Nếu stack khác rỗng (top>0) thì mới lấy. Sau khi lấy, đỉnh stack lùi lại một
vị trí.
elementType DeleteStack(S) {
* Tổ chức stack trên danh sách liên kết đơn (lưu trữ móc nối).
30
Dùng một danh sách móc nối đơn có nút đầu danh sách trỏ bởi S và tổ chức
stack trên đó. Ở đây ta chọn vị trí đầu tiên của danh sách là đỉnh và vị trí cuối cùng
của danh sách là đáy.
Stack đầy tùy thuộc vào bộ nhớ và cấu hình của máy, đứng về góc độ lý thuyết
ta giả sử không xảy ra hiện tượng stack đầy.
typedef elementType...;
struct element{
elementType info;
element *next;
Stack S;
Bổ sung một phần tử x vào stack: bổ sung một phần tử vào đầu danh sách
(InsertFirst)
void InsertStack(x,S){}
Lấy một phần tử ra khỏi stack: lấy phần tử đầu tiên trong danh sách
elementType DeleteStack(S) {
Stack p; elementType x;
else { p = S; x = (*p).info;
S = (*S).next;
delete p;
return x;
}
31
3.5.3. Hàng đợi (Queue) và các thao tác cơ bản trên các hàng đợi
Queue là một cấu trúc lưu trữ trên đó trang bị một cấu trúc dữ liệu tuyến tính,
hoạt động theo nguyên tắc:
- Mỗi lần nộp vào hoặc lấy ra chỉ thực hiện với một phần tử.
Vị trí nộp phần tử vào là vị trí đầu tiên (hoặc cuối cùng) và vị trí lấy ra sẽ là vị
trí cuối cùng (hoặc đầu tiên) của danh sách. Vị trí nộp phần tử vào được gọi là lối
vào (hay lối sau), vị trí lấy phần tử ra được gọi là lối ra (hay lối trước).
Queue rỗng khi không có phần tử nào trong Queue và Queue đầy khi không
còn chỗ để nộp phần tử vào.
Ta tổ chức Queue trên một danh sách móc nối đơn có nút đầu tiên trỏ bởi Q,
nút cuối cùng trỏ bởi R. Lối vào của Queue là vị trí cuối cùng (R), lối ra là vị trí đầu
tiên của danh sách (Q). Queue rỗng khi danh sách rỗng: Q=R=NULL, Queue đầy
tùy thuộc vào bộ nhớ và cấu hình máy, giả sử không xảy ra hiện tượng Queue đầy.
typedef elementType...;
struct element{
elementType info;
element *next;
Queue Q,R;
Bổ sung một phần tử x vào queue: bổ sung một phần tử vào cuối danh sách
void InsertQueue(x,Q,R) {
Queue p;
p = new element;
if(Q==NULL){Q = p; R = p;}
32
else {(*R).next = p; R = p;}
Lấy một phần tử ra khỏi queue: lấy phần tử đầu tiên trong danh sách
elementType Delete Queue(Q,R) {
Queue p; elementType x;
else { p = Q; x = (*p).info;
else Q = (*Q).next;
delete p;
return x;
}
33
BÀI TẬP CHƯƠNG 4
Bài 1. So sánh ưu điểm, nhược điểm của danh sách kề và danh sách móc nối.
Bài 2. Cho trước một danh sách móc nối đơn F, mỗi nút chứa một số nguyên,
danh sách này chưa có thứ tự. Viết giải thuật để tạo một danh sách móc nối đơn mới
từ FS từ F sao cho FS có thứ tự không giảm.
Bài 3. Cho một danh sách móc nối đơn F, mỗi nút chứa một số nguyên. Hãy
viết các giải thuật giải quyết các công việc sau:
2. Tìm tới nút thứ k trong danh sách, nếu có thì cho ra địa chỉ của nút đó, nếu
không có trả về NULL.
3. Bổ sung nút mới chứa số nguyên x vào sau nút thứ k, nếu không có nút thứ
k thì bổ sung vào sau cùng.
5. Cho một danh sách móc nối đơn có nút đầu trỏ bởi P. Hãy chèn danh sách P
vào sau nút trỏ bởi M trên danh sách F.
7. Nếu trong danh sách F còn có hai nút chứa số nguyên bằng nhau thì loại bỏ
bớt một nút.
8. Trên cơ sở danh sách đã xử lý ở câu 7, cài đặt các thuật toán sắp xếp sau trên
danh sách F:
f. Quick Sort
34
g. Merge Sort
Bài 4. Cài đặt lại bài 3 trên danh sách nối vòng và danh sách liên kết đôi.
Bài 5. Có một nhóm người đang đứng thành một vòng tròn, người ta cần chọn
ra một người đi dự tiệc theo nguyên tắc sau:
- Cho trước một số nguyên n và chọn ngẫu nhiên một người, giả sử người đó
tên A.
- Bắt đầu từ người A, theo chiều kim đồng hồ, bắt đầu đếm từ 1 đến n. Người
đếm thứ n được đưa ra khỏi vòng. Lặp lại quy trình đếm đó bắt đầu từ người
tiếp theo cho đến khi chỉ còn một người.
Hãy tổ chức cấu trúc dữ liệu phù hợp và viết các giải thuật giải quyết các công
việc sau:
b. Nếu có một người tên A rất muốn đi dự tiệc, hãy giúp A chỉ ra người chọn
đầu tiên sao cho A là người cuối cùng còn lại sau khi đã loại hết mọi người
khỏi vòng.
Bài 6. Một trung tâm đào tạo tin học cần quản lý việc học của học viên theo
chứng chỉ môn học. Trong mỗi kỳ, trung tâm lưu trữ thông tin về kết quả học tập
của các học viên trong một danh sách liên kết với nút đầu trỏ bởi F. Mỗi nút của
danh sách lưu các thông tin:
- dsMon: lưu địa chỉ nút đầu của một danh sách khác chứa các kết quả các
môn học mà học viên này đã đăng kí trong học kì đó (gọi là danh sách môn
học).
- down: con trỏ lưu địa chỉ nút tiếp theo trong danh sách học viên.
Mỗi nút trong danh sách môn học gồm các trường:
- next: con trỏ trỏ tới nút kế tiếp trong danh sách môn học.
Tổ chức dữ liệu và viết các giải thuật xử lý các công việc sau:
a. Tìm địa chỉ nút trong danh sách học viên F có hoten là ht.
b. Tìm địa chỉ nút trong danh sách môn học P có tenmh là mh.
c. Bổ sung một học viên có hoten là ht vào danh sách F và học viên này có
danh sách môn học ban đầu gồm 1 môn có tên là mh. Nếu học viên này đã
có thì bổ sung môn học có tên mh vào cuối danh sách môn học của học viên
này.
d. Cập nhật điểm thi dt của môn học mh cho học viên ht. Nếu không tìm thấy
học viên ht thì bổ sung học viên ht và môn học mh kèm số điểm dt. Nếu tìm
thấy học viên ht nhưng không tìm thấy môn học mh thì bổ sung môn học mh
kèm số điểm dt vào danh sách môn học của học viên này.
e. In danh sách học viên và điểm của từng môn kèm điểm trung bình (tổng
điểm chia cho số môn học).s