Chương 4. Danh sách liên kết

Tr n Minh Thái Email: minhthai@huflit.edu.vn Website: www.minhthai.edu.vn

Cập nhật: ngày 10 tháng 04 năm 2016

Mục tiêu

• Nắm vững khái niệm về kiểu dữ liệu tĩnh và động

• Nắm vững cách tổ chức dữ liệu động bằng danh sách liên

kết và minh họa được các thao tác xử lý trên danh sách liên

kết đơn

• Cài đặt minh họa được các thao tác của danh sách đơn

bằng ngôn ngữ C/ C++

2

Vấn đề kiểu dữ liệu tĩnh

6

15

10

9

7

5

3 4

1 2 3 5

2 6

7

1 8

? Làm sao để chèn thêm số 6 vào vị trí 5 của mảng

3

Vấn đề kiểu dữ liệu tĩnh

6

Giả sử cần thêm tiếp 1 phần tử

15

10

9

7

? 5

1 2 3

3 4

5

2 6

1 8

4

7 9

Bổ sung thêm

Bài tập

Hãy cài đặt hàm (bằng ngôn ngữ C/C++) chèn một phần tử có giá trị x vào vị trí vt trong mảng số nguyên a, kích thước n, theo mẫu hàm như sau:

void ChenX(int a[], int &n, int x, int vt);

5

Vấn đề kiểu dữ liệu tĩnh

15

10

9

7

5

1 2 3

3 4

5

2 6

7

1 8

? Làm sao để xóa phần tử 9

6

Vấn đề kiểu dữ liệu tĩnh

15

10

9

7

5

1 2 3

3 4

5 7

1 8

2 6

7

Bài tập

• Hãy cài đặt hàm (bằng ngôn ngữ C/C++) xóa phần tử có giá trị x (nếu có) trong mảng số nguyên a, kích thước n (giả sử giá trị các phần tử trong mảng không trùng nhau), theo mẫu hàm như sau:

void XoaX (int a[], int &n, int x);

8

Vấn đề kiểu dữ liệu tĩnh

Độ phức tạp của chèn/ xóa trên mảng 1 chiều là O(n)

i

9

Vấn đề kiểu dữ liệu tĩnh

• Giải quyết vấn đề phức tạp khi chèn/ xóa?

• Giải quyết vấn đề giới hạn kích thước vùng nhớ tối đa?

• Giải quyết vấn đề vùng nhớ không liên tục?

• Giải quyết vấn đề giải phóng vùng nhớ khi không cần dùng đến?

DÙNG CẤU TRÚC DỮ LIỆU ĐỘNG

10

Biến tĩnh và biến động trong C++

• Biến tĩnh

tên biến;

Vd: int a; float y; char s[20];

ü

Tồn tại trong phạm vi khai báo

ü Được cấp phát vùng nhớ trong vùng dữ liệu

ü

Kích thước cố định

11

Biến tĩnh và biến động trong C++

• Biến động

*tên biến;

Vd: int *a; float *y;

ü Chứa địa chỉ của một đối tượng dữ liệu

ü Được cấp phát hoặc giải phóng bộ nhớ tùy thuộc vào người lập

trình

ü

Kích thước có thể thay đổi

12

Biến tĩnh và biến động trong C++

• Biến động

ü Cấp phát bộ nhớ: new int [kích thước]

ü Giải phóng bộ nhớ: delete vùng nhớ

• Ví dụ: int *a;

a=new int [10]; // Cấp phát

//Các thao tác trên a

delete a; // Giải phóng

13

Danh sách liên kết (DSLK)

1

Các phần tử kết dính với nhau bằng “sợi dây liên kết”

14

7 2 6

3 10 8 5 9 4

1

Để đơn giản hơn trong việc minh họa

7 2 6

15

3 10 8 5 9 4

Đặc điểm DSLK

• Một dãy tuần tự các nút (Node)

• Giữa hai nút có con trỏ liên kết

• Các nút không cần phải lưu trữ liên tiếp nhau trong bộ nhớ

• Có thể mở rộng tuỳ ý (chỉ giới hạn bởi dung lượng bộ nhớ)

16

Đặc điểm DSLK

• Thao tác Chèn/Xóa không cần phải dịch chuyển phần tử mà chỉ

cần thay đổi mối liên kết

• Quản lý phần tử đầu tiên bằng con trỏ pHead

