Ví du: Hồ sơ các học sinh của một trường ñược tổ chức thành danh sách gồm nhiều hồ sơ của từng học sinh; số lượng học sinh trong trường có thể thay ñổi do vậy cần có các thao tác thêm, h
Trang 13.1 Kiểu dữ liệu con trỏ 3.2 Danh sách liên kết (link list) 3.3 Danh sách liên kết ñơn
3.4 Sắp xếp danh sách
Chương 3:
DANH SÁCH LIÊN KẾT
Trang 23.1 Kiểu Dữ Liệu Con Trỏ
3.1.1 Biến không ñộng
3.1.2 Kiểu con trỏ
3.1.3 Biến ñộng
Trang 3Dùng ñề lưu trữ những ñối tượng dữ liệu ñược sử dụng không có nhu cầu thay ñổi và kích thước, số lượng
• ðược khai báo tường minh
• Tồn tại trong phạm vi khái báo
• Kích thước không thay ñổi trong suốt quá trình sống
Ví dụ:
int a;
char b[10];
3.1.1 Biến không ñộng
Trang 4Kiểu con trỏ là kiểu cơ sở dùng lưu ñịa chỉ của một ñối tượng dữ liệu khác.
Biến thuộc kiểu con trỏ là biến mà giá trị của nó là ñịa chỉ một vùng nhớ của một biến hoặc là giá trị Null Tùy vào loại con trỏ gần (near pointer) hay con trỏ xa (far pointer) mà kiểu dữ liệu con trỏ có các kích thước khác nhau:
+ Con trỏ gần: 2 bytes + Con trỏ xa: 4 bytes
3.1.2 Kiểu con trỏ
Trang 5Cú pháp ñịnh nghĩa một kiểu con trỏ
typedef <kiểu con trò> *<kiểu cơ sở>;
Trang 6cout<<"\nGia tri cua bien a="<<a;
cout<<"\nGia tri cua bien b="<<b;
pa=&a;
pb=&b;
cout<<"\nDia chi cua o nho con tro pa tro toi="<<pa;
cout<<"\nDia chi cua o nho con tro pb tro toi="<<pb;
cout<<"\nNoi dung cua o nho con tro pa tro toi="<<*pa;
cout<<"\nNoi dung cua o nho con tro pb tro toi="<<*pb;
*pa=20; /* Thay doi giá tr cua *pa*/
*pb=20; /* Thay doi giá tri cua *pb*/
cout<<"\nGia tri moi cua bien a="<<a;
cout<<"\nGia tri moi cua bien b="<<b;
}
Trang 7Trong trường hợp, tại thời ñiểm biên dịch không thể xác ñịnh trước kích thước chính xác của ñối tượng dữ liệu(do chúng phụ thuộc vào ngữ cảnh) Các ñối tượng
dữ liệu này ñược khai báo như biến ñộng.
Biến ñộng là những biến thỏa:
3.1.3 Biến ñộng
Không ñược khai báo tường minh
ðược cấp phát/giải phóng bộ nhớ khi yêu cầu.
Các biến này không theo qui tắc phạm vi
Vùng nhớ của biến ñược cấp phát trong Heap
Trang 8Các thao tác trên biến ñộng:
Tạo biến ñộng và cho con trỏ p trỏ ñến:
Các hàm cấp phát bộ nhớ:
void* malloc(size); // trả về con trỏ chỉ ñến một vùng
// nhớ size byte vừa ñược cấp phát
void* calloc(n,size);// trả về con trỏ chỉ ñến một vùng
// nhớ vừa ñược cấp phát gồm n
//phần tử,mỗi phần tử có kích
//thước size byte
new // hàm cấp phát bộ nhớ trong C++
Trang 113.2 Danh Sách Liên Kết
3.2.1 Ðịnh nghĩa 3.2.2 Các hình thức tổ chức danh sách
Trang 12Ox = {Tạo danh sách; Tìm 1 phần tử trong danh sách; Chèn một phần tử vào danh sách; Huỷ một phần
tử khỏi danh sách ; Liệt kê danh sách, Sắp xếp danh sách }
Trang 13Ví du: Hồ sơ các học sinh của một trường ñược tổ chức thành danh sách gồm nhiều hồ sơ của từng học sinh; số lượng học sinh trong trường có thể thay ñổi
do vậy cần có các thao tác thêm, hủy một hồ sơ; ñể phục vụ công tác giáo vụ cần thực hiện các thao tác tìm hồ sơ của một học sinh, in danh sách hồ sơ
Trang 143.2.2 Các hình thức tổ chức danh sách
Mối liên hệ giữa các phần tử ñược thể hiện ngầm:
Mỗi phần tử trong danh sách ñược ñặc trưng bằng chỉ số
Cặp phần tử xi, xi+1 ñược xác ñịnh là kế cận
Với hình thức tổ chức này, các phần tử của danh sách phải lưu trữ liên tiếp trong bộ nhớ, công thức xác ñịnh ñịa chỉ phần tử thứ I là.
Cách biểu diễn này cho phép truy xuất ngẫu nhiên, ñơn giản và nhanh chóng ñến một phần tử bất kỳ trong danh sách, nhưng lại hạn chế về mặt sử dụng bộ nhớ bị lãng phí.
Trang 15Mối liên hệ giữa các phần tử thể hiện tường minh:
Mỗi phần tử ngoài các thông tin còn chứa một liên kết (ñịa chỉ) ñến phần tử kế trong danh sách nên còn ñược gọi là danh sách móc nối
Với hình thức này các phần tử trong danh sách không cần phải lưu trữ kế cận trong bộ nhớ nên khắc phục ñược các khuyết ñiểm của hình thức tổ chức mảng, nhưng việc truy xuất ñến một phần tử ñòi hỏi phải thực hiện truy xuất qua một số phần tử khác
Trang 16Có 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
Danh sách liên kết kép
Danh sách liên kết vòng
Trang 173.3 Danh Sách Liên Kết ðơn
3.3.1 Tổ chức danh sách ñơn theo cách cấp phát liên kết 3.3.2 Các thao tác cơ bản trên danh sách ñơn
Trang 183.3.1 Tổ chức danh sách ñơn theo cách cấp phát liên kết
Mỗi phần tử là một cấu trúc chứa 2 thông tin :
- Thành phần dữ liệu: Lưu trữ các thông tin về bản thân
phần tử
- Thành phần mối liên kết: lưu trữ ñịa chỉ của phần tử
kế tiếp trong danh sách, hoặc lưu trữ giá trị NULL nếu
Trang 19Ví dụ: Ðịnh nghĩa danh sách ñơn lưu trữ hồ sơ SV
typedef struct SinhVien {
Trang 20Nếu biết ñược ñịa chỉ của phần tử ñầu tiên trong danh sách ñơn thì có thể dựa vào thông tin pNext của nó ñể truy xuất ñến phần tử thứ 2, thứ 3 ðể quản lý một xâu ñơn chỉ cần biết ñịa chỉ phần tử ñầu xâu Con trỏ Head dùng ñể lưu trữ ñịa chỉ phần tử ñầu xâu
Trang 213.3.2 Các thao tác cơ bản trên danh sách ñơn
Trang 22Chèn một phần tử vào ñầu danh sách
Bắt ñầu:
Nếu Danh sách rỗng Thì B11 : Head = new_elelment;
B12 : Tail = Head;
Ngược lại B21 : new_ele ->pNext = Head;
B22 : Head = new_ele ;
Trang 23void AddFirst(LIST &l, NODE* new_ele) {
if (l.pHead==NULL) //Xâu rỗng { l.pHead = new_ele; l.pTail = l.pHead;
} else { new_ele->pNext = l.pHead;
l.pHead = new_ele;
} } NODE* InsertHead(LIST &l, Data x) { NODE* new_ele = GetNode(x);
if (new_ele ==NULL) return NULL;
if (l.pHead==NULL) { l.pHead = new_ele; l.pTail = l.pHead;
}
Trang 24Chèn một phần tử vào cuối danh sách
Bắt ñầu :Nếu Danh sách rỗng Thì B11 : Head = new_elelment;
B12 : Tail = Head;
Ngược lại B21 : Tail ->pNext = new_ele;
B22 : Tail = new_ele ;
Trang 25void AddTail(LIST &l, NODE *new_ele) {
if (l.pHead==NULL) {
l.pHead = new_ele; l.pTail = l.pHead;
} else {
l.pTail->Next = new_ele;
l.pTail = new_ele;
} } NODE* InsertTail(LIST &l, Data x) {
NODE* new_ele = GetNode(x);
if (new_ele ==NULL) return NULL;
if (l.pHead==NULL) { l.pHead = new_ele; l.pTail = l.pHead;
}
Trang 26Chèn một phần tử vào sau phần tử q
Bắt ñầu : Nếu ( q != NULL) thìB1: new_ele -> pNext = q->pNext;
B2: q->pNext = new_ele
Trang 27void AddAfter(LIST &l,NODE *q, NODE* new_ele) {
if ( q!=NULL) { new_ele->pNext = q->pNext;
q->pNext = new_ele;
if(q == l.pTail) l.pTail = new_ele;
}else //chèn vào ñầu danh sách AddFirst(l, new_ele);
}
void InsertAfter(LIST &l,NODE *q, Data x){
NODE* new_ele = GetNode(x);
if (new_ele ==NULL) return NULL;
if ( q!=NULL) { new_ele->pNext = q->pNext;
q->pNext = new_ele;
Trang 28Tìm một phần tử trong danh sách ñơn
Xâu ñơn ñòi hỏi truy xuất tuần tự, áp dụng thuật toán tìm tuyến tính ñể xác ñịnh phần tử trong xâu có khoá k Thuật toán ñược thể hiện như sau :
Bước 1:
p = Head; //Cho p trỏ ñến phần tử ñầu danh sách Bước 2:
Trong khi (p != NULL) và (p->pNext != k ) thực hiện:
B21 : p:=p->Next;// Cho p trỏ tới phần tử kếBước 3:
Nếu p != NULL thì p trỏ tới phần tử cần tìm Ngược lại: không có phần tử cần tìm
Trang 29NODE *Search(LIST l, Data k) {
Trang 30Hủy một phần tử ñầu danh sách ñơn
Trang 31Data RemoveHead(LIST &l) {
NODE *p;
Data x = NULLDATA;
if ( l.pHead != NULL) {
p = l.pHead; x = p->Info;
l.pHead = l.pHead->pNext;
delete p;
if(l.pHead == NULL)l.pTail = NULL;
} return x;
}
Trang 32Hủy một phần tử ñứng sau phần tử q
Thu
Bắt ñầu:
Nếu (q!= NULL) thìB1: p = q->Next; // p là phần tử cần hủy B2: Nếu (p != NULL) thì // q không phải là cuối xâu
B21 : q->Next = p->Next; // tách p ra khỏi xâu B22 : free(p); // Hủy biến ñộng do p trỏ ñến
Trang 33void RemoveAfter (LIST &l, NODE *q) {
if ( q != NULL) {
p = q ->pNext ;
if ( p != NULL) {
if(p == l.pTail)l.pTail = q;
q->pNext = p->pNext;
delete p;
} }
Trang 34Hủy một phần tử có khóa k
Thu
Bước 1:
Tìm phần tử p có khóa k và phần tử q ñứng trước nó Bước 2:
Nếu (p!= NULL) thì // tìm thấy k Hủy p ra khỏi xâu tương tự hủy phần tử sau q;
Ngược lại
Báo không có k;
Trang 35int RemoveNode(LIST &l, Data k) {NODE *p = l.pHead;
if(p == l.pTail) l.pTail = q;
q->pNext = p->pNext;
delete p;
}else{
Trang 36Duyệ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 xử lý các phần tử của danh sách như:
- Ðếm các phần tử của danh sách,
- Tìm tất cả các phần tử thoả ñiều kiện,
- Huỷ toàn bộ danh sách (và giải phóng bộ nhớ)
Trang 37void ProcessList (LIST &l) {
NODE *p;
p = l.pHead;
while (p!= NULL) {
ProcessNode(p); // xử lý cụ thể tùy ứng dụng
p = p->pNext;
} }
Trang 383.4 Sắp Xếp Danh Sách
3.4.1 Các cách tiếp cận 3.4.2 Một số phương pháp sắp xếp trên danh sách
Trang 393.4.1 Các cách tiếp cận
DS có thứ tự là DS mà các phần tử ñược sắp xếp theo một thứ tự nào ñó dựa trên trường khoá Ðể sắp xếp một danh sách, có 2 phương án:
Phương án 1: Hoán vị nội dung các phần tử trong danh sách
Sử dụng các thuật toán sắp xếp như trên mảng, dựa trên việc hoán vị nội dung của các phần tử
Phương pháp này ñòi hỏi sử dụng vùng nhớ trung gian, số lần hoán vị có thể lên ñến bậc n2 Làm cho thao tác sắp xếp chậm không tận dụng ñược các ưu ñiểm của DSLK
Trang 40voidListSelectionSort (LIST &l){
NODE *min; // chỉ ñến phần tử có giá trị nhỏ nhất NODE *p,*q;
p = l.pHead;
while(p != l.pTail){
q = p->pNext; min = p;
while(q != NULL){
if(q->Info< min->Info )
min = q; // ghi nhận vị trí phần tử min hiện hành qq= q->pNext;
}// Hoán vị nội dung 2 phần tửHoanvi(min->Info, p->Info]);
p = p->pNext;
}
Ví dụ : Cài ñặt thuật toán sắp xếp Chọn trực tiếp
Trang 41Phương án 2: Thay ñổi các mối liên kết (trên vùng Next)
Thay ñổi trình tự móc nối của các phần tử sao cho tạo lập nên ñược thứ tự mong muốn,
Tuy nhiên thao tác trên các mọc nối thường sẽ phức tạp hơn làthao tác trực tiếp trên dữ liệu
Bước1: Khởi tạo danh sách mới Result là rỗng;
Bước2: Tìm trong danh sách cũ l phần tử nhỏ nhất;
Bước3: Tách min khỏi danh sách l;
Bước4: Chèn min vào cuối danh sách Result;
Bước5: Lặp lại bước 2 khi chưa hết danh sách Head;
Trang 42void ListSelectionSort2 (LIST &l){
p = q; q = q->pNext;
} if(minprev != NULL) minprev->pNext = min->pNext;
else l.pHead = min->pNext;
min->pNext = NULL;
AddTail(lRes, min);
}
l = lRes;
Trang 433.4.2 Một số phương pháp sắp xếp trên danh sách
Thuật toán Quick Sort
Bước 1: Chọn X là phần tử ñầu DS L làm phần tử cầm canh Loại X ra khỏi L
Bước 2: Tách DS L ra làm 2 DS L1 (gồm các phần tử nhỏ hơn hay 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ì Quick Sort (L1)
Bước 4: Nếu L2 != NULL thì Quick Sort (L2)
Bước 5: Nối L1, X, và L2 theo trình tự ta có DS L ñược sắp xếp
Trang 44Ví dụ
Chọn X = 4 làm phần tử cầm canh và tách L thành L1, L2:
Sắp xếp L1:
Trang 45Sắp xếp L2:
Chọn X = 6 làm phần tử cầm canh và tách L2 thanh L21, L22
Nối L12, X2, L22 thành L2:
Trang 46void ListQSort(LIST & l){
if(l.pHead == l.pTail) return;//ñã có thứ tự l1.pHead == l1.pTail = NULL; //khởi tạo l2.pHead == l2.pTail = NULL;
X = l.pHead; l.pHead = X->pNext;
while(l.pHead != NULL) //Tách l thành l1, l2;{
p = l.pHead;
l.pHead = p->pNext; p->pNext = NULL;
if (p->Info <= X->Info) AddTail(l1, p);
else AddTail(l2, p);
}
//Nối l1, X và l2 lại thành l ñã sắp xếp
if(l1.pHead != NULL) { l.pHead = l1.pHead; l1.pTail->pNext = X;
} else l.pHead = X;
X->pNext = l2;
if(l2.pHead != NULL) l.pTail = l2.pTail;
else l.pTail = X;
Trang 47Ngoài ra còn một số thuật toán khác như Merge
Sort, Radix Sort , Heap Sort