You are on page 1of 36

HỌC VIỆN NGÂN HÀNG

KHOA HỆ THỐNG THÔNG TIN QUẢN LÝ

BÀI TẬP LỚN

HỌC PHẦN: CẤU TRÚC DỮ LIỆU VÀ GIẢI THUẬT

ĐỀ TÀI:

ỨNG DỤNG DANH SÁCH LIÊN KẾT QUẢN LÝ HÓA ĐƠN

HỌC PHÍ SINH VIÊN

GIÁO VIÊN HƯỚNG DẪN: GIANG THỊ THU HUYỀN

MÃ LỚP HỌC PHẦN: 222IS07A01

NHÓM THỰC HIỆN: NHÓM 1

Hà Nội, 3/2023
HỌC VIỆN NGÂN HÀNG

KHOA HỆ THỐNG THÔNG TIN QUẢN LÝ

BÀI TẬP LỚN

HỌC PHẦN: CẤU TRÚC DỮ LIỆU VÀ GIẢI THUẬT

ĐỀ TÀI:

ỨNG DỤNG DANH SÁCH LIÊN KẾT QUẢN LÝ

HÓA ĐƠN HỌC PHÍ SINH VIÊN

Nhóm 1

Mã sinh viên Tên sinh viên

24A4041418 Ngô Hồng Ngọc

24A4043051 Nguyễn Thu Huyền

24A4040378 Nguyễn Thị Thanh

24A4040475 Đỗ Ngọc Khánh

24A4040014 Phạm Gia Bình

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%

Tổng hợp nội dung,


24A4043051 Nguyễn Thu Huyền 19%
thiết kế báo cáo

Chỉnh sửa nội dung, lên ý


24A4040378 Nguyễn Thị Thanh 24%
tưởng, phát triển code

Nội dung + minh họa DSLK


24A4040475 Đỗ Ngọc Khánh 19%
đơn, đôi, thuyết trình

Nội dung + minh họa DSLK


24A4040014 Phạm Gia Bình 17%
đơn, đôi, dẫn vào thuyết trình
LỜI MỞ ĐẦU
1. Lý do chọn đề tài:

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.

2. Mục tiêu đề tài:

- 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.

3. Phạm vi nghiên cứu:

- 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.

4. Phương pháp nghiên cứu:

- 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

1. Danh sách liên kết là gì ?............................................................................................... 1

2. Các kiểu tổ chức liên kết giữa các phần tử .................................................................... 1

CHƯƠNG II. DANH SÁCH LIÊN KẾT ĐƠN ............................................................... 2

1. Tổ chức của DSLK đơn ................................................................................................. 2

2. Cấu trúc dữ liệu ............................................................................................................. 2

3. Các thao tác với DSLK đơn .......................................................................................... 3

3.1. Tạo 1 DSLK đơn rỗng ................................................................................................ 3

3.2. Tạo 1 nút và gán giá trị cho nút .................................................................................. 3

3.3. Thêm nút vào DSLK .................................................................................................. 3

3.4. Xóa nút trong DSLK .................................................................................................. 6

3.5. Duyệt DSLK đơn ........................................................................................................ 8

3.6. Tìm kiếm nút có giá trị x trong DSLK ....................................................................... 9

3.7. Sắp xếp các phần tử có trong DSLK ........................................................................ 10

CHƯƠNG III. DANH SÁCH LIÊN KẾT ĐÔI ............................................................. 11

1. Tổ chức của DSLK đôi ................................................................................................ 11

2. Cấu trúc dữ liệu của DSLK đôi ................................................................................... 11

3. Các thao tác với DSLK đôi ......................................................................................... 12

3.1. Tạo 1 DSLK đôi ....................................................................................................... 12

3.2. Tạo 1 nút mới trong DSLK đôi ................................................................................ 12

3.3. Thêm nút vào DSLK đôi .......................................................................................... 12

3.4. Xóa nút trong DSLK đôi .......................................................................................... 16

3.5. Duyệt DSLK đôi ....................................................................................................... 20

3.6. Tìm kiếm trong DSLK đôi ....................................................................................... 21

