Các loại danh sách liên kế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 kép: Mỗi phần tử liên kết với phần tử đứng trư
Trang 1CHƯƠNG 4
DANH SÁCH ( LIST_ADT)
DANH SÁCH ( LIST_ADT)
Trang 2Mục tiêu
Sau khi học xong chương này, sinh viên:
Hiểu thế nào là cấu trúc dữ liệu động
Cài đặt các kiểu dữ liệu bằng ngôn ngữ lập trình
cụ thể
Ứng dụng được các kiểu dữ liệu trừu tượng trong bài toán thực tế
Trang 3 Cài đặt danh sách liên kết kép
Trang 4Biến Tĩnh
Trang 6Biến Động
gọi
Trang 7 Biến thuộc kiểu con trỏ Tp là biến mà giá trị của nó
là địa chỉ cuả một vùng nhớ ứng với một biến kiểu
T, hoặc là giá trị NULL.
Khai báo trong C :
typedef int *intpointer;
Bản thân biến con trỏ là không động
Dùng biến con trỏ để lưu giữ địa chỉ của biến động
=> truy xuất biến động thông qua biến con trỏ
Trang 8 Hàm free(p) huỷ vùng nhớ cấp phát bởi hàm
malloc hoặc calloc do p trỏ tới
new do p trỏ tới
Trang 9Biến động có địa chỉ 0xFF
Trang 10Kiểu danh sách
Danh sách = { các phần tử có cùng kiểu}
Danh sách là một kiểu dữ liệu tuyến tính:
Là kiểu dữ liệu quen thuộc trong thực tế:
Trang 12Danh sách liên kết ngầm (mảng)
address(i) = address(1) + (i-1)*sizeof(T)
Ưu điểm : Truy xuất trực tiếp, nhanh chóng
Trang 13Liên kết tuờng minh (Danh sánh liên kết)
CTDL cho một phần tử
Trang 14Các loại danh sách liên kế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 kép: 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 Vòng: Phần tử cuối danh sách
liên với phần tử đầu danh sách
Trang 15Các loại danh sách liên kết (tt)
Danh sách liên Vòng: Phần tử cuối danh sách liên với
Trang 17Khái niệm về danh sách (List)
Là tập hợp hữu hạn các phần tử có cùng kiểu
Kiểu chung được gọi là kiểu phần tử (element type)
Ta thường biểu diễn dạng: a 0 , a 1 , a 2 , a 3 , , a n-1
Nếu n=0: danh sách rỗng
n>0: phần tử đầu tiên là a 0 , phần tử cuối cùng là a n-1
Độ dài của danh sách: số phần tử của danh sách
Các phần tử trong danh sách có thứ tự tuyến tính theo vị trí xuất hiện Ta nói a i đứng trước a i+1 (i=0 n-1)
Trang 18Makenull_List(L) Khởi tạo 1 ds L rỗng
FirstList(L) Cho kết quả là vị trí phần tử đầu tiên trong
dsEndList(L) Trả về vị trí sau phần tử cuối cùng trong dsEmpty_List(L) Kiểm tra ds L có rỗng không
Full_List(L) Kiểm tra ds L có đầy không
Next(p,L) Cho kết quả là vị trí phần tử đứng sau
phần tử pRetrieve(p,L) Truy xuất nút tại vị trí p trong ds
Previous(p,L) Cho kết quả là vị trí đứng trước ptu p
Trang 19Delete_List(p,L) Xóa phần tử tại vị trí p trong ds
Printlist(L) Duyệt tất cả các nút trong ds
Selectionsort(L) Sắp xếp ds theo thứ tự tăng
Linearsearch(L,x) Tìm tuyến tính, trả về vị trí tìm thấy, else -1Binarysearch(L,x) Tìm kiếm nhị phân
Trang 22typedef struct List {
int Last; //giữ độ dài của ds
int nodes[Maxlength]; /*mỗi nút là 1 ptử trên
mảng 1 chiều*/ };
List L;
Trang 23Chỉ số Mảng
Trang 24return L.Last==0;
}
Trang 25return L->Last;
}
Trang 26return L->Last==Maxlength; }
Trang 27return 0;
}
Trang 28return L.Last;
}
Trang 29Truy xuất nút tại vị trí p trong ds
int Retrieve(int p, List *L) {
if(p<0 || p>=Listsize(L))
cout<<“Vi tri khong hop le”;
else if(empty(L)) cout<<“Danh sach rong”; else return(L->nodes[p]);
}
Trang 32Tóm lại, để chèn x vào vị trí p của L, ta làm như sau:
– Nếu mảng đầy thì thông báo lỗi
– Ngược lại, nếu vị trí p không hợp lệ thì báo lỗi
– Ngược lại:
• Dời các phần tử từ vị trí p đến cuối danh sách ra sau một vị trí
• Đưa phần tử mới x vào tại vị trí p
• Độ dài danh sách tăng 1
Trang 33for(int i=Listsize(L); i>p; i ) L->nodes[i]=L->nodes[i-1];
Trang 35Tóm lại, để xóa phần tử tại vị trí p của L, ta làm như sau:
• Nếu p là một vị trí không hợp lệ thì thông báo lỗi
Trang 36for(int i=p;i<Listsize(L);i++)
L->nodes[i]=L->nodes[i+1];
L->Last ;
} }
Trang 41{ int i, j, min, vitrimin;
for(i=0; i<L->Last-1; i++) {
min=L->nodes[i]; vitrimin=i;
for(j=i+1; j<L->Last; j++)
if(min>L->nodes[j]) {
min=L->nodes[j];
vitrimin=j;
} L->nodes[vitrimin]=L->nodes[i]; //hoan doi L->nodes[i]=min;
} }
Trang 43cout<<“cac phan tu trong ds la: ”<<endl;
printlist(L);
getch(); }
Trang 455 Viết chương trình ALTEST5.CPP để nhập 1 ds từ bàn phím, sau đó xóa một phần tử đầu tiên có nd x trong ds (x: nhập)
và hiển thị ds sau khi xóa
Trang 46• Trong cài đặt, để một ô có thể chỉ đến ô khác ta cài đặt mỗi
ô là một mẩu tin (record, struct) có hai trường: trường info giữ giá trị của các phần tử trong danh sách; trường next là
một con trỏ giữ địa chỉ của ô kế tiếp.Trường next của phần
tử cuối trong danh sách chỉ đến một giá trị đặc biệt là
NULL Cấu trúc như vậy gọi là danh sách cài đặt bằng con
trỏ hay danh sách liên kết đơn hay ngắn gọn là danh sách liên kết
Trang 47Để quản lý danh sách ta chỉ cần một biến giữ địa chỉ
ô chứa phần tử đầu tiên của danh sách, tức là một con trỏ trỏ đến phần tử đầu tiên trong danh sách Biến này gọi là
chỉ điểm đầu danh sách (Header)
Trang 48Ở đây ta cần phân biệt rõ giá trị của một phần tử và
vị trí (position) của nó trong cấu trúc trên Ví dụ giá trị của phần tử đầu tiên của danh sách là a1, Trong khi vị trí của
nó là địa chỉ của ô chứa nó, tức là giá trị nằm ở trường next của ô Header Giá trị và vị trí của các phần tử của danh sách được mô tả như sau:
Trang 49int info; //Chứa nội dung của phần tử
node* Next; //con trỏ chỉ đến phần tử kế tiếp
};
typedef struct node *Pos;
typedef Pos List;
Trang 50-Tác vụ MakeNull_List: Khởi tạo ds rỗng
Ta dùng Header như là một biến con trỏ có kiểu giống như kiểu của một ô chứa một phần tử của danh sách
Tuy nhiên trường info của Header không bao giờ được dùng, chỉ có trường Next dùng để trỏ tới ô chứa phần tử đầu tiên của danh sách
Vậy nếu như danh sách rỗng thì trường ô Header vẫn
phải tồn tại và ô này có trường next chỉ đến NULL
void MakeNull_List(List *Header){
(*Header)= (node*)malloc(sizeof(node));
(*Header)->Next=NULL;
}
Trang 51- Tác vụ Empty_List: Kiểm tra một danh sách rỗng
Danh sách rỗng nếu như trường next trong ô Header trỏ tới
NULL
int Empty_List(List L){
}
Trang 52Pos p=First_List(L);
while(p->Next!=NULL) p=p->Next;
return p;
}
Trang 54Minh họa thuật toán chèn vào vị trí p
5
4
7T
Trang 55- Tác vụ Delete_List: Xóa phần tử ra khỏi ds liên kết
Muốn xóa một phần tử khỏi danh sách ta cần biết vị trí p của phần tử muốn xóa trong danh sách L Nối kết lại các con trỏ bằng cách cho p trỏ tới phần tử đứng sau phần tử thứ p
void Delete_List(Pos p, List *L)
(1) T=p->Next (2) p->Next=T->Next
(3) free(T)
Trang 57- Tác vụ Locate: Định vị một phần tử trong danh sách liên kết
Để định vị phần tử x trong danh sách L ta tiến hành tìm từ đầu danh sách (ô header) nếu tìm thấy thì vị trí của phần tử đầu tiên được tìm thấy sẽ được trả về nếu không thì ENDLIST(L) được trả về Nếu x có trong sách sách và hàm Locate trả về vị trí p mà trong đó ta
Trang 59- Tác vụ Retrieve: Xác định nội dung phần tử:
Nội dung phần tử đang lưu trữ tại vị trí p trong danh sách L là p->next->info Do đó, hàm sẽ trả về giá trị p->next-
>info nếu phần tử có tồn tại, ngược lại phần tử không tồn tại
(p->next=NULL) thì hàm không xác định
int Retrieve(Pos p, List L){
if (p->Next!=NULL) return p->Next->info;
}
Trang 60} }
Trang 62N 6
5f 7
Header
4f 4
Trang 63Sắp xếp danh sách
Cách 2: Thay đổi thành phần 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)
3f
N 6
5f 7
Trang 64Ưu, nhược điểm của 2 cách tiếp cận
Thay đổi thành phần Info (dữ liệu)
Ưu: Cài đặt đơn giản, tương tự như sắp xếp mảng
Trang 65– Nhập vào một danh sách các số nguyên
– Hiển thị danh sách vừa nhập ra màn hình
– Thêm phần tử có nội dung x vào danh sách sau vị trí p (trong
đó x và p được nhập từ bàn phím)– Xóa phần tử đầu tiên có nội dung x (nhập từ bàn phím) ra khỏi danh sách
Trang 67ds mà ko cần trở về nút đầu tiên như ds lk thông thường.
Nhược điểm: Ko biết khi nào đã duyệt qua toàn bộ phần tử của ds Cho nên trong quá trình duyệt kiểm tra xem trở về nút ban đầu hay chưa?
Trang 68Chúng ta qui ước dùng thêm con trỏ plist chỉ nút cuối của ds
lk vòng Thứ tự của các nút trong ds lk vòng như sau:
-Con trỏ plist chỉ nút cuối-Nút cuối chỉ nút đầu tiên-Nút đầu tiên chỉ nút thứ 2
plist
info next info next info next info next
Trang 69typedef struct node *List;
Trang 70List p;
p=(List) malloc(sizeof(struct node));
return p;
}
Trang 71}
Trang 73p=p->next;
}return i;
}
Trang 77}
Trang 79{ p==*plist;
*plist=NULL;
freenode(p); }p=*plist; //p la nut can xoa
*plist=nodepoiter(plist,listsize(plist)-2);
(*plist)->next=p->next;
freenode(p);
}}
Trang 80if(empty(plist)) cout<<“DS rong”;
else { if(p==NULL) cout<<“Nut ko hien huu”;
else {
if(p==plist) cout<<“Nut p la nut cuoi”;
else { if( p->next==*plist) cout<<“dung tac vu dellast”; else{ q=p->next;
p->next=q->next;
freenode(q);
}
} } } }
Trang 82if(empty(plist)) return NULL;
p=(*plist)->next; //p chi nut dauif(x==p->info)
Trang 83} }
Trang 84– Nhập vào một danh sách các số nguyên
– Hiển thị danh sách vừa nhập ra màn hình
– Thêm phần tử có nội dung x vào danh sách ngay vị trí i (trong
đó x và i được nhập từ bàn phím)– Xóa phần tử đầu tiên có nội dung x (nhập từ bàn phím) ra khỏi danh sách
- Sắp xếp ds theo thứ tự tăng dần
- Chèn phần tử x vào ds theo đúng thứ tự (x: nhập từ bp)
- Xóa các phần tử trùng lắp trong ds
Trang 85Danh sách liên kết kép
Một số ứng dụng đòi hỏi chúng ta phải duyệt danh sách theo cả hai chiều một cách hiệu quả Chẳng hạn cho phần tử X cần biết ngay phần tử trước X và sau X một cách mau chóng
Mỗi phần tử liên kết với phần tử đứng trước (left) và sau (right) nó trong danh sách
Hình vẽ minh họa danh sách liên kết kép:
Trang 86Danh sách liên kết kép
plist là con trỏ chỉ nút đầu của ds lk kép, liên kết right của nút đầu chỉ nút thứ 2
Chúng ta có thể duyệt ds lk kép: duyệt xuôi lần theo lk right, duyệt ngược lần theo lk left
Nút cuối của ds kép có trường right chì NULL, nút đầu của ds lk kép có trường left chỉ NULL
Khi khởi động ds lk kép con trỏ plist được gán giá trị NULL
Khi ds rỗng thì ko thể thực hiện tác vụ xóa nút
Nếu gọi nút p là con trỏ chỉ đến 1 nút trong ds lk kép, thì left(p) là con trỏ chỉ nút trước, và right(p)
là con trỏ chỉ nút sau left(right(p))=p=right(left(p))
Trang 89Cài đặt các tác vụ trên ds lk kép
void MakeNull_List(List *plist)
{
*plist = NULL;
}
int Empty_List(List *plist)
Trang 90n++:
} return n;
Trang 91Cài đặt các tác vụ trên ds lk kép
List nodepointer(List *plist, int i)
Trang 92Cài đặt các tác vụ trên ds lk kép
Tác vụ push: Thêm nút có nd x vào đầu ds lk
void push(List *plist, int x)
(*plist)->left=p;
p->left=NULL;
*plist = p;
} }
Trang 95q->left=p p->right=q
Trang 96Cài đặt các tác vụ trên ds lk kép
Tác vụ insertleft Thêm nút có nd x vào trước nút p
void insertleft(List p, int x)
q->right=p;
p->left=q;
} }
Trang 97q>right=p p->left=q
Trang 98Cài đặt các tác vụ trên ds lk kép
Tác vụ pop: Xóa nút đầu trong ds lk kép
void pop(List *plist)
{
List p;
if(empty(plist)) cout<<“danh sach rong”;
else { if((*plist)->right==NULL) //ds co 1 nut { p=*plist;
*plist = NULL;
freenode(p);
} p=*plist; (*plist)=p->right;
(*plist)->left=NULL;
freenode(p);
} }
Trang 99Cài đặt các tác vụ trên ds lk kép
Tác vụ delnode: Xóa nút p trong ds lk kép
void delnode(List *plist, List p)
{
List q,r;
if(p==NULL) cout<<“Nut khong hien huu”;
else { if(empty(plist)) cout<<“danh sach rong”;
else { if(p==*plist) pop(plist); //xoa nut dau q=p->left; //nut truoc
r=p->right; //nut sau r->left=q;
q->right=r freenode(p);
} } }
Trang 100Cài đặt các tác vụ trên ds lk kép
void printright(List *plist)
{
List p;
else { p=*plist;
while(p != NULL) {
cout<<p->info<<“ “;
p=p->right;
} } }
Trang 101Cài đặt các tác vụ trên ds lk kép
void printleft(List *plist)
{
List p;
else { p=nodepointer(plist,listsize(plist)-1); //nut cuoi while(p != NULL)
{ cout<<p->info<<“ “;
p=p->left;
} } }
Trang 103Hết chương 4