Professional Documents
Culture Documents
Báo Cáo CTDL - GT Nhóm 1
Báo Cáo CTDL - GT Nhóm 1
ĐỀ TÀI:
Hà Nội, 3/2023
HỌC VIỆN NGÂN HÀNG
ĐỀ TÀI:
Nhóm 1
Hà Nội, 3/2023
ĐÁNH GIÁ THÀNH VIÊN
Tỷ lệ
Mã sinh viên Họ tên Nhiệm vụ
đóng góp
24A4041418 Ngô Hồng Ngọc Làm slide, chỉnh sửa báo cáo 21%
Danh sách liên kết là một trong những cấu trúc dữ liệu cơ bản trong môn học cấu
trúc dữ liệu và giải thuật. Việc tìm hiểu về danh sách liên kết và ứng dụng trong quản lý
sinh viên sẽ giúp sinh viên hiểu rõ hơn về cấu trúc dữ liệu này và cách sử dụng nó trong
thực tế.
Tính ứng dụng cao: quản lý sinh viên là một trong những công việc quan trọng trong
các cơ sở giáo dục. Sử dụng danh sách liên kết để quản lý thông tin sinh viên có thể giúp
giảm thiểu thời gian và công sức, đồng thời cải thiện hiệu quả công việc.
Có nhiều thách thức và vấn đề cần giải quyết: trong quá trình quản lý sinh viên bằng
danh sách liên kết, sẽ có nhiều vấn đề cần giải quyết như tìm kiếm, thêm, xoá, cập nhật
thông tin sinh viên.
Có nhiều hướng phát triển tiềm năng: đề tài còn có nhiều hướng phát triển tiềm năng
như ứng dụng danh sách liên kết trong quản lý nhân sự, quản lý tài liệu, quản lý đơn hàng.
Việc tìm hiểu về danh sách liên kết và ứng dụng trong quản lý sinh viên cũng giúp sinh
viên mở rộng tầm nhìn và áp dụng kiến thức được học vào các lĩnh vực khác.
- Củng cố lại các kiến thức đã học về danh sách liên kết đơn.
- Bổ sung các khái niệm và giải thuật của danh sách liên kết đôi.
- Ứng dụng các lý thuyết vào giải quyết một bài toán cụ thể.
- Rèn luyện kỹ năng và tư duy lập trình với ngôn ngữ C.
- Các khái niệm, cấu trúc dữ liệu, các thao tác cơ bản và giải thuật của danh sách liên
kết đơn và đôi.
- Bài toán quản lý sinh viên với danh sách liên kết đơn và quản lý hóa đơn học phí
với danh sách liên kết đôi.
- Nghiên cứu slide bài giảng của giáo viên và các tài liệu khác có trên web.
- Từ đó viết chương trình chạy thử trên C và kiểm tra.
LỜI CẢM ƠN
Nhóm chúng em xin gửi lời cảm ơn chân thành tới cô Giang Thị Thu Huyền, người
đã hướng dẫn và hỗ trợ chúng em tận tình trong suốt quá trình môn học. Bên cạnh kiến
thức chuyên sâu của môn học, thì cô cũng truyền dạy cho chúng em cách tư duy, nhìn nhận
vấn đề cùng các kỹ năng ứng xử và thái độ trong học tập, làm việc nói riêng và cuộc sống
nói chung.
Chúng em rất biết ơn và trân trọng công lao của cô, và mong rằng sẽ được tiếp tục
học tập và phát triển dưới sự hướng dẫn của cô trong tương lai.
MỤC LỤC
CHƯƠNG I. TỔNG QUAN VỀ DANH SÁCH LIÊN KẾT ........................................... 1
CHƯƠNG IV. TƯƠNG QUAN GIỮA CÁC CẤU TRÚC DỮ LIỆU .......................... 24
1. Danh sách liên kết và mảng ......................................................................................... 24
REFERENCES ............................................................................................................... 29
CHƯƠNG I. TỔNG QUAN VỀ DANH SÁCH LIÊN KẾT
Danh sách liên kết là 1 cấu trúc dữ liệu được sử dụng để lưu trữ các tập dữ liệu.
Một danh sách liên kết đơn có những đặc tính như sau:
▪ 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
▪ Danh sách liên kết đôi: mỗi phần tử liên kết với phần tử đứng trước và sau nó
trong danh sách
▪ Danh sách liên kết vòng: phần tử cuối danh sách liên kết với phần tử đầu danh
sách
1
CHƯƠNG II. DANH SÁCH LIÊN KẾT ĐƠN
Trong DSLK đơn, mỗi phần tử được cấu tạo nên từ hai thành phần chính.
● Thành phần dữ liệu: lưu trữ thông tin về bản thân phần tử (Info)
● Thành phần liên kết: lưu trữ địa chỉ phần tử đứng sau. Nếu phần tử được xét đang
đứng cuối danh sách thì thành phần liên kết sẽ bằng NULL.
Một phần tử hoàn chỉnh được cấu thành từ data (dữ liệu) và pointer (liên kết) sẽ được gọi
là một nút (node).
Struct tagNode *Next; //Lưu địa chỉ của nút đứng sau
} Node;
Dưới đây là cài đặt cho 1 DSLK đơn của 1 tập các số nguyên:
Struct ListNode
int data;
};
Node *pHead; //Lưu địa chỉ nút đầu tiên trong danh sách
Node *pTail; //Lưu địa chỉ nút cuối cùng trong danh sách
} List;
2
3. Các thao tác với DSLK đơn
Xây dựng hàm createList() để gán các con trỏ head và tail trỏ về NULL.
Xây dựng hàm createNode() có tham số là giá trị của node muốn tạo.
Hàm createNode() có kiểu trả về là con trỏ node.
Node* CreateNode(Data x)
Node *p;
if ( p==NULL) exit(1);
p->pNext = NULL;
return p;
+ pHead = p;
+ pTail = pHead;
Trường hợp 2: Nếu danh sách liên kết đơn không rỗng thì:
3
o Cho con trỏ next của node mới trỏ vào node đầu tiên trong danh sách hiện tại.
o Cho con trỏ đầu của danh sách liên kết đơn (*head) trỏ vào node mới.
+ p->pNext = pHead;
+ pHead = p;
l.pHead = p;
l.pTail = l.pHead;
// Gán con trỏ next của node p bằng phần tử đang là nút đầu tiên của danh sách
p->pNext = l.pHead;
l.pHead = p;
Trường hợp 1: Nếu danh sách liên kết đơn rỗng thì node mới được xem là node đầu tiên
và cũng là node cuối cùng.
pHead = p;
pTail = pHead;
Trường hợp 2: Nếu danh sách liên kết đơn không rỗng thì:
o Cho con trỏ liên kết (next) của node cuối danh sách hiện tại trỏ đến đến node mới.
o Cho con trỏ cuối của danh sách liên kết đơn (*tail) trỏ vào node mới.
pTail->pNext=p;
pTail=p;
4
void AddTail (LIST &l, Node *p){
l.pTail = l.pHead;
l.pTail->Next = p; // Gán con trỏ của phần tử cuối trong ds bằng node p
Cho con trỏ liên kết (next) của node p chỉ vào node sau của q.
if(q != NULL) {
p->pNext=Q->Next; //Gán con trỏ next của node p bằng con trỏ next của code q
if(l.pTail==q)
l.Tail=q;
//Ngược lại không tồn tại node q thì chèn node p vào đâu
} else
5
3.4. Xóa nút trong DSLK
//tạo nút p
p = ds.pHead; ; //gán p bằng node pHead đầu tiên của danh sách
//xóa node p
delete p;
if(k->next == ds.pTail) //nếu duyệt đến phần tử pTail cuối trong danh sách
ds.pTail = k; //thay đổi lại phần tử cuối pTail của danh sách
6
Xóa nút giữa:
Node *p;
if (q != NULL) {
q->pNext=p->pNext; x=p->Info;
delete p;
return 1;
} else
return 0;
Node *p = l.pHead;
Node *q = NULL;
While (p != NULL)
q = p;
7
p = p ->pNext;
Else
Ta có Head là nút đầu của danh sách. Để duyệt danh sách, ta đi theo các bước sau:
p=p->Next;
Duyệt danh sách thường được dùng khi có các nhu cầu:
8
While ( current != NULL) { //duyệt ds tới cuối
count ++; //tăng biến đếm lên 1 đơn vị
current = current -> next; //cho con trỏ trỏ đến nút kế tiếp
}
return count; //trả về số lượng nút có trong ds
}
Hàm Search() có kiểu trả về là con trỏ node. Nếu tìm thấy x thì node được trả về
khác NULL, ngược lại node được trả về là NULL.
{
9
Node *p; //khởi tạo con trỏ p
//Trong khi p chưa là phần tử cuối và giá trị của p không trùng với x cần tìm
//vòng lặp thứ hai, cho q gán bằng nút sau nút p, trong khi q chưa là nút cuối, cho
q trỏ tới nút tiếp
if (p->info > q->info) //giá trị nút p > giá trị nút q thì hoán đổi giá trị 2 nút
p->info = q->info;
q->info = temp;
}
10
CHƯƠNG III. DANH SÁCH LIÊN KẾT ĐÔI
Danh sách liên kết đôi (Doubly linked list) là danh sách liên kết mà mỗi phần tử có hai
liên kết đến phần tử liền trước và liền sau nó.
Khi duyệt các nút sẽ thực hiện theo hai chiều về trước và về sau thay vì thực hiện duyệt
một chiều như danh sách liên kết đơn.
Ở mối liên kết tới Node khác, thay vì chỉ có mỗi pNext trỏ tới phần tử sau nó như DSLK
đơn, thì trong DSLK đôi cần có thêm pPrev để trỏ tới phần tử trước nó.
Tổ chức biểu diễn một Node của danh sách liên kết đôi.
struct Node {
int data;
};
11
Ta có:
● pHead là Node đầu tiên trong DSLK đôi, quản lý Node đầu.
● pTail là Node cuối cùng trong DSLK đôi, quản lý Node cuối.
● Node B có hai con trỏ, trỏ đến A và C, tương tự các Node khác cũng vậy.
struct SingleList
{
Node *pHead; //Node đầu pHead
Node *pTail; // Node cuối pTail
};
Đầu tiên ta sử dụng hàm CreateNode() để tạo một Node mới newNode.
Tiếp đến ta xét điều kiện, nếu danh sách rỗng thì newNode vừa là Node đầu và Node
cuối: head = newNode và tail = newNode. Nếu trong danh sách có phần tử thì ta thực
hiện chèn vào đầu danh sách bằng cách:
Thiết lập con trỏ prev của head trỏ đến newNode: head -> prev = newNode
12
Thiết lập con trỏ next của newNode trỏ đến head: newNode -> next = head
Tương tự như thêm Node vào đầu danh sách, ta sử dụng hàm CreateNode() để tạo một
Node mới newNode.
Sau đó cũng xét điều kiện, nếu danh sách rỗng thì newNode vừa là head vừa là tail. Nếu
danh sách có phần tử thì ta thực hiện:
● Thiết lập con trỏ next của tail trỏ đến newNode.
● Thiết lập con trỏ prev của newNode trỏ đến tail.
● Dịch chuyển tail về newNode.
if (l.pHead == NULL)
else
13
{
l.pTail = new_node;
DNode*p = q->pNext; //tạo con trỏ p để trỏ tới nút sau nút q
new_node -> pNext = p; //gán con trỏ next của nút mới bằng nút p
if (p!=NULL) p -> pPrev = new_node; //con trỏ prev của nút p trỏ tới nút mới
new_node -> pPrev = q; //con trỏ prev của nút p trỏ tới nút q
q -> pNext = new_node; //con trỏ next của nút q trỏ tới nút mới
}
14
Thuật toán chèn vào sau q:
DNode*p = q->pPrev; //khởi tạo con trỏ p bằng con trỏ prev của nút q
if (q != NULL)
new_node -> pNext =q; // con trỏ next của nút mới trỏ tới nút q
q -> pPrev = new_node; //con trỏ prev của nút q trỏ tới nút mới
new_node -> pPrev = p; //con trỏ prev của nút mới trỏ tới p
if (p != NULL)
p->pNext = new_node; //con trỏ next của nút p trỏ tới nút mới
if (q == l.pHead)
} else
15
3.4. Xóa nút trong DSLK đôi
Trước khi xóa Node đầu khỏi danh sách ta cần kiểm tra xem trong danh sách có phần tử
nào hay không. Nếu danh sách rỗng thì ta return rồi thoát khỏi hàm, ngược lại nếu danh
sách có phần tử thì ta thực hiện các bước sau:
● Dịch chuyển Node head đến Node kế tiếp: head = head -> next
● Cho con trỏ prev của head trỏ đến null: head -> prev = NULL
DNode*p = l.pHead;
delete p;
return 1;
Tương tự như xóa Node đầu trong danh sách, đầu tiên ta cần kiểm tra xem danh sách có
phần tử hay không. Nếu không thì ta return rồi thoát khỏi hàm, ngược lại nếu danh sách
có phần tử thì ta thực hiện các bước sau:
16
● Dịch chuyển Node tail về Node trước đó: tail = tail -> prev
● Cho con trỏ next của tail trỏ về null: tail -> next = NULL
l.pTail->pNext = NULL;
delete p; //xóa p
if (l.pHead == NULL)
l.pTail = NULL;
else
return 1;
Dnode *p = q->pNext; //khởi tạo con trỏ p gán bằng con trỏ next của nút q
if (p != NULL){
17
q->pNext = p->pNext; //gán con trỏ next của q bằng con trỏ next của p
if (p==l.pTail) l.pTail = q; //nếu p là nút đầu thì nút đầu khi này là q
else
// cập nhật con trỏ prev của nút đứng sau nút cần xóa (p) để trỏ tới nút đứng trước
nó (q)
p->pNext->pPrev = q;
delete p;
return 1;
else return 0;
if (q==NULL) return 0; //kiểm tra xem có nút nào phía trước không
DNode *p = q->pPrev; //khởi tạo con trỏ p và gán bằng con trỏ prev của nút q
if(p!=NULL) //
q->pPrev = p->Prev; //gán con trỏ prev của nút q bằng con trỏ prev của p
if(p == l.pHead) l.pHead=q; //q là nút đầu ds nếu p là xóa p là nút đầu
else //cập nhật con trỏ next của nút đứng trước nút cần xóa p
return 1;
else return 0;
}
18
Xóa nút có khóa k:
DNode *p = l.pHead; //khởi tạo con trỏ p gán bằng nút đầu
if (p->data==k) break; //nếu giá trị của p bằng giá trị k cần tìm thì
thoát khỏi vòng lặp
p=p->pNext; //gán p bằng con trỏ next của nút tiếp theo
DNode *q = p->pPrev;
return removeAfter(l,q);
return removeHead(l);
19
3.5. Duyệt DSLK đôi
Để duyệt danh sách liên kết đôi, ta cần sử dụng các con trỏ next và prev để truy cập các
nút của danh sách. Quá trình duyệt danh sách này khá tương tự việc duyệt danh sách liên
kết đơn. Tuy nhiên, nhờ có các con trỏ prev nên ta có thể điều chỉnh hướng duyệt của
danh sách theo hướng đi lùi từ cuối về đầu.
//Khởi tạo 1 nút tạm thời temp, sử dụng nút này để duyệt danh sách thay cho Head
printf("Forward: ");
printf("%d", temp->data);
temp = temp->next;
printf("\n");
20
Duyệt từ cuối về đầu danh sách:
// Khởi tạo 1 nút tạm thời temp, sử dụng nút này để duyệt danh sách thay cho Tail
printf("Reverse: ");
//Dùng vòng lặp while để duyệt danh sách theo thứ tự đảo ngược
printf("\n");
Người dùng nhập vào số cần tìm k và hàm thực hiện tìm kiếm xem số k đó có trong danh
sách hay không. Nếu có thì trả về true và ngược lại nếu không có thì trả về false.
bool Search (Node l, int k ) // tham số truyền vào bao gồm một danh sách và phần tử k
{
Node *p = head; // tạo một node tạm p để thay thế cho node head
//sử dụng vòng lặp while để lặp từng phần tử trong danh sách
While (p != NULL) {
if(p->data == k) return true; //nếu giá trị tại node hiện tại == k thì return true
else p = p-> next; //nếu không == k thì trỏ đến node kế tiếp
}
return false; //kết thúc vòng lặp vẫn ko tìm thấy thì return false
}
21
Minh họa thuật toán:
Hàm sắp xếp danh sách liên kết đôi bằng phương pháp sắp xếp đổi chỗ trực tiếp
p->info = q->info;
q->info = temp;
}
22
23
CHƯƠNG IV. TƯƠNG QUAN GIỮA CÁC CẤU TRÚC DỮ LIỆU
Ngoài DSLK thì cũng có rất nhiều cấu trúc dữ liệu khác được dùng để lưu trữ dữ liệu,
trong đó phải kể đến mảng cũng rất phổ biến.
Bởi cả DSLK và mảng đều được sử dụng cho cùng 1 mục đích là lưu trữ dữ liệu, ta cần
nắm được sự khác biệt của chúng để lựa chọn phù hợp trong những trường hợp khác
nhau..
Tĩnh: được cấp theo chế độ trong Động: được cấp theo chế độ trong quá
Bộ nhớ
quá trình biên dịch trình khởi chạy
Cố định, cần chỉ rõ khi khai báo, và Có thể thay đổi trong quá trình thêm,
Kích không thể thay đổi trong quá trình bớt phần tử.
thước chạy chương trình Kích thước tối đa chỉ phụ thuộc vào
giới hạn khả năng lưu trữ bộ nhớ.
Biết trước số lượng phần tử cần lưu Không biết trước số lượng phần tử
trữ hoặc khi ta muốn truy cập phần hoặc khi ta cần thực hiện các thao tác
tử ở chỉ mục cụ thể một cách nhanh thêm, sửa, xóa phần tử một cách
Sử
chóng. thường xuyên.
dụng
24
Độ phức tạp của các thao tác:
25
2. DSLK đơn và DSLK đôi
● Danh sách liên kết đơn: Mỗi nút trong danh sách liên kết đơn chỉ chứa thông tin về
phần tử tiếp theo của nó.
● Danh sách liên kết đôi: Mỗi nút trong danh sách liên kết đôi chứa thông tin về
phần tử tiếp theo và phần tử trước của nó.
Bởi vậy, trên DSLK đôi, từ 1 phần tử bất kỳ ta có thể truy xuất đến 1 phần tử bất kỳ khác.
Trong khi đó trên DSLK đơn, ta chỉ có thể truy xuất đến các phần tử đứng sau 1 phần tử
cho trước.
Tuy nhiên, cũng bởi mỗi nút trong DSLK đơn chỉ chứa thông tin về phần tử tiếp theo nên
cấu trúc dữ liệu này sử dụng ít bộ nhớ hơn so với danh sách liên kết đôi.
26
CHƯƠNG V. ỨNG DỤNG CỦA DANH SÁCH LIÊN KẾT
Danh sách liên kết đơn và đôi là các cấu trúc dữ liệu phổ biến trong lập trình. Chúng
được sử dụng để lưu trữ các phần tử dữ liệu theo thứ tự và cho phép truy cập nhanh
chóng đến các phần tử trong danh sách.
Danh sách liên kết đơn và đôi đều có thể được sử dụng trong nhiều ứng dụng khác nhau,
bao gồm:
- Lưu trữ danh sách các thư mục trong hệ thống tập tin của máy tính: mỗi thư mục
được đại diện bởi một nút trong danh sách liên kết, và các nút này được liên kết
với nhau để tạo thành một cây thư mục.
- Quản lý bộ nhớ động: mỗi nút trong danh sách liên kết đại diện cho một vùng nhớ
trong bộ nhớ, và các nút này được liên kết với nhau để tạo thành một danh sách
các vùng nhớ.
- Lưu trữ lịch sử duyệt web: mỗi trang web được truy cập được đại diện bởi một nút
trong danh sách liên kết, và các nút này được liên kết với nhau theo thứ tự mà
người dùng truy cập các trang web.
- Lưu trữ các đối tượng trong các ngôn ngữ lập trình hướng đối tượng: mỗi đối
tượng được đại diện bởi một nút trong danh sách liên kết, và các nút này được liên
kết với nhau để tạo thành một danh sách các đối tượng.
- Thực hiện thuật toán: danh sách liên kết cũng được sử dụng để thực hiện các thuật
toán trong lập trình, chẳng hạn như thuật toán sắp xếp hoặc tìm kiếm.
Ngoài những ứng dụng đã nêu trên trên, một ứng dụng khá gần gũi phải kể đến là sử
dụng danh sách liên kết để quản lý thông tin sinh viên, cụ thể ở đây là quản lý học phí.
Nhờ ứng dụng DSLK, phòng QLNH bên cạnh việc có thể nắm được toàn bộ các thông tin
cá nhân liên quan đến sinh viên như tên, lớp, địa chỉ, … thì còn có thể quản lý được các
thông tin có trong hóa đơn đóng tiền học, góp phần không nhỏ vào trong công tác quản lý
các hoạt động của trường học.
27
Đối tượng
Ứng dụng Thông tin quản lý Thao tác
quản lý
- Nhập thông tin SV
Mã SV - In danh sách SV theo lớp
Tên SV
Sinh viên DSLK đơn Lớp - Tìm kiếm theo mã SV
Địa chỉ - Sắp xếp theo mã SV
Xếp loại
- Thêm/ Xóa SV
28
REFERENCES
[1]. Hoang, P. H., 2021. TopDev. [Online]
Available at: https://topdev.vn/blog/array-va-linked-list-stack-va-
queue/?amp=#amp_tf=Ngu%E1%BB%93n%3A%20%251%24s&aoh=16799016396577
&referrer=https%3A%2F%2Fwww.google.com
[Accessed 2023].
[2]. Karumanchi, N., 2017. Data structures and Algorithms made easy. 5 ed. s.l.:M-
Tech, IIT Bombay.
29