3.7. Sắp xếp trong DSLK đôi .......................................................................................... 22

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

2. DSLK đơn và DSLK đôi ............................................................................................. 26

CHƯƠNG V. ỨNG DỤNG CỦA DANH SÁCH LIÊN KẾT ....................................... 27

1. Một số ứng dụng phổ biến ........................................................................................... 27

2. Bài toán cụ thể ............................................................................................................. 27

REFERENCES ............................................................................................................... 29
CHƯƠNG I. TỔNG QUAN VỀ DANH SÁCH LIÊN KẾT

1. Danh sách liên kết là gì ?

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:

 Mỗi phần tử của danh sách gọi là nút (node).


 Mỗi nút có 2 thành phần: phần dữ liệu và phần liên kết chứa địa chỉ của node kế
tiếp hoặc node trước nó và liên kết bằng con trỏ.
 Phần tử cuối cùng của danh sách trỏ đến NULL.
 Kích thước có thể thay đổi tùy ý và có thể kéo dài cho tới khi hết bộ nhớ.

2. Các kiểu tổ chức liên kết giữa các phần 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
▪ 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

1. Tổ chức của DSLK đơ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).

2. Cấu trúc dữ liệu

CTDL của 1 nút (node) trong DSLK đơn

Typedef struct tagNode{

Data Info; //Lưu thông tin bản thân phần tử

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;

struct ListNode *next;

};

CTDL của 1 DSLK đơn

Typedef struct tagList

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

3.1. Tạo 1 DSLK đơn rỗng

Xây dựng hàm createList() để gán các con trỏ head và tail trỏ về NULL.

void createsList(sList &l)

l.head = NULL; //cho nút đầu tiên trỏ đến NULL

l.tail = NULL; ////cho nút cuối trỏ đến NULL

3.2. Tạo 1 nút và gán giá trị cho nút

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;

p = new Node; //Cấp phát vùng nhớ cho phần tử

if ( p==NULL) exit(1);

p ->Info = x; //gán dữ liệu cho nút

p->pNext = NULL;

return p;

3.3. Thêm nút vào DSLK


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ì:

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;

Cài đặt hàm thêm 1 nút vào đầu danh sách:

void AddHead (LIST &l, Node* p)