• Có thể truy xuất đến các phần tử khác thông qua con trỏ liên

kết

17

Cấu tạo của DSLK

Node

List

pHead pTail

18

Cấu tạo của DSLK

• Quản lý toàn bộ danh sách liên kết thông qua con trỏ đầu

pHead

• pHead không phải là 1 nút, nó chỉ là “con trỏ chỉ đến nút” mà

thôi

• Ta cũng có thể quản lý danh sách bằng cách sử dụng thêm con

trỏ cuối (pTail)

• pTail không phải là 1 nút, nó chỉ là “con trỏ chỉ đến nút” mà thôi

19

Cấu tạo của nút

• Tạo lập bằng cách cấp phát bộ nhớ động

• Mỗi nút có 2 thông tin:

• Dữ liệu (data)

• Con trỏ liên kết đến phần tử kế tiếp trong danh sách (Next pointer link) • Nếu không trỏ đến phần tử nào thì con trỏ Next = NULL

20

Thao tác chèn thêm node vào DSLK

• “Kết nối” lại sợi dây liên kết theo trình tự

List

pHead pTail

21

Thao tác xóa node khỏi DSLK

Cần xóa

List

pHead pTail

22

Các loại hình DSLK

DSLK đơn: Các phần tử kết nối với nhau theo hướng “chiều đi tới”

23

Các loại hình DSLK

DSLK đôi: Các phần tử kết nối với nhau theo hướng “chiều đi tới và và đi lui”

24

Các loại hình DSLK

Danh sách liên kết vòng: Các phần tử kết nối với nhau theo hướng “chiều đi tới” và phần tử cuối cùng có “đường đi vòng trở lại tới” phần tử đầu danh sách

25

ả So sánh M ng và DSLK

M ngả

Danh sách liên k tế ố

ầ ử

ổ  thay đ i tùy

ướ ố ị

Kích th

c c  đ nh

S  ph n t ý

ế

liên  k t

Các  ph n  t ằ v i nhau b ng con tr

ầ ữ ử ư Các  ph n  t   l u  tr   ỉ ị ự ầ tu n  t   (đ a  ch   tăng  ớ ộ ầ d n) trong b  nh

ỉ ầ

ổ ế k t

ị ả Ph i  d ch  chuy n  các  ầ ử  khi Thêm/Xóa ph n t

ầ ự26

Truy xu t ng u nhiên

Ch   c n  thay  đ i  con  ỏ tr   khi  liên  Thêm/Xóa ấ Truy xu t tu n t

DSLK đơn

pNext

Data

Cấu trúc 1 node

List

pHead pTail

Data : Dữ liệu của node pNext : Con trỏ đến node kế tiếp pHead: Con trỏ đến node đầu pTail: Con trỏ đến node cuối

27

Khai báo cấu trúc node

pNext

Data

struct tNODE {

Data; struct tNODE *pNext;

28

}; typedef struct tNODE NODE;

Khai báo cấu trúc node lưu số nguyên

pNext

20

struct tNODE {

int Data; struct tNODE *pNext;

29

}; typedef struct tNODE NODE;

Khai báo cấu trúc node lưu thông tin SV

pNext

ID, hoten, dtb

struct tSinhVien {

char ID[10], hoten[30]; float dtb;

}; typedef struct tSinhVien SINHVIEN;

struct tNODE {

30

SINHVIEN Data; struct tNODE *pNext;

}; typedef struct tNODE NODE;

Khai báo cấu trúc DSLK đơn

List

pHead pTail

struct tList {

NODE *pHead, *pTail;

31

}; typedef struct tList LIST;

Khai báo cấu trúc DSLK đơn

list

pHead pTail

32

struct tList { NODE *pHead, *pTail; }; typedef struct tList

struct tNODE { Data; struct tNODE *pNext; }; typedef struct tNODE NODE;

LIST;

Các thao tác trên DSLK đơn

• Tạo lập danh sách rỗng

• Kiểm tra danh sách rỗng

• Thêm 1 nút vào danh sách

• Duyệt danh sách

• Xóa 1 nút

• Tìm 1 phần tử

• Sắp xếp danh sách

33

Khai báo thư viện hàm

1

h n

Khai báo cấu trúc danh sách liên kết

2

ì r t

Khai báo các nguyên mẫu hàm

3

g n ơ ư h c t

4

á u q

g n ổ

