Ví dụ: int *a = new int[n], HocSinh *a = new HocSinh[n] => Ưu điểm: Không phải tốn bộ nhớ, cần xài bao nhiêu thì cấp bấy nhiêu Đã gọi là mảng thì phải tuân thủ theo nguyên tắc sau: Các p
Trang 11
DANH SÁCH LIÊN KẾT ĐƠN
Mảng tĩnh
Ví dụ: int a[100], HocSinh a[100]
=> Nhược điểm: Kích thước bộ nhớ lúc nào cũng phải là 100, dù là xài không hết
=> tốn bộ nhớ
Mảng động
Ví dụ: int *a = new int[n], HocSinh *a = new HocSinh[n]
=> Ưu điểm: Không phải tốn bộ nhớ, cần xài bao nhiêu thì cấp bấy nhiêu
Đã gọi là mảng thì phải tuân thủ theo nguyên tắc sau: Các phần tử
trong bộ nhớ phải nằm liên tục nhau
=> Giả sử cần cấp phát 1 mảng 5 phần tử, trong bộ nhớ lúc này cũng còn trống đúng 5 ô nhưng vấn đề 5 ô không nằm liên tục nhau => giải pháp dùng mảng đã THẤT BẠI
Lúc này họ sẽ dùng giải pháp khác đó là DANH SÁCH LIÊN KẾT
=> Sẽ không cần các ô nhớ phải nằm cạnh nhau, ô này nằm chỗ này, ô khác nằm chỗ khác cũng đc, miễn có đủ 5 ô là đc
Danh sách liên kết có 4 dạng sau
- Danh sách liên kết đơn
- Danh sách liên kết đôi
- Danh sách liên kết vòng đơn
- Danh sách liên kết vòng kép
So sánh ưu và nhược điểm của mảng so với danh sách liên kết đơn
* Mảng *
- Nhược điểm:
✓ Các phần tử phải liên tục nhau, cho nên giả sử cần 5 ô nhưng còn trúng đúng 5 ô nhưng 5 ô đó không liên tục nhau => ko dùng mảng
đc
✓ Luôn phải yêu cầu cấp trước 1 số lượng nào đó (mảng tĩnh là 100, mảng động là n)
✓ Thao tác thêm/Xóa phải lùi lại hay tiến lên rất nhiều phần tử => chương trình chạy chậm
- Ưu điểm:
Trang 22
✓ Tiết kiệm bộ nhớ hơn (chỉ đối với mảng động)
✓ Truy xuất tới 1 phần tử sẽ nhanh hơn (nhờ vào toán tử lấy chỉ số) vd: a[3];
* Danh sách liên kết *
- Ưu điểm:
✓ Các phần tử không cần liên tục, khắc phục tình trạng phân mảnh
bộ nhớ
✓ Không cần cấp phát trước số lượng như bên mảng, dữ liệu luôn được ở trạng thái động (khi nào cần thì cứ thêm vào như bình thường, không cần phải cấp lại bộ nhớ như bên mảng)
✓ Các thao tác thêm/xóa không cần phải lùi lại hay tiến lên các phần tử từ vị trí đó trở xuống như bên mảng mà ta chỉ cần bẻ lại mối liên kết giữa 2 Node là được => chạy nhanh hơn bên mảng trong trường hợp này
- Nhược điểm:
✓ 1 Node trong danh sách liên kết sẽ lưu 2 thông tin đi kèm đó là Data (dữ liệu chứa) và 1 con trỏ pNext để tạo mối liên kết giữa Node này với Node kia
=> đòi hỏi bộ nhớ phải tốn nhiều hơn mảng
✓ Muốn truy xuất tới 1 Node cụ thể (ví dụ Node nằm ở đoạn giữa danh sách) thì ta phải đi từ đầu cho đến đó hoặc đi từ cuối cho đến đó
✓ Nếu như danh sách liên kết mà vô tình tại 1 Node nào đó ta bị hư
là hư hết cả danh sách bởi vì nó truyền dữ liệu liên tục cho nhau
Trang 33
1 ĐỊNH NGHĨA danh sách liên kết đơn
2 Hàm KHỞI TẠO danh sách rỗng
3 Hàm kiểm tra có phải danh sách RỖNG
4 Tạo một NÚT có thành phần dữ liệu là x
5 Chèn một phần tử vào danh sách
• Chèn phần tử vào ĐẦU danh sách
• Chèn phần tử vào CUỐI danh sách
• Chèn phần tử vào SAU phần tử q cho trước trong danh sách
• Chèn phần tử vào TRƯỚC phần tử q cho trước trong danh sách
6 Hủy một 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 một phần tử có KHÓA x
• Hủy phần tử vào SAU phần tử q cho trước trong danh sách
• Hủy phần tử vào TRƯỚC phần tử q cho trước trong danh sách
7 Duyệt danh sách
8 Tìm một phần tử trong danh sách theo một điều kiện cho trước
9 Sắp xếp danh sách
❖ Thay đổi thành phần dữ liệu trong các phần tử để tạo danh sách có thứ tự
Uư điểm: Cài đặt đơn giản,tương tự như mảng
Nhược điểm: Thêm vùng nhớ để hoán vị Nên phù hợp với kích thước dữ liệu nhỏ
Ví dụ: SelectionSort
❖ Thay đổi thành phần liên kết các phần tử
Uư điểm: Không phụ thuộc vào kích thước bản chất dữ liệu, thao tác sắp xếp nhanh hơn
Nhược điểm: Cài đặt phức tạp
Ví dụ: QuickSort, MergeSort, RadiSort 10.Hủy danh sách liên kết
❖
Trang 44
❖
1 ĐỊNH NGHĨA danh sách liên kết đơn
NULL NODE(nút)
LIST(danh sách)
❖ Danh sách liên kết đơn là: Trong danh sách mà mỗi phần
tử liên kết với phần tử đứng liền sau trong danh sách
❖ Mỗi phần tử trong danh sách liên kết đơn là một cấu trúc
có hai thành phần
chỉ
• 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
Ví dụ 1: Khai báo cấu trúc danh sach liên kết đơn PHANSO
BG:
struct PhanSo {
int Tu;
int Mau;
};
typedef struct PhanSo PS;
struct Node {
Trang 55
PS Info; //PS là kiểu Data:int, Diem
struct Node*pNext;
};
typedef struct Node NODE;
struct List {
NODE*pHead; //Lưu địa chỉ phần tử đầu tiên NODE*pTail; //Lưu địa chỉ phần tử cuối };
typedef struct List LIST;
Ví dụ 2: Khai báo cấu trúc danh sach liên kết đơn HOCSINH
BG:
struct SinhVien //Phần Data cảu Info {
char Ten[30];
int MSSV;
};
typedef struct SinhVien SV;
struct Node {
SV Info;
struct Node*pNext;
};
typedef struct Node NODE;
struct List {
NODE*pHead; //Lưu địa chỉ phần tử đầu tiên NODE*pTail; //Lưu địa chỉ phần tử cuối };
typedef struct List LIST;
2 Hàm KHỞI TẠO danh sách rỗng
Thao tác thực hiện: Thao tác này sẽ khởi tạo một danh sách đơn rỗng ban đầu Địa chỉ nút đầu với nút cuối đều là NULL void CreateList(LIST &l)
Trang 66
{
l.pHead = NULL;
l.pTail = NULL;
}
3 Hàm kiểm tra có phải danh sách RỖNG
int IsEmpty(LIST l) {
if (l.pHead == 0)
return 1; //danh sách rỗng return 0; //danh sách không rỗng }
4 Tạo một NÚT có thành phần dữ liệu là x
Thao tác thực hiện: Thao tác này sẽ tạo một phần tử mới và gán thành phần dữ liệu là x, chuẩn bị cho thao tác thêm phần tử vào danh sách Hàm trả về địa chỉ phần tử mới
NODE *CreateNode(KDL x) {
NODE *p = new NODE; //Cấp phát bộ nhớ
if (p == NULL) return NULL; //Hoặc exit(1); danh sách rỗng p->Info = x;//gán dữa liệu cho nút
p->pNext = NULL;
return p;
}
5 Chèn một phần tử vào danh sách
• Chèn phần tử vào ĐẦU danh sách
Trang 77
6 nf
x
.
pHead
p=pHead
p->pNext=pHead
• Thuật toán
Nếu List rỗng thì + pHead = p;
+ pTail = pHead;
Ngược lại + p->pNext = pHead;
+ pHead = p
• Hàm chèn một phần tử vào ĐẦU danh sách AddHead
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;
} }
• Chèn phần tử vào CUỐI danh sách
Trang 88
x
pHead
pTail->pNext=p
pTail
pTail=p
• Thuật toán Nếu danh sách rỗng thì + pHead = p;
+pTail = pHead;
Ngược lại + pTail->pNext = p;
+pTail = p
• Hàm chèn một phần tử vào CUỐI danh sách AddTail
void AddTail(LIST &l, Node *p) {
if (l.pHead == NULL) {
l.pHead = p;
l.pTail = l.pHead;
} else {
l.pTail->pNext = p;
l.pTail = p;
} }
• Chèn phần tử vào SAU phần tử q cho trước trong danh sách
Trang 99
x
pTail
q pHead
• Thuật toán Nếu (q!= NULL) thì B1: p->pNext = q->pNext B2:
+ q->pNext = p + nếu q = pTail thì pTail = p
• Hàm chèn một phần tử vào SAU nút q danh sách InsertAfterQ
void InsertAfterQ(LIST &l, NODE *p, NODE *q) {
if (q != NULL) {
p->pNext = q->pNext;
q->pNext = p;
if (l.pTail == q) l.pTail = p;
} else AddHead(l, p);// thêm q vào đầu list }
• Chèn phần tử vào TRƯỚC phần tử q cho trước trong danh sách
6 Hủy một phần tử trong danh sách
• Hủy phần tử ĐẦU danh sách
Trang 1010
6 nf
pHead=pHead->pNext
p=pHead
• Thuật toán
Nếu (pHead != NULL) thì B1 : p = pHead
B2 :pHead = pHead->pNext delete p;
B3 : Nếu pHead == NULL thì
pTail = NULL
• Hàm hủy phần tử ĐẦU danh sách
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 1;
} return 0;
• Hủy phần tử CUỐI danh sách
Trang 1111
• Hủy một phần tử có KHÓA x
• Thuật toán
*Bước 1: Tìm phần tử p có khoá bằng x, và q đứng trước
p
*Bước 2 : Nếu(p != NULL) thì //tìm thấy phần tử có khoá bằng x
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á
• Hàm hủy một phần tử có KHÓA x
int RemoveX(List &l, int x) {
Node *p, *q = NULL; p = l.Head;
while ((p != NULL) && (p->Info != x)) //tìm {
q = p;
p = p->Next;
}
if (p == NULL) //không tìm thấy phần tử có khoá bằng x
return 0;
if (q != NULL)//tìm thấy phần tử có khoá bằng x
DeleteAfterQ(l, q, x);
else //phần tử cần xoá nằm đầu List
RemoveHead(l, x);
return 1;
}
• Hủy phần tử vào SAU phần tử q cho trước trong danh sách
Trang 1212
6 nf
pTail
pHead
7 6f
p=q->pNext
q=pNext=p->pNext
• Thuật toán
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
• Hàm hủy phần tử vào SAU phần tử q cho trước trong danh sách
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;
} return 1;
} else
return 0;
}
Trang 1313
• Hủy phần tử vào TRƯỚC phần tử q cho trước trong danh sách
7 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ảo điều kiện
+ Hủy toàn bộ danh sách
• Thuật toán
• *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 + xử lý phần tử p
+ p = p->pNext;// qua phần tử kế
• Ví dụ
//Cài đặt xuất các phần tử trong dslk void Output(LIST l)
{
NODE *p = l.pHead;
while (p != NULL) {
cout << p->Info;
p = p->pNext;
cout << " "; //Cac cac phan tu }
}
//Nhập danh sách liên kết đơn void Input(LIST&l)
{
int n;
cout << "Nhap vao n = ";
cin >> n;
Trang 1414
CreateList(l);
for (int i = 1; i <= n; i++) {
int x;
cout << "Nhap so nguyen " << " ";
cin >> x;
NODE *p = CreateNode(x);
if (p != NULL)
AddHead(l, p);
} }
8 Tìm một phần tử trong danh sách theo một điều kiện cho trước
• Thuật toá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 1: p = pHead; // địa chỉ của phần tử đầu trong list đơn
**Bước 2:
Trong khi p!= NULL và p->Info != x
p = p->pNext; // xét phần tử kế
**Bước 3:
+Nếu p != NULL thì p lưu địa chỉ của nút có Info = x
+ Ngược lại : Không có phần tử cần tìm
• Hàm tìm
Node *Search(LIST l, Data x) {
Node *p;
p = l.pHead;
while ((p != NULL) && (p->Info != x))
p = p->pNext;
return p;
}
Node * Search (LIST l, int x) {
Trang 1515
Node * p ;
p = l pHead ;
while ( p != NULL ) {
if ( p -> Info == x)
return p ;
p = p -> pNext ; }
return NULL ; }
9 Sắp xếp danh sách
• 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<p->Info)
min = q;
q = q->pNext;
} Swap(min->Info, p->Info);
p = p->pNext;
} }
• 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 :
Trang 1616
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
• void QuickSort(List &l) {
NODE *p;
NODE *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ự DS rỗng hoặc có một phần
tử
else {
CreateList(L1); //Hàm tạo ds L1 rỗng CreateList(L2); //Hàm tạo ds L2 rỗng
X = l.pHead;
l.pHead = X->pNext;
while (l.pHead != NULL) //tách L = L1 + L2 {
p = l.pHead;
l.pHead = p->pNext;
p->pNext = NULL;
if (p->Info <= X->Info)
AddHead(L1, p); //Hàm thêm pt p vào L1 else
AddHead(L2, p); //Hàm thêm pt p vào L2 }
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 vào danh sách
{
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
Trang 1717
l.pTail = X;
} }
• Thuật tóan sắp xếp Merge Sort
* *Bước 1: Phân phối luân phiên từng đường chạy của xâu L vào 2 xâu con L1 và L2
* *Bước 2 : Nếu L1 != NULL thì Merge Sort(L1)
* *Bước 3 : Nếu L2 != NULL thì Merge Sort(L2)
* *Bước 4 : Trộn L1 và L2 đã sắp xếp lại ta có xâu L
đã được sắp xếp Không tốn thêm không gian lưu trữ cho các dãy phụ
10.Hủy danh sách liên kết đơn
• Thuật toán
*Bước 1:
Trong khi(danh sách chưa hết) thực hiện
• B11 :
p = pHead;
pHead = pHead->pNext; // cập nhật pHead
• B12 : Hủy p
*Bước 2 : pTail = NULL; // bảo toàn tính nhất quán khi xâu rỗng
• Hàm hủy danh sách liên kết đơn
void RemoveList(List &l) {
NODE *p;
while (l.pHead != NULL)//còn phần tử trong List {
p = l.pHead;
l.pHead = p->pNext;
delete p;
} }
Trang 1818
Mảng môt chiều Danh sách liên kết đơn
For(int i=0;i<n;i++) For(NODE
*p=l.pHead;p!=NULL;p=p->pNext) For(int i=0;i<n-1;i++) For(NODE
*p=l.pHead;p!=l.pTail;p=p->pNext) Int i=0
While (i<n)
{
…
i++;
}
NODE *p=l.pHead;
While (p!=NULL) {
……
p=p->pNext;
}