if (l.pHead==NULL) { //DS rỗng

// Gán cho nút đầu và nút cuối

l.pHead = p;

l.pTail = l.pHead;

} else{ //Nếu danh sách không rỗng

// 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;

// Gán pHead bằng node p

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){

if (l.pHead ==NULL) { //DS rỗng

l.pHead = p; // Chèn đầu và cuối đều bằng nút p

l.pTail = l.pHead;

} else{ //Nếu DS không rỗng

l.pTail->Next = p; // Gán con trỏ của phần tử cuối trong ds bằng node p

l.pTail = p; // Gán pTail bằng node p

● Thêm nút p vào sau nút q trong danh sách đơn

Cho con trỏ liên kết (next) của node p chỉ vào node sau của q.

Cho con trỏ liên kết của q chỉ vào node p.

Nếu q là nút cuối thì gán lại p là nút cuối.

void InsertAfterQ (List &l, Node *p, Node *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

q->Next=p; // Gán con trỏ next của node q bằng node p

//Nếu q là phần tử cuối cùng thì chèn node p vào cuối

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

AddHead(l,q); // thêm q vào đầu list

5
3.4. Xóa nút trong DSLK

Để xóa 1 nút, ta cần phải cô lập phần tử cần hủy trước.

Xóa nút đầu:

void XoaDau(LIST &ds){

//tạo nút p

NODE *p = new NODE

p = ds.pHead; ; //gán p bằng node pHead đầu tiên của danh sách

ds.pHead = ds.pHead->next; //thay đổi lại pHead của danh sách

p->next = NULL; //gán node p ban đầu trỏ đến NULL

//xóa node p

delete p;

Xóa nút cuối:

void XoaCuoi(LIST &ds){

//duyệt các phần tử có trong danh sách

for(NODE *k = ds.pHead; k != NULL; k = k ->next)

if(k->next == ds.pTail) //nếu duyệt đến phần tử pTail cuối trong danh sách

delete ds.pTail; //xóa phần tử cuối

k->next = NULL; //trỏ phần tử trước phần tử cuối về NULL

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:

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ại pTail

q->pNext=p->pNext; x=p->Info;

delete p;

return 1;

} else

return 0;

Hủy 1 nút có khóa k:

int removeNode (List &l, int k)

//Tìm nút p có khóa k và nút q đứng trước nó

Node *p = l.pHead;

Node *q = NULL;

While (p != NULL)

If ( p -> data == k) break;

q = p;

7
p = p ->pNext;

if(p == NULL) return 0; //ko tìm thấy

else if( q == NULL)

//xóa phần tử đầu danh sách là p

Else

//xóa phần tử sau p sau q

3.5. Duyệt DSLK đơn

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:

● Đi theo con trỏ


● Hiển thị nút tiếp theo (hoặc đếm) khi nó được duyệt
● Dừng lại cho đến khi con trỏ trỏ đến NULL

void processList (List l)

Node *p = l.pHead; //Tạo node p và gán bằng phần tử đầu tiên

while (p != NULL) //trong khi p chưa phải là phần tử cuối

{ //trỏ đến node tiếp theo trong ds

p=p->Next;

Duyệt danh sách thường được dùng khi có các nhu cầu:

Đếm số lượng phần tử có trong danh sách:

int ListLength (struct ListNode*head) {


Struct ListNode *current=head; //gán con trỏ current bằng nút đầu
Int count=0; // khởi tạo biến đếm và gán bằng 0

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
}

In các phần tử có trong danh sách:

Void PrintList (List l) {

//Khởi tạo con trỏ p và cho nó trỏ tới nút đầu


Node*p;
p=l.pHead;
While (p != NULL) { //trong khi chưa đi hết ds
Printf (“%d ”, p->Info); //in giá trị của nút hiện tại
P=p -> pNext; //chuyển con trỏ tới nút tiếp theo
}
}

Hủy toàn bộ danh sách:

Void RemoveList (List &l) {


Node *p; //khởi tạo con trỏ p
While (l.pHead != NULL) { //trong khi chưa đi hết ds
p=l.pHead; //gán p là nút đầu
l.pHead=p->pNext; // cho p trỏ tới nút kế tiếp
delete p; //xóa nút p
}
}

3.6. Tìm kiếm nút có giá trị x trong DSLK

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.

Node *Search(LIST l, Data x)

{
9
Node *p; //khởi tạo con trỏ p

p = l.pHead; //gán phần tử đầu tiên bằng 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

while((p!= NULL)&&(p->Info != x))

p = p->pNext; //trỏ đến nút tiếp theo

return p; //trả về nút p

3.7. Sắp xếp các phần tử có trong DSLK

void InterchangeSort(List *list)

Node *p, *q; //khởi tạo con trỏ p và q

for (p = list->head; p != NULL; p = p->next)

//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

for (q = p->next; q != NULL; q = q->next)

if (p->info > q->info) //giá trị nút p > giá trị nút q thì hoán đổi giá trị 2 nút

int temp = p->info;

p->info = q->info;

q->info = temp;

}
10
CHƯƠNG III. DANH SÁCH LIÊN KẾT ĐÔI

1. Tổ chức của DSLK đô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.

Danh sách liên kết đôi tổ chức như sau:

● Giá trị (data).


● Mối liên kết tới Node khác (pPre và pNext).

Ở 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ó.

2. Cấu trúc dữ liệu của DSLK đôi

CTDL của 1 nút:

Trong Node có:

● Data là giá trị của Node.


● pPre là con trỏ, trỏ tới phần tử liền trước.
● pNext là con trỏ, trỏ tới phần tử liền sau.

Tổ chức biểu diễn một Node của danh sách liên kết đôi.

struct Node {

int data;

struct Node* next;

struct Node* prev;

};

CTDL của 1 DSLK đôi:

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.

3. Các thao tác với DSLK đôi

3.1. Tạo 1 DSLK đôi

Khai báo Node đầu pHead và Node cuối pTail:

struct SingleList
{
Node *pHead; //Node đầu pHead
Node *pTail; // Node cuối pTail
};

Khởi tạo danh sách


void Initialize(List &list)
{
list.pHead=list.pTail=NULL;// khởi tạo giá trị cho Node đầu và Node cuối là Null
}

3.2. Tạo 1 nút mới trong DSLK đôi

Node *creatNode (int x ) { // Tạo thông tin cho node


Node *p = new Node; // tạo mới một node p
If (p == NULL) exit(1); // nếu p rỗng thì thoát khỏi hàm
p ->next = NULL; // khi tạo mới một node thì p->next == p->prev == null
p->prev = NULL;
p->data = x; //gán giá trị data = x
return p;
}
3.3. Thêm nút vào DSLK đôi

Thêm nút vào đầu danh sách:

Đầ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

Dịch chuyển node head về newNode: head = newNode

void addHead (Dlist &l, Dnode *new_node)


{
if (l.pHead == NULL) // ds rỗng
l.pHead = l.pTail = new_node;
else{
new_node->pNext = l.pHead;
l.pHead->pPrev = new_node;
l.pHead = new_node
}
}

Thêm nút vào cuối danh sách:

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.

void addTail (DList &l, DNode *new_node)

if (l.pHead == NULL)

l.pHead = l.pTail = new_node;

else
13
{

l.pTail-> pNext = new_node;

new_node -> pPrev = l.pTail;

l.pTail = new_node;

Thêm nút p vào giữa danh sách

Thuật toán chèn vào trước q:

void addAfter(DList &l, DNode*q, Dnode*new_node)

DNode*p = q->pNext; //tạo con trỏ p để trỏ tới nút sau nút q

if (q!=NULL) { //q không là nút đầu hay cuối

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

//nếu q là nút cuối thì nút mới là nút cuối

if (q == l.pTail) l.pTail = new_node;

} else //q là nút đầu

addFirst(l,new_node); // chèn vào đầu danh sách

}
14
Thuật toán chèn vào sau q:

void addBefore (DList &l, DNode q, DNode *new_node)

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)

l.pHead = new_node; //nút đầu là nút mới nếu q là nút đầu

} else

AddTail (l,new_node); // Chèn vào cuối danh sách

15
3.4. Xóa nút trong DSLK đôi

Xóa nút ở đầu danh sách:

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

int removeHead (Dlist &l)

if (l.pHead == NULL) return 0;

DNode*p = l.pHead;

l.pHead = l.pHead -> pNext;

l.pHead -> pPrev = NULL;

delete p;

if (l.pHead == NULL) l.pTail = NULL;

else l.pHead->pPrev = NULL;

return 1;

Xóa nút cuối trong danh sách:

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

int removeTail (Dlist &l){

if (l.pTail == NULL) return 0;

DNode *p = l.pTail; //tạo node p và gán bằng phần tử cuối

//Thực hiện gán lại phần tử cuối ds

l.pTail = l.pTail ->pPrev;

l.pTail->pNext = NULL;

delete p; //xóa p

if (l.pHead == NULL)

l.pTail = NULL;

else

l.pHead -> pPrev = NULL;

return 1;

Xóa nút nằm ở giữa:

Xóa nút p sau nút q:

Int removeAfter (DList &l, DNode *q)

if( q == NULL) return 0;

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;

Xóa nút p trước q:

int removeBefore (DList &l, DNode *q){

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

p->pPrev -> pNext=q;

delete p; //xóa nút p

return 1;

else return 0;

}
18
Xóa nút có khóa k:

int removeNode (Dlist &l, int k)

DNode *p = l.pHead; //khởi tạo con trỏ p gán bằng nút đầu

While (p != NULL) //trong khi ds chưa hết

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

if (p ==NULL) return 0; // Ko tìm thấy k

DNode *q = p->pPrev;

if (q != NULL) //Xóa nút p sau q

return removeAfter(l,q);

else //xóa p là nút đầu ds

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.

Duyệt từ đầu tới cuối danh sách:

void Print(Node* head) {

//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

Node* temp = head;

printf("Forward: ");

while (temp != NULL) { // Sử dụng vòng lặp để duyệt danh sách

printf("%d", temp->data);

temp = temp->next;

printf("\n");

20
Duyệt từ cuối về đầu danh sách:

void ReversePrint (Node* tail) {

// 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

Node* temp = tail;

// Nếu danh sách rỗng, trả về hàm

if (temp == NULL) return;

printf("Reverse: ");

//Dùng vòng lặp while để duyệt danh sách theo thứ tự đảo ngược

while (temp != NULL) {

printf("%d", temp->data); // In giá trị hiện tại của nút

temp = temp->prev; // Chuyển tới nút tiếp theo

printf("\n");

3.6. Tìm kiếm trong DSLK đôi

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:

3.7. Sắp xếp trong DSLK đôi

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

void InterchangeSort(List *list)

Node *p, *q;

for (p = list->head; p != NULL; p = p->next)

for (q = p->next; q != NULL; q = q->next)

if (p->info > next->info)

int temp = p->info;

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

1. Danh sách liên kết và mảng

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..

Mảng Danh sách liên kết


Lưu trữ dưới dạng các phần tử liên Lưu trữ dưới dạng các nút, được liên
tiếp trong bộ nhớ kết với nhau qua con trỏ
Cấu
 các phần tử có thể được truy cập  các phần tử chỉ có thể được truy
trúc
ngẫu nhiên thông qua chỉ mục index cập theo thứ tự và thông qua việc truy
cập các nút tiếp theo
Làm thay đổi kích thước mảng, dẫn Thực hiện một cách dễ dàng bằng
Thao đến sự di chuyển các phần tử khác cách chỉnh sửa con trỏ liên kết giữa
tác trong mảng và tốn thời gian. các nút, mà không ảnh hưởng đến các
phần tử khác.

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:

Thao tác Mảng DSLK


O (n) vì phải duyệt qua từng
Truy cập
O (1) phần tử để đến phần tử cần
phần tử
truy cập
Thêm phần O(n) do phải dịch chuyển các phần tử
O(1)
tử vào đầu để tạo vị trí trống
● O (1) nếu mảng chưa đầy
Thêm phần ● O (n) nếu mảng đã đầy và O(1)
tử vào cuối
phải tạo 1 mảng mới

● O(n) trong trường hợp


truy cập tới vị trí cần
Thêm phần chèn.
tử vào vị trí O(n) do phải dịch chuyển các phần tử
● O(1) nếu biết node
bất kỳ
trước và node sau địa
chỉ của node mới.

● O(1) nếu xóa ở cuối mảng. ● O (1) nếu xóa ở đầu.


● O(n) nếu xóa ở vị trí cụ thể do ● O(1) nếu biết node
phải dịch chuyển các phần tử trước và node sau địa
còn lại để lấp đầy vị trí trống. chỉ của node cần xóa.

Xóa phần tử ● O(n) nếu xóa ở vị trí bất


kỳ vì phải duyệt qua
danh sách để đến vị trí
cần xóa.

25
2. DSLK đơn và DSLK đôi

Có sự khác biệt trong cấu trúc dữ liệu:

● 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

1. Một số ứng dụng phổ biến

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.

2. Bài toán cụ thể

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

Mã hóa đơn - Nhập thông tin hóa đơn


Ngày đóng
- In danh sách hóa đơn
Hóa đơn Mã SV
đóng tiền DSLK đôi Tên SV - Tìm kiếm theo mã hóa đơn
học Học phí
- Sắp xếp theo số tiền
Số tiền miễn giảm
Số tiền cần đóng - Xóa hóa đơn

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.

[3]. Mai, N. T. Q., 2020. Viblo. [Online]


Available at: https://viblo.asia/p/su-khac-biet-giua-linked-list-va-array-gAm5y14X5db
[Accessed 2023].

[4.]Nguyen, T. T., 2020. Youtube. [Online]


Available at: https://youtube.com/@thientamnguyen.94
[Accessed 3 2023].

[5]. Tyler, A., n.d. Slide Player. [Online]


Available at: https://slideplayer.com/slide/14706950/
[Accessed 3 2023].

29

You might also like