Bài giảng Kỹ thuật lập trình - Bài 7: Cấu trúc dữ liệu (Data structures) - Đào Trung Kiên

Mở đầu: mảng động

 Mảng trong C có số phần tử cố định từ khi khai báo

 Mảng động là mảng có số phần tử thay đổi: bản chất là

một con trỏ và một biến cho biết số phần tử

 int n = 5;

int* arr = (int*)malloc(n*sizeof(int));

 Bài toán chèn phần tử vào mảng động:

 pos = 2; val = 25;

arr1 = (int*)malloc((n+1)*sizeof(int));

memcpy(arr1, arr, pos*sizeof(int));

memcpy(arr1+pos+1, arr+pos, (n-pos)*sizeof(int));

arr1[pos] = val;

free(arr);

arr = arr1;

 Tương tự khi xoá phần tử

pdf21 trang | Chia sẻ: phuongt97 | Lượt xem: 299 | Lượt tải: 0download
Bạn đang xem trước 20 trang nội dung tài liệu Bài giảng Kỹ thuật lập trình - Bài 7: Cấu trúc dữ liệu (Data structures) - Đào Trung Kiên, để xem tài liệu hoàn chỉnh bạn click vào nút DOWNLOAD ở trên
EE3490: Kỹ thuật lập trình – HK1 2017/2018 TS. Đào Trung Kiên – ĐH Bách khoa Hà Nội Bài 7: Cấu trúc dữ liệu (Data structures) 1 EE3490: Kỹ thuật lập trình – HK1 2017/2018 TS. Đào Trung Kiên – ĐH Bách khoa Hà Nội Mở đầu: mảng động  Mảng trong C có số phần tử cố định từ khi khai báo  Mảng động là mảng có số phần tử thay đổi: bản chất là một con trỏ và một biến cho biết số phần tử  int n = 5; int* arr = (int*)malloc(n*sizeof(int));  Bài toán chèn phần tử vào mảng động:  pos = 2; val = 25; arr1 = (int*)malloc((n+1)*sizeof(int)); memcpy(arr1, arr, pos*sizeof(int)); memcpy(arr1+pos+1, arr+pos, (n-pos)*sizeof(int)); arr1[pos] = val; free(arr); arr = arr1;  Tương tự khi xoá phần tử 2 10 20 30 40 50 25 10 20 25 30 40 50 EE3490: Kỹ thuật lập trình – HK1 2017/2018 TS. Đào Trung Kiên – ĐH Bách khoa Hà Nội Khái niệm  Ví dụ trên cho thấy mảng động khá kém hiệu quả trong việc thêm/bớt phần tử vì cần di chuyển các vùng nhớ, nhất là khi mảng có nhiều phần tử  cần các cấu trúc dữ liệu linh hoạt hơn  Các cấu trúc dữ liệu phổ biến, ứng dụng tuỳ bài toán:  Ngăn xếp (stack)  Hàng đợi (queue)  Danh sách liên kết (linked list)  Mảng động (vector, dynamic array)  Ánh xạ (map), từ điển (dictionary), bảng băm (hash table)  Tập hợp (set)  Cây (tree)  Đồ thị (graph) 3 EE3490: Kỹ thuật lập trình – HK1 2017/2018 TS. Đào Trung Kiên – ĐH Bách khoa Hà Nội Danh sách liên kết (DSLK)  Là tập hợp các phần tử được móc nối với nhau bằng con trỏ:  Một con trỏ trỏ đến phần tử đầu tiên (hoặc NULL nếu chưa có phần tử nào)  Mỗi phần tử bao gồm 2 thành phần: dữ liệu, con trỏ next tới phần tử tiếp theo  Con trỏ next của phần tử cuối cùng trỏ đến NULL 4 data next data next data next list EE3490: Kỹ thuật lập trình – HK1 2017/2018 TS. Đào Trung Kiên – ĐH Bách khoa Hà Nội Khai báo DSLK  Ví dụ với dữ liệu là kiểu int:  struct SELEM; typedef struct SELEM ELEM, *PELEM, *LLIST; struct SELEM { int data; PELEM next; };  Thư viện DSLK:  llist.h  llist.c 5 EE3490: Kỹ thuật lập trình – HK1 2017/2018 TS. Đào Trung Kiên – ĐH Bách khoa Hà Nội Thao tác với DSLK  Các hàm cần viết:  LLIST llInit();  LLIST llInsertHead(LLIST l, int data);  LLIST llInsertTail(LLIST l, int data);  LLIST llInsertAfter(LLIST l, PELEM a, int data);  LLIST llDeleteHead(LLIST l);  LLIST llDeleteTail(LLIST l);  LLIST llDeleteAfter(LLIST l, PELEM a);  LLIST llDeleteAll(LLIST l);  PELEM llSeek(LLIST l, int i);  void llForEach(LLIST l, LLCALLBACK func, void* user);  int llLength(LLIST l); 6 EE3490: Kỹ thuật lập trình – HK1 2017/2018 TS. Đào Trung Kiên – ĐH Bách khoa Hà Nội Khởi tạo DSLK LLIST llInit() { return NULL; }  Khởi tạo DSLK với NULL  chưa có phần tử nào 7 EE3490: Kỹ thuật lập trình – HK1 2017/2018 TS. Đào Trung Kiên – ĐH Bách khoa Hà Nội LLIST llInsertHead(LLIST l, int data) { PELEM e = (PELEM)malloc(sizeof(ELEM)); e->data = data; e->next = l; return (LLIST)e; } Thêm phần tử vào đầu DSLK 8 data next data next data next list data next EE3490: Kỹ thuật lập trình – HK1 2017/2018 TS. Đào Trung Kiên – ĐH Bách khoa Hà Nội LLIST llInsertTail(LLIST l, int data) { PELEM p; PELEM e = (PELEM)malloc(sizeof(ELEM)); e->data = data; e->next = NULL; if (l==NULL) return (LLIST)e; for (p=l; p->next; p = p->next) ; p->next = e; return l; } Thêm phần tử vào cuối DSLK 9 data next data next data next list data next EE3490: Kỹ thuật lập trình – HK1 2017/2018 TS. Đào Trung Kiên – ĐH Bách khoa Hà Nội Thêm phần tử vào sau một phần tử của DSLK LLIST llInsertAfter(LLIST l, PELEM a, int data) { PELEM e; if (!a) return l; e = (PELEM)malloc(sizeof(ELEM)); e->data = data; e->next = a->next; a->next = e; return l; } 10 data next data next data next list a data next EE3490: Kỹ thuật lập trình – HK1 2017/2018 TS. Đào Trung Kiên – ĐH Bách khoa Hà Nội Xoá phần tử đầu trong DSLK 11 LLIST llDeleteHead(LLIST l) { PELEM p; if (!l) return NULL; p = l->next; free(l); return (LLIST)p; } data next data next data next list EE3490: Kỹ thuật lập trình – HK1 2017/2018 TS. Đào Trung Kiên – ĐH Bách khoa Hà Nội Xoá phần tử cuối DSLK LLIST llDeleteTail(LLIST l) { PELEM p; if (!l) return NULL; if (!l->next) { free(l); return NULL; } for (p=l; p->next->next; p = p->next) ; free(p->next); p->next = NULL; return l; } 12 data next data next data next list EE3490: Kỹ thuật lập trình – HK1 2017/2018 TS. Đào Trung Kiên – ĐH Bách khoa Hà Nội Xoá phần tử sau một phần tử trong DSLK LLIST llDeleteAfter(LLIST l, PELEM a) { PELEM p; if (!a || !a->next) return l; p = a->next; a->next = p->next; free(p); return l; } 13 data next data next data next list a EE3490: Kỹ thuật lập trình – HK1 2017/2018 TS. Đào Trung Kiên – ĐH Bách khoa Hà Nội Duyệt DSLK  Tìm đến phần tử thứ i của DSLK PELEM llSeek(LLIST l, int i) { for (; i>0 && l; i--) l = l->next; return (PELEM)l; }  Gọi hàm với mỗi phần tử của DSLK typedef void (*LLCALLBACK)(int, void*); void llForEach(LLIST l, LLCALLBACK func, void* user) { for (; l; l=l->next) func(l->data, user); } 14 EE3490: Kỹ thuật lập trình – HK1 2017/2018 TS. Đào Trung Kiên – ĐH Bách khoa Hà Nội Các hàm khác với DSLK 15  Đếm số phần tử int llLength(LLIST l) { int c; for (c=0; l; c++) l = l->next; return c; }  Xoá tất cả các phần tử LLIST llDeleteAll(LLIST l) { PELEM p; for (; l; l=p) { p = l->next; free(l); } return NULL; } EE3490: Kỹ thuật lập trình – HK1 2017/2018 TS. Đào Trung Kiên – ĐH Bách khoa Hà Nội Ví dụ sử dụng DSLK #include #include "llist.h" void printList(LLIST l) { printf("%d phan tu: ( ", llLength(l)); for (; l; l = l->next) printf("%d ", l->data); printf(") \n"); } void listSum(int data,void* user) { int* pS = (int*)user; *pS += data; } int main() { LLIST l; PELEM p; int i, s; l = llInit(); for (i=0; i<5; i++) { l = llInsertTail(l, i); l = llInsertHead(l, -i); } printList(l); p = llSeek(l, 1); l = llDeleteAfter(l, p); l = llDeleteHead(l); l = llDeleteTail(l); printList(l); s = 0; llForEach(l, listSum, (void*)&s); printf("Tong gia tri: %d\n", s); l = llDeleteAll(l); printList(l); return 0; } 16 EE3490: Kỹ thuật lập trình – HK1 2017/2018 TS. Đào Trung Kiên – ĐH Bách khoa Hà Nội Đặc điểm của DSLK  Ưu/nhược điểm so với mảng động: + Linh hoạt: thêm/bớt phần tử dễ dàng, sắp xếp và thay đổi thứ tự phần tử mà không cần di chuyển trong bộ nhớ + Không cần cấp phát một vùng nhớ lớn và liên tục – Cần thêm bộ nhớ phụ cho các biến con trỏ – Không truy cập tới phần tử bất kỳ thứ i được như mảng, mà phải duyệt từ đầu tới  Ngoài dạng cơ bản, có rất nhiều biến thể của DSLK: DSLK kép, DSLK có thứ tự, hàng đợi, ngăn xếp, DSLK vòng, và cũng có nhiều cách cài đặt khác nhau tuỳ thuộc vào bài toán cụ thể 17 EE3490: Kỹ thuật lập trình – HK1 2017/2018 TS. Đào Trung Kiên – ĐH Bách khoa Hà Nội Ngăn xếp (stack)  Ngăn xếp là trường hợp riêng của DSLK, chỉ có hai thao tác:  push: thêm phần tử vào đầu danh sách  pop: lấy giá trị đồng thời xoá phần tử đầu danh sách  thuộc loại danh sách LIFO (last in, first out)  Ứng dụng:  Tính toán biểu thức (ký pháp Ba Lan ngược)  Thực hiện việc gọi hàm trong ngôn ngữ lập trình  Giải đệ quy  Dữ liệu undo 18 PopPush EE3490: Kỹ thuật lập trình – HK1 2017/2018 TS. Đào Trung Kiên – ĐH Bách khoa Hà Nội Hàng đợi (queue)  Hàng đợi là trường hợp riêng của DSLK, chỉ có hai thao tác:  enqueue: thêm phần tử vào đầu danh sách  dequeue: lấy giá trị đồng thời xoá phần tử cuối danh sách  thuộc loại danh sách FIFO (first in, first out)  Để lập trình hàng đợi, thường dùng hai con trỏ: một tới đầu danh sách để thêm, một tới cuối danh sách để lấy phần tử  Ứng dụng:  Truyền thông tin, dữ liệu  Bộ nhớ đệm đọc/ghi dữ liệu  Cài đặt các dịch vụ (mô hình client/server) 19 Dequeue Enqueue EE3490: Kỹ thuật lập trình – HK1 2017/2018 TS. Đào Trung Kiên – ĐH Bách khoa Hà Nội DSLK kép (doubly linked list)  Trong DSLK thông thường, mỗi phần tử chỉ có một con trỏ next tới phần tử kế tiếp  gọi là DSLK đơn, chỉ duyệt DS được một chiều  DSLK kép: mỗi phần tử có thêm con trỏ prev tới phần tử ở trước  có thể duyệt được hai chiều 20 data next prev data next prev data next prev list EE3490: Kỹ thuật lập trình – HK1 2017/2018 TS. Đào Trung Kiên – ĐH Bách khoa Hà Nội Bài tập 1. Khai báo DSLK kép và viết các hàm: thêm/xoá phần tử đầu 2. Viết các hàm Pop(), Push() của ngăn xếp và Enqueue(), Dequeue() của hàng đợi dựa trên DSLK và nhận xét hàm nào không hiệu quả 3. Tính giá trị trung bình các giá trị của một DSLK bằng hai cách: duyệt danh sách, và sử dụng hàm llForEach() 4. Viết hàm llConvert(int* arr, int count) chuyển dữ liệu từ mảng sang DSLK, không dùng các hàm đã viết 5. Viết hàm llReverse(LLIST l) đảo ngược thứ tự DSLK, không cấp phát thêm phần tử mới 6. Như bài trên nhưng dùng phương pháp đệ quy 7. Có hai DSLK l1 và l2, viết hàm llInsertListAfter(l1, l2, p) để chèn l2 vào l1 ở sau vị trí của phần tử p 8. Sửa thư viện DSLK để có thể chứa dữ liệu bất kỳ 21

Các file đính kèm theo tài liệu này:

  • pdfbai_giang_ky_thuat_lap_trinh_bai_7_cau_truc_du_lieu_data_str.pdf
Tài liệu liên quan