void main() {

Tạo lập danh sách rỗng Nhập dữ liệu vào danh sách Các thao tác xử lý trên danh sách Hủy danh sách

t c ú r t

34

}

Cài đặt các hàm con

5

u ấ C

Tạo lập danh sách rỗng

pHead và pTail chưa xác định

pHead và pTail trỏ vào NULL (rỗng)

List

List

? pHead

? pTail

pHead pTail

Sau khi tạo lập

Trước khi tạo lập void CreateEmptyList(LIST &list)

{

list.pHead = list.pTail = NULL;

}

35

Kiểm tra danh sách rỗng

List

pHead pTail

Danh sách rỗng

bool IsEmptyList(LIST list)

{

return ((list.pHead==NULL) &&

(list.pTail==NULL));

}

36

Thêm một nút vào danh sách

TH danh sách rỗng

list.pHead = pNew

30

list

pHead pTail

pNew

if(IsEmptyList(list)) {

list.pHead = list.pTail = pNew;

}

37

list.pTail = pNew

Thêm một nút vào danh sách

TH danh sách đã có phần tử

30 25

list

pHead pTail

pNew

Có 2 trường hợp để thêm pNew 1. Thêm pNew vào đầu (AddHead) 2. Thêm pNew vào cuối (AddTail)

38

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

2

1

30 25

list

pHead pTail

pNew

39

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

30 25

list

pHead pTail

Vẽ lại

25 30

list

pHead pTail

40

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

?

Hãy vẽ lại “đường kết nối” theo thứ tự thích hợp khi thêm pNew vào đầu danh sách

25 30 42

list

pHead pTail

pNew

41

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

pNew->pNext = list.pHead

1

list.pHead = pNew

42

2

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

?

Hãy viết hàm thêm phần tử pNew vào đầu danh sách (bằng ngôn ngữ C/C+ +), theo mẫu sau: void AddHead(LIST &list, NODE *pNew )

43

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

30 25 1

list

pHead pTail

pNew

2

list.pTail->pNext = pNew

1

list.pTail = pNew

44

2

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

?

Hãy vẽ lại “đường kết nối” theo thứ tự thích hợp khi thêm pNew vào cuối danh sách

30 25 42

list

pHead pTail

pNew

45

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

?

Hãy viết hàm thêm phần tử pNew vào cuối danh sách (bằng ngôn ngữ C/C+ +), theo mẫu sau: void AddTail (LIST &list, NODE *pNew)

46

Nhập dữ liệu vào danh sách

Nhập dữ liệu cho node

Tạo con trỏ node

47

Thêm node vào danh sách

Nhập dữ liệu vào danh sách

Để tạo node mới từ dữ liệu x có sẵn

• Đưa dữ liệu có giá trị x vào phần Data

• Con trỏ pNext trỏ đến NULL

pNew->Data = x

Data

x

pNew->pNext = NULL

pNew

48

Nhập dữ liệu vào danh sách

VD hàm tạo và trả về con trỏ node có chứa giá trị nguyên x bằng ngôn ngữ C++

NODE *CreateNode (int x) {

NODE *p; p = new NODE; if(p == NULL) {

cout<<“Loi cap phat duoc vung nho!"; exit(0);

} p->Data = x; p->pNext=NULL; return p;

49

}

Nhập dữ liệu vào danh sách

VD hàm nhập dữ liệu cho danh sách số nguyên và đưa vào đầu danh sách

void Input(LIST &list) {

int x; NODE *pNew; CreateEmptyList(list); do{

cout<<"Nhap gia tri (Nhap 0 ket thuc): "; cin>>x; if(x==0)

break;

pNew=CreateNode(x); AddHead(list, pNew);

}while(true);

50

}

Xuất dslk

p

List

pHead pTail

51

Bài tập

Cài đặt các hàm sau trên dslk đơn số nguyên:

1. Đếm số lượng node trong dslk

int DemSL(LIST list);

2. Tìm node có giá trị lớn nhất

NODE* TimMax(LIST list);

3. Tìm node có giá trị x

NODE* TimX(LIST list, int x);

4. In những node có giá trị chẵn

void InChan(LIST list);

5. Tính giá trị trung bình cộng các node lẻ

float TBLe(LIST list);

52

Chèn node vào DSLK đơn

• Chèn vào sau node p

• Chèn vào trước node p

25

pNew

p

list

53

pHead pTail

Chèn node vào sau node p

pNew

