Bài giảng Cấu trúc dữ liệu và giải thuật - Chương 3: Danh sách (List) - Đỗ Ngọc Như Loan
Danh sách liên kết đơn
Danh sách liên kết đơn là một tập các nút biểu diễn cấu trúc dữ liệu gồm các đối tượng được sắp đặt theo một trật tự tuyến tính
Trật tự tuyến tính trong danh sách liên kết được xác định bởi các pointer kết hợp với mỗi đối tượng
Danh sách liên kết cung cấp một sự biểu diễn mềm dẻo và đơn giản cho các tập động, hỗ trợ các thao tác như tìm kiếm, chèn, xóa v.v
Bạn đang xem 20 trang mẫu của tài liệu "Bài giảng Cấu trúc dữ liệu và giải thuật - Chương 3: Danh sách (List) - Đỗ Ngọc Như Loan", để tải tài liệu gốc về máy hãy click vào nút Download ở trên
Tóm tắt nội dung tài liệu: Bài giảng Cấu trúc dữ liệu và giải thuật - Chương 3: Danh sách (List) - Đỗ Ngọc Như Loan
Danh sách (List) GV: Đỗ Ngọc Như Loan 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 Danh sách liên kết đơn Danh sách liên kết đơn là một tập các nút biểu diễn cấu trúc dữ liệu gồm các đối tượng được sắp đặt theo một trật tự tuyến tính Trật tự tuyến tính trong danh sách liên kết được xác định bởi các pointer kết hợp với mỗi đối tượng Danh sách liên kết cung cấp một sự biểu diễn mềm dẻo và đơn giản cho các tập động, hỗ trợ các thao tác như tìm kiếm , chèn, xóa v.v Ví dụ về một danh sách liên kết đơn Mỗi nút x trong DS lưu trữ một đối tượng có một khóa ( có thể có thông tin khác) và một pointer next chỉ đến nút ( đối tượng) kế tiếp Nếu next[x]= NULL, thì x là nút cuối cùng còn gọi là đuôi của danh sách Danh sách L là rỗng khi L = NULL Các thao tác trên danh sách liên kết ListInitialize (L) ListSearch (L, k) ListInsert (L, k) ListDelete (L, k) ListWalk (L) ĐỊNH NGHĨA DANH SÁCH LIÊN KẾT ĐƠN TRONG C++ Danh sách các đối tượng có khóa là số nguyên typedef struct CELL *LIST; struct CELL { int key; LIST next; }; LIST L; ListInitialize void ListInitialize(LIST &L ) { L=NULL ; } ListSearch Thao tác ListSearch(L, k) tìm một đối tượng có khóa k trong danh sách L Nếu có đối tượng có khóa k, thủ tục sẽ trả về một pointer chỉ đến đối tượng này Nếu không có đối tượng nào có khóa bằng k trong danh sách , thủ tục trả về NULL ListSearch LIST ListSearch(LIST L, int k) { LIST x; x=L ; while(x !=NULL && x->key!=k) x=x->next; return x; } ListInsert Thao tác ListInsert(L, k) thực hiện đơn giản bằng cách chèn đối tượng x có khóa k vào đầu của L ListInsert void ListInsert(LIST &L, int k) { LIST x; x=new(CELL ); x- >key=k; x- >next=L; L=x ; } ListDelete Thao tác ListDelete(L, k) xóa đối tượng x có khóa k khỏi L Được thực hiện bằng cách tìm x và cập nhật lại các con trỏ nút y đi trước x để loại x ra khỏi danh sách ListDelete void ListDelete(LIST &L,int k) { LIST x, y; if(L != NULL) { y = NULL; x =L; while(x !=NULL && x->key!=k) { y=x ; x=x->next; } if(x !=NULL) //Tìm thấy { if(y ==NULL) L=x->next; //nút cần xóa ở đầu DS else y->next=x->next; // Loại bỏ nút x khỏi DS delete x; // Xóa khỏi bộ nhớ } } } ListWalk (Duyệt DS) void ListWalk(LIST L) { if(L !=NULL) { cout key)<<' '; ListWalk(L- >next); } } ĐỘ PHỨC TẠP CÁC THAO TÁC ListSearch(L , k) có độ phức tạp trung bình T(n) = O(n) ListInsert(L , k) có độ phức tạp T(n)=O(1) ListDelete(L , k) có độ phức tạp trung bình T(n) = O(n) DANH SÁCH LIÊN KẾT KÉP Danh sách liên kết kép là cấu trúc dữ liệu trong đó các đối tượng được sắp đặt theo một trật tự tuyến tính Trật tự tuyến tính trong danh sách liên kết kép được xác định bởi một cặp pointer trong mỗi đối tượng ĐỊNH NGHĨA DANH SÁCH LIÊN KẾT KÉP TRONG C++ Danh sách các đối tượng có khóa là số nguyên typedef struct CELL *LIST; struct CELL { int key; LIST prev, next; }; LIST L; ListInitialize void ListInitialize(LIST &L ) { L=NULL ; } ListSearch Thao tác ListSearch(L, k) tìm một phần tử có khóa k trong danh sách L Nếu có phần tử có khóa k, thủ tục sẽ trả về một pointer chỉ đến phần tử này Nếu không có đối tượng nào có khóa bằng k trong danh sách , thủ tục trả về NULL ListSearch LIST ListSearch(LIST L, int k) { LIST x; x=L ; while(x !=NULL && x->key!=k) x=x->next; return x; } ListInsert Thao tác ListInsert(L, x) thực hiện đơn giản bằng cách chèn x vào đầu của DS void ListInsert(LIST &L, int k) { LIST x; x=new(CELL); x->key=k; x- > prev=NULL; x->next=L; if(L!=NULL) L->prev=x; L = x; } ListDelete Thao tác ListDelete(L, x) xóa x khỏi L Được thực hiện bằng cách cập nhật lại các con trỏ khi biết con trỏ tới x để loại x ra khỏi danh sách ListDelete void ListDelete(LIST &L, int k) { LIST x=L; while(x !=NULL && x->key!=k) x=x->next; if(x !=NULL) { if(x- >prev!=NULL) x->prev->next=x->next; else L = x->next; if(x- >next!=NULL) x->next->prev=x->prev; delete x; } } ListWalk (Duyệt DS) void ListWalk(LIST L) { if(L !=NULL) { cout key)<<' '; ListWalk(L- >next); } } ĐỘ PHỨC TẠP CÁC THAO TÁC Độ phức tạp các thao tác trên DSLK kép tương tự như trên DSLK đơn Thuật toán sắp xếp nào? Điểm khác nhau giữa Danh sách liên kết và Mảng ? Yêu cầu: Cho dãy số nguyên. Hãy tính tổng của dãy số này. Dữ liệu vào từ file TONG.INP có dạng: - Dòng đầu là số nguyên N - Dòng thứ hai gồm N số nguyên Kết quả ra file TONG.OUT chứa 1 số nguyên là tổng cần tìm. Ví dụ: TONG.INP 5 3 5 2 7 1 TONG.OUT 18 Giải bài toán này bằng cách sử dụng danh sách liên kết Yêu cầu: Cho dãy số nguyên. Hãy xóa các số chẵn trong dãy. Dữ liệu vào từ file XOACHAN.INP có dạng: - Dòng đầu là số nguyên N - Dòng thứ hai gồm N số nguyên Kết quả ra file XOACHAN.OUT chứa dãy số sau khi đã xóa các số chẵn. Ví dụ: XOACHAN.INP 5 3 5 2 7 1 XOACHAN.OUT 3 5 7 1 Giải bài toán này bằng cách sử dụng danh sách liên kết Yêu cầu: Cho dãy số nguyên có N phần tử và số nguyên x. Hãy chèn số x vào vị trí k trong dãy (vị trí tính bắt đầu từ 0, k < N). Dữ liệu vào từ file CHEN.INP có dạng: - Dòng đầu là số nguyên N, x, k - Dòng thứ hai gồm N số nguyên Kết quả ra file CHEN.OUT chứa dãy số sau khi đã chèn x. Ví dụ: CHEN.INP 5 9 3 3 5 2 7 1 CHEN.OUT 3 5 2 9 7 1 Giải bài toán này bằng cách sử dụng danh sách liên kết Liệt kê 3 loại danh sách liên kết đã học? Độ phức tạp trung bình của các thao tác ListSearch(L,k), ListInsert(L,k) là bao nhiêu? Chỉnh sửa lại đoạn code trong thao tác ListDelete để có thể xóa phần tử cuối của danh sách liên kết đơn. Có bao nhiêu người trả lời đúng hết 3 câu trên? STACK QUEUE STACK (NGĂN XẾP) Biểu diễn một tập động (dynamic set) mà thao tác chèn và xóa theo nguyên lý vào sau ra trước (last in, first out hay LIFO) Stack là mô hình dữ liệu được ứng dụng nhiều trong thực tế Trong khoa học máy tính stack được dùng để quản lý quá trình gọi và thực thi chương trình con CÁC THAO TÁC TRÊN STACK StackInitialize(S ): Khởi tạo stack Push(S , x): Chèn vào stack S một phần tử Pop(S ): Loại khỏi stack một phần tử StackEmpty(S ): Kiểm tra stack rỗng StackFull(S ): Kiểm tra stack đầy BIỂU DIỄN STACK Dùng mảng một chiều n phần tử S[1..n] Dùng danh sách được liên kết (khi số phần tử trong stack không bị hạn chế) BIỂU DIỄN STACK BẰNG MẢNG Ký hiệu top[S] là đỉnh stack (chỉ đến phần tử được chèn vào gần nhất) Tại một thời điểm stack bao gồm các phần tử S[1], S[2 ], , S[top[S]] Trong đó S[1] là phần tử ở đáy và S[top[S]] là phần tử ở đỉnh stack BIỂU DIỄN STACK BẰNG MẢNG Nếu top[S] = 0, stack rỗng Nếu top[S] = n, stack đầy Mỗi lần thao tác push thực hiện top[S] tăng lên 1 Mỗi lần thao tác pop thực hiện top[S] giảm đi 1 BIỂU DIỄN STACK BẰNG MẢNG Ví dụ: 9 2 8 9 2 8 3 9 2 8 3 1 t op[S] = 3 t op[S] = 4 t op[S] = 5 1 2 3 4 5 6 BIỂU DIỄN STACK BẰNG MẢNG Biểu diễn stack trong C++, mỗi phần tử là một số nguyên #define MAX 200 struct STACK{ int a[MAX]; int top; int N; //kich thuoc stack }; STACK S; StackInitialize void StackInitialize(STACK &S) { S.top=0 ; S.N=30 ; } StackEmpty bool StackEmpty(STACK S) { if(S.top ==0) return true; else return false; } StackFull bool StackFull(STACK S) { if(S.top ==S.N) return true; else return false; } Push void Push(STACK &S, int x) { if(StackFull(S )) cout<<"overflows"; else { S.top=S.top+1 ; S.a[S.top ]=x; } } POP int pop(STACK &S) { if(StackEmpty(S )) { cout <<"underflows"; return -1 ; } else { S.top=S.top-1 ; return S.a[S.top+1]; } } BIỂU DIỄN STACK BẰNG DSLK Tại một thời điểm stack bao gồm một tập (không bị hạn chế ) các phần tử Phần tử đầu tiên của stack là phần tử đầu tiên của DS được trỏ bởi top[S] Phần tử ở đáy của stack là phần tử cuối cùng của DS BIỂU DIỄN STACK BẰNG DSLK Ðịnh nghĩa stack trong C ++ typedef struct CELL *STACK; struct CELL { int Obj; STACK next; }; STACK S; StackInitialize void StackInitialize(STACK & S) { S=NULL ; } StackEmpty bool StackEmpty(STACK S) { if(S ==NULL) return true; else return false; } StackFull bool StackFull(STACK S) { return false; } PUSH void Push(STACK &S, int x) { STACK p; p=new(CELL ); p- >Obj=x; p- >next=S; S=p ; } POP int Pop(STACK &S) { int x; STACK p; if(StackEmpty(S )) {cout<<"underflows"; return -1;} else { p=S ; S=p- >next; x=p- >Obj; delete(p ); return x; } } QUEUE (HÀNG ĐỢI) Biểu diễn một tập động (dynamic set) mà thao tác chèn và xóa theo nguyên lý vào trước ra trước (First in, first out hay FIFO ) Queue là mô hình dữ liệu được ứng dụng nhiều trong thực tế Queue có thể được ứng dụng trong quá trình quản lý và thực thi có trật tự các chương trình của một hệ điều hành CÁC THAO TÁC TRÊN QUEUE QueueInitialize(Q ): Khởi tạo hàng đợi Enqueue(Q , x): Chèn vào queue một phần tử Dequeue(Q ): Loại khỏi queue một phần tử QueueEmpty(Q ): Kiểm tra queue rỗng QueueFull(Q ): Kiểm tra queue đầy BIỂU DIỄN QUEUE Có thể dùng một mảng một chiều với n phần tử Q[1.. n] để biểu diễn một hàng đợi tối đa n - 1 phần tử Có thể dùng danh sách liên kết Mỗi hàng đợi có một đầu (head) và một đuôi (tail) Một phần tử được đưa vào hàng đợi tại đuôi của nó Phần tử bị loại khỏi hàng đợi là phần tử ở đầu hàng đợi BIỂU DIỄN QUEUE BẰNG MẢNG Ký hiệu head[Q] và tail[Q] là đầu và đuôi của hàng đợi Các phần tử trong hàng đợi được đặt tại các vị trí head[Q], head[Q]+1,..., tail[Q ] và coi vị trí 1 đi ngay sau vị trí n trong một trật tự “vòng ” head[Q] = tail[Q] = 0 thì hàng đợi rỗng (khởi tạo hàng đợi rỗng : head[Q] = tail[Q] = 0) Khi head[Q] = tail[Q] + 1 hoặc tail[Q]=n và head[Q] = 1 thì hàng đợi đầy BIỂU DIỄN QUEUE BẰNG MẢNG Ví dụ: 8 3 1 9 8 3 1 4 9 3 1 4 8 3 1 4 1 2 3 4 5 6 head[Q] = 3 tail[Q] = 5 head[Q] = 3 tail[Q] = 6 head[Q] = 3 tail[Q] = 1 head[Q] = 4 tail[Q] = 1 BIỂU DIỄN QUEUE typedef struct QUEUE{ int a[MAX]; int head, tail; int n; }; QUEUE Q; QueueInitialize void QueueInitialize(QUEUE &Q) { Q.head=Q.tail=0; Q.n=20 ; //tùy chọn } QueueEmpty bool QueueEmpty(QUEUE Q) { if (Q.head==0) return true; else return fasle; } QueueFull bool QueueFull(QUEUE Q) { if ((Q.tail==Q.n&&Q.head==1 )|| ( Q.head==Q.tail+1)) return true; else return fasle; } Enqueue void Enqueue(QUEUE &Q, int x) { if(QueueFull(Q )) cout<<"overflow"; else { if(Q.tail ==Q.n) Q.tail=1; else Q.tail=Q.tail+1 ; Q.a[Q.tail ]=x ; if (Q.head==0) Q.head = 1; } } Dequeue int Dequeue(QUEUE &Q) { int x; if(QueueEmpty(Q )){ cout <<"underflow"; return -1 ; } else { x=Q.a[Q.head]; if (Q.head==Q.tail) // chi con 1 phan tu Q.head = Q.tail = 0; else { if(Q.head ==Q.n) Q.head=1; else Q.head=Q.head+1 ; } return x; } } BIỂU DIỄN QUEUE BẰNG DSLK Tại một thời điểm queue bao gồm một tập (không bị hạn chế ) các phần tử Phần tử đầu tiên của DS là phần tử ở đầu của queue được trỏ bởi head[Q] Phần tử cuối của queue là phần tử cuối của DS được trỏ bởi tail[Q] BIỂU DIỄN QUEUE BẰNG DSLK Định nghĩa queue mà mỗi phần tử là một số nguyên typedef struct CELL *LIST; struct CELL { int Obj; LIST next; }; struct QUEUE { LIST head, tail; }; QUEUE Q; QueueInitialize void QueueInitialize(QUEUE &Q) { Q.head=Q.tail=NULL ; } QueueEmpty bool QueueEmpty(QUEUE Q) { if(Q.head ==NULL) return true; else return false; } Enqueue void Enqueue(QUEUE &Q,int x) { LIST p; p=new(CELL ); p- >Obj=x; p- >next=NULL; if(Q.head ==NULL) Q.head=p; else Q.tail->next=p; Q.tail=p ; } Dequeue int Dequeue(QUEUE &Q) { int x; LIST p; if(QueueEmpty(Q )){cout<<"underflows"; return -1;} else { p=Q.head ; x=p- >Obj; if(Q.head ==Q.tail) {Q.head=Q.tail=NULL;} else Q.head=p->next; delete p; return x; } } ĐỘ PHỨC TẠP CÁC THAO TÁC TRÊNSTACK VÀ QUEUE Các thao tác trên stack có độ phức tạp là O(1) Các thao tác trên queue có độ phức tạp là O(1) HÀNG ÐỢI Ư U TIÊN Hàng đợi ưu tiên (priority queue) gồm một tập đối tượng trong đó đối tượng có khoá ưu tiên được xử lý trước Dùng max-heap để biểu diễn hàng đợi ưu tiên theo khóa lớn hơn Dùng min-heap để biểu diễn hàng đợi ưu tiên theo khóa nhỏ hơn BIỂU DIỄN HÀNG ĐỢI ƯU TIÊN BẰNG MAX-HEAP # define MAX 1000 struct HEAP { int heap_size; int x[MAX]; }; CÁC THAO TÁC TRÊN HÀNG ĐỢI ƯU TIÊN Thao tác trên hàng đợi ưu tiên (theo khóa lớn ) QueueInitialize(Q ) QueueInsert(Q , x) QueueExtractMax(Q ) QueueIncreaseKey(Q , x, k) QueueInitialize void QueueInitialize(HEAP &Q) { Q.heap_size=0 ; } QueueExtractMax QueueExtractMax(Q) loại phần tử được ưu tiên nhất ra khỏi hàng đợi Q int QueueExtractMax(HEAP &Q) { int max; if(Q.heap_size<1 ) { cout <<“Error Heap Undeflow!\n "; return -1; } else { max=Q.x[1 ]; Q.x[1 ]=Q.x[Q.heap_size]; Q.heap_size = Q.heap_size-1; MaxHeapify(Q,1 ); return max ; | } QueueExtractMax void MaxHeapify(HEAP &Q, int i) { int l, r, largest; l = Left(i); r = Right(i); if(l Q.x[i ]) largest=l ; else largest=i; if(r Q.x[largest]) largest=r ; if(largest !=i) { Exchange(Q.x[i ], Q.x[largest]); MaxHeapify(Q,largest ); } } QueueExtractMax Thời gian chạy của QueueExtractMax(Q) là O(log 2 n ) trên một heap n phần tử QueueIncreaseKey QueueIncreaseKey(Q , i, key) tăng khoá tại nút i lên thành khoá key D i chuyển phần tử có khóa key từ nút i hướng đến gốc để tìm nơi chính xác cho nút nhận khoá này QueueIncreaseKey void QueueIncreaseKey(HEAP &Q, int i, int key) { if(key<Q.x[i ]) cout<<“ Error: new key is smaller current key!\n"; Q.x[i ]=key; while ((i>1)&&(Q.x[parent(i)]<Q.x[i])) { Exchange(Q.x[i ], Q.x[parent(i)]); i=parent(i ); } } QueueIncreaseKey Thời gian chạy của QueueIncreaseKey tối đa là O(log 2 n) trên một heap n phần tử QueueInsert QueueInsert(Q , key) chèn một phần tử có khoá key vào một hàng đợi ưu tiên (max-heap) Đầu tiên, mở rộng heap bằng cách thêm vào một lá mới Áp dụng QueueIncreaseKey để tăng khóa key cho nút lá này QueueInsert void QueueInsert(HEAP &Q, int key) { Q.heap_size = Q.heap_size+1; Q.x[Q.heap_size]=-1000000; QueueIncreaseKey(Q , Q.heap_size, key); } QueueInsert Thời gian chạy của QueueInsert tối đa là O(log 2 n ) trên một heap n phần tử Yêu cầu: Cho dãy số nguyên. Hãy tính tổng của dãy số này. Dữ liệu vào từ file SUM.INP có dạng: - Dòng đầu là số nguyên N - Dòng thứ hai gồm N số nguyên Kết quả ra file SUM.OUT chứa 1 số nguyên là tổng cần tìm. Ví dụ: SUM.INP 5 3 5 2 7 1 SUM.OUT 18 Giải bài toán này bằng cách sử dụng QUEUE Yêu cầu: V iết chương trình đổi 1 số từ hệ thập phân sang hệ nhị phân Dữ liệu vào từ file NP.INP chứa số nguyên N ở hệ thập phân Kết quả xuất ra file NP.OUT chứa kết quả sau khi đã đổi N sang hệ nhị phân Ví dụ: NP.INP 9 NP.OUT 1001 Giải bài toán này bằng cách sử dụng STACK
File đính kèm:
- bai_giang_cau_truc_du_lieu_va_giai_thuat_chuong_3_danh_sach.pptx