p

list

54

pHead pTail

Chèn node vào trước node p – Cách 1

pNew

p

pPrev

list

55

pHead pTail

Chèn node vào trước node p – Cách 1

Hãy viết hàm tìm và trả về con trỏ node đứng trước con trỏ node p (bằng ngôn

?

ngữ C/C++), theo mẫu sau:

NODE *PrevNode (LIST list, NODE *p)

56

Chèn node vào trước node p – Cách 1

NODE *PrevNode (LIST list, NODE *p) {

if(p == list.pHead) return NULL;

NODE *pTruoc = list.pHead; while(pTruoc->pNext != p)

pTruoc = pTruoc -> pNext;

return pTruoc;

}

57

Chèn node vào trước node p – Cách 1

void ChenTruocP1 (LIST &list, NODE *p, NODE *pNew) {

NODE *pTruoc = PrevNode(list, p); if(pTruoc == NULL)

AddHead(list, pNew);

else {

pTruoc -> pNext = pNew; pNew -> pNext = p;

58

}

}

Chèn node vào trước node p – Cách 2

ướ ướ ị B c 1. Chèn pNew vào sau p ị B c 2. Hoán v  giá tr  pNew và p

pNew

p

list

59

pHead pTail

Xóa một nút trong danh sách

• Xóa nút đầu của danh sách  Ảnh hưởng pHead

• Xóa nút cuối của danh sách  Ảnh hưởng pTail

• Xóa nút giữa danh sách

60

Xóa một nút trong danh sách

• Xóa nút đầu của danh sách

Cần xóa

30 25 41 78

list

pHead pTail

pDel

61

NODE *pDel = list.pHead list.pHead = list.pHead->pNext delete pDel

Xóa một nút trong danh sách

?

Hãy viết hàm xóa nút đầu của danh sách (bằng ngôn ngữ C/C++), theo mẫu sau: void DeleteHead (LIST &list)

(lưu ý trường hợp danh sách chỉ còn 1 node trước khi xóa)

62

Xóa một nút trong danh sách

void DeleteHead (LIST &list) {

NODE *pDel = list.pHead; if(list.pHead != list.pTail)

list.pHead = pDel -> pNext;

else

list.pHead = list.pTail = NULL;

delete pDel;

}

63

Xóa một nút trong danh sách

• Xóa nút cuối của danh sách

Cần xóa

pPrev

pDel

30 25 41 78

list

pHead pTail

64

NODE *pDel = list.pTail NODE *pPrev = “Tìm node trước pTail” pPrev->pNext = NULL list.pTail = pPrev delete pDel

Xóa một nút trong danh sách

?

Hãy viết hàm xóa nút cuối của danh sách (bằng ngôn ngữ C/C++), theo mẫu sau: void DeleteTail (LIST &list) (lưu ý trường hợp xóa danh sách chỉ có 1 node)

65

Xóa một nút trong danh sách

void DeleteTail (LIST &list) {

NODE *pDel = list.pTail; NODE *pTruoc = PrevNode(list, list.pTail); if(pTruoc != NULL) {

pTruoc ­> pNext = NULL; list.pTail = pTruoc;

} else

list.pHead = list.pTail = NULL;

66

delete pDel;

}

Xóa một nút trong danh sách

• Xóa nút giữa của danh sách

Cần xóa

pDel

pPrev

30 25 41 96 78

list

pHead pTail

67

NODE *pPrev = “Tìm node trước pDel” pPrev->pNext = pDel->pNext delete pDel

Xóa một nút trong danh sách

?

Hãy viết hàm xóa một node bất kỳ trong danh sách (bằng ngôn ngữ C/C++), theo mẫu sau: void DeleteNode (LIST &list, NODE * pDel)

68

Xóa một nút trong danh sách

?

Hãy viết hàm hủy toàn bộ danh sách (bằng ngôn ngữ C/C++), theo mẫu sau: void DestroyList (LIST &list)

69

Bài tập

6. Chèn node có giá trị x vào phía sau node có giá trị lớn nhất (giả sử danh sách không có giá trị trùng nhau)

void ChenXSauMax(LIST &list, int x);

7. Xóa node có giá trị x xuất hiện đầu tiên trong danh sách (nếu có xóa: trả về true, ngược lại trả về false)

bool XoaX(LIST &list, int x);

8. Sắp tăng dslk

70

void SapTang(LIST &list);