TỔNG QUAN VỀ CẤU TRÚC DỮ LIỆU VÀ GIẢI THUẬT
CÁC KHÁI NIỆM
Trong lĩnh vực máy tính, dữ liệu là thông tin được mã hóa thành định dạng dễ xử lý hơn Ngày nay, trong khoa học máy tính và truyền thông, dữ liệu thường được chuyển đổi thành dạng số nhị phân.
Cấu trúc lưu trữ là cách biểu diễn một cấu trúc dữ liệu trong bộ nhớ máy tính, và có thể có nhiều hình thức khác nhau cho cùng một cấu trúc dữ liệu Ví dụ, một cấu trúc dữ liệu kiểu danh sách có thể được lưu trữ ở các vùng nhớ liên tiếp thông qua mảng, hoặc ở các vùng nhớ rời nhau bằng cách sử dụng danh sách liên kết.
Có nhiều loại cấu trúc dữ liệu khác nhau được lưu trữ qua các cấu trúc lưu trữ Ví dụ, cấu trúc chuỗi ký tự và cấu trúc mảng đều được lưu trữ trong các ô nhớ liên tiếp.
1.1.3 Lựa chọn cấu trúc dữ liệu cho bài toán
Việc lựa chọn cấu trúc dữ liệu phù hợp để tổ chức dữ liệu đầu vào và đầu ra, cùng với việc thiết lập giải thuật thích hợp, là một bước quan trọng để đạt được kết quả mong muốn.
Khi lựa chọn cấu trúc dữ liệu, cần xem xét các phép toán mà nó sẽ thực hiện Ngược lại, khi phân tích các phép toán, cần chú ý đến cấu trúc dữ liệu mà chúng tác động, vì có những phép toán hiệu quả với một cấu trúc dữ liệu nhưng không hiệu quả với cấu trúc khác.
▪ Khái niệm về giải thuật và các tính chất của giải thuật
▪ Mối liên hệ giữa cấu trúc dữ liệu và giải thuật
▪ Ngôn ngữ biểu diễn giải thuật
▪ Độ phức tạp tính toán của giải thuật
GIẢI THUẬT
1.2.1 Khái niệm về giải thuật
Giải thuật hay thuật toán (algorithrm) dùng để chỉ phương pháp hay cách thức giải quyết vấn đề
Giải thuật là một chuỗi các câu lệnh rõ ràng và chặt chẽ, xác định trình tự thao tác trên các đối tượng đầu vào (input), nhằm đạt được kết quả đầu ra (output) mong muốn sau một số bước thực hiện hữu hạn.
According to Donald Knuth in "The Art of Computer Programming," an algorithm is defined as a finite, effective procedure that takes specific inputs and produces outputs.
Có nhiều giải thuật khác nhau cho một bài toán Ví dụ, tính tổng S = 1 + 2 + … + n
Cách 1: sử dụng kỹ thuật cộng dồn
- Cho biến i chạy từ 1 đến n: S = S + i;
Cách 2: Sử dụng công thức của cấp số cộng công bội 1: S = n (n + 1)/2;
1.2.2 Các tính chất của giải thuật
Các giải thuật đều có một số tính chất chung, nắm rõ các tính chất của giải thuật sẽ thuận lợi khi phân tích và thiết kế giải thuật
Mỗi bài toán đều có giả thiết với một vài đại lượng đầu vào xác định mà ta thường gọi là dữ liệu vào
Thuật toán xử lý dữ liệu đầu vào để thu được các đại lượng đầu ra xác định, trong đó những đại lượng này chính là nghiệm hoặc kết quả của bài toán.
Yêu cầu quan trọng nhất của một giải thuật là tính đúng đắn, nghĩa là với mỗi bộ dữ liệu đầu vào cụ thể, sau một số bước thực hiện hữu hạn, giải thuật sẽ dừng lại và cung cấp kết quả chính xác cho bài toán.
Tính xác định trong giải thuật yêu cầu mọi thao tác phải rõ ràng và không gây nhầm lẫn Điều này có nghĩa là trong cùng một điều kiện, hai bộ xử lý (người hoặc máy) thực hiện cùng một bước của giải thuật phải đạt được kết quả giống nhau Hơn nữa, các bộ xử lý không cần phải hiểu ý nghĩa của các thao tác trong quá trình thực hiện.
Tính hữu hạn (tính dừng)
Với dữ liệu đầu vào xác định, giải thuật bao giờ cũng phải dừng sau một số hữu hạn bước thực hiện và cho kết quả đầu ra
Giải thuật không chỉ nhằm giải quyết một bài toán đơn lẻ mà còn phải có khả năng xử lý nhiều bài toán tương tự với các dữ liệu khác nhau, đồng thời đảm bảo luôn đạt được kết quả mong muốn.
Tính hiệu quả được đánh giá dựa trên một số tiêu chuẩn nhất định như khối lượng tính toán, thời gian và không gian thực hiện giải thuật
1.2.3 Mối quan hệ giữa cấu trúc dữ liệu và giải thuật
Khi giải quyết bài toán trên máy tính, việc thiết kế giải thuật là rất quan trọng Giải thuật xác định cách thức xử lý dữ liệu của đối tượng Để lưu trữ và xử lý hiệu quả, dữ liệu cần được biểu diễn theo một cấu trúc dữ liệu phù hợp.
Theo lập trình có cấu trúc, Niklaus Wirth đã phát triển một công thức thể hiện mối quan hệ giữa cấu trúc dữ liệu và thuật toán.
GIẢI THUẬT + CẤU TRÚC DỮ LIỆU = CHƯƠNG TRÌNH
Khi cấu trúc dữ liệu trong bài toán thay đổi, giải thuật cần được điều chỉnh để phù hợp với cách tổ chức dữ liệu mới Ngược lại, trong quá trình phát triển và hoàn thiện giải thuật, điều này cũng giúp lập trình viên tìm ra cách tổ chức dữ liệu hiệu quả, tiết kiệm tài nguyên hệ thống.
Quá trình giải quyết bài toán trên máy tính cần chú ý đến mối quan hệ chặt chẽ giữa thuật toán và cấu trúc dữ liệu Do đó, khi nghiên cứu cấu trúc dữ liệu cho một bài toán, cần phải xác định các thuật toán tương ứng với cấu trúc dữ liệu đó.
NGÔN NGỮ BIỂU DIỄN GIẢI THUẬT
Khi thiết kế một giải thuật, việc trình bày rõ ràng là cần thiết để kiểm tra xem giải thuật có đáp ứng các yêu cầu như tính đúng đắn, tính phổ dụng và tính hữu hạn hay không Điều này giúp người đọc dễ dàng hiểu và đánh giá giải thuật mà chúng ta đã phát triển.
Có nhiều cách thức khác nhau để biểu diễn giải thuật, cụ thể:
- Ngôn ngữ tự nhiên (Natural language)
1.3.1 Ngôn ngữ tự nhiên Để biểu diễn giải thuật theo ngôn ngữ tự nhiên, có thể sử dụng ngôn ngữ đời thường để liệt kê các bước của thuật toán
Ví dụ 1.1: Tìm số lớn nhất trong ba số a, b, c max(a,b,c)
Khi mô tả giải thuật bằng mã giả, chúng ta sử dụng cú pháp của một ngôn ngữ lập trình nào đó để thể hiện giải thuật một cách rõ ràng Mã giả không chỉ tận dụng các khái niệm trong ngôn ngữ lập trình mà còn giúp người cài đặt dễ dàng hiểu nội dung của giải thuật Tuy nhiên, mã giả cũng bao gồm một phần ngôn ngữ tự nhiên, và khi đã áp dụng cú pháp và khái niệm từ ngôn ngữ lập trình, mã giả sẽ chịu ảnh hưởng từ ngôn ngữ đó.
Ví dụ 1.2: Tìm số lớn nhất trong ba số a, b, c
11 function max(a,b,c) begin m = a; if b > m then m = b; if c > m then m = c; return m; end;
Lưu đồ, hay còn gọi là sơ đồ khối, là công cụ trực quan hữu ích để biểu diễn giải thuật Việc sử dụng lưu đồ một cách khéo léo giúp tránh những hiểu lầm về ngữ nghĩa trong các đoạn giải thích bằng lời, đồng thời cung cấp cái nhìn tổng quan hơn về toàn bộ quá trình xử lý của giải thuật.
Lưu đồ là một hệ thống bao gồm các nút với hình dạng khác nhau, đại diện cho các chức năng riêng biệt, được kết nối bằng các cung Cấu trúc của lưu đồ chủ yếu bao gồm năm thành phần chính.
1.3.3.1 Nút giới hạn Được biểu diễn bởi hình ôvan, trong đó có ghi chữ: Begin hoặc End Chúng còn được gọi là các nút đầu và nút cuối của lưu đồ
Là hình chữ nhật trong đó có ghi các lệnh cần thực hiện
1.3.3.3 Nút điều kiện Được biểu diễn dưới dạng một hình thoi, bên trong ghi điều kiện cần kiểm tra
1.3.3.4 Nút xuất/nhập dữ liệu Được biểu diễn dưới dạng một hình bình hành, bên trong ghi các lệnh xuất/nhập: Input(…)/Output(…)
1.3.3.5 Đường đi của thuật toán
Là những đường có hướng nối từ nút này đến nút khác của lưu đồ
Thuật toán hoạt động như một lưu đồ, bắt đầu từ nút đầu tiên Sau khi thực hiện các thao tác hoặc kiểm tra điều kiện tại mỗi nút, bộ xử lý sẽ di chuyển theo các cung đến nút tiếp theo cho đến khi đạt đến nút kết thúc, lúc này thuật toán sẽ dừng lại.
Ví dụ 1.3: Tìm số lớn nhất trong ba số a, b, c
PHÂN TÍCH VÀ ĐÁNH GIÁ GIẢI THUẬT
Với một giải thuật đã được thiết kế để giải quyết một bài toán, có nhiều góc độ để đánh giá giải thuật đó Chẳng hạn:
▪ Đánh giá tính đúng đắn của giải thuật, liệu giải thuật có cho kết quả đúng với mọi bộ dữ liệu đầu vào hay không?
▪ Giải thuật có dễ hiểu, dễ cài đặt và dễ chỉnh sửa hay không?
▪ Giải thuật phải sử dụng bao nhiêu bộ nhớ khi dữ liệu đầu vào có kích thước tương đối lớn?
Khi áp dụng giải thuật cho các bộ dữ liệu lớn, việc đánh giá thời gian thực hiện trở nên quan trọng Thời gian thực hiện có thể nhanh hoặc chậm, và điều này được gọi là đánh giá thời gian thực hiện giải thuật.
Trong giáo trình này, chúng tôi tập trung vào việc đánh giá thời gian thực hiện của các thuật toán, vì đây là tiêu chí quan trọng để xác định hiệu quả thực thi Độ phức tạp thời gian của thuật toán được đánh giá thông qua số phép toán tích cực, tức là những phép toán có thời gian thực thi tối thiểu bằng hoặc lớn hơn thời gian thực thi của các phép toán khác, khi kích thước của các giá trị đầu vào được xác định.
Phép toán tích cực của chương trình A thực hiện n lần, trong khi chương trình B chỉ thực hiện 1 lần Kết luận: chương trình B chạy nhanh hơn chương trình A
1.4.2 Độ phức tạp tính toán của giải thuật
Một giải thuật với kích thước dữ liệu vào là n có thời gian thực hiện được biểu diễn bằng hàm không âm f(n), với điều kiện f(n) ≥ 0 cho mọi n ≥ 0 Định nghĩa 1: Hàm f(n) được coi là có cấp bé hơn hoặc bằng hàm g(n) nếu tồn tại một hằng số C > 0 và một số tự nhiên n0 sao cho f(n) ≤ C * g(n) cho mọi n ≥ n0.
Ký hiệu: f(n) = O(g(n)) và gọi f(n) thoả mãn quan hệ big-O đối với g(n)
Hàm f(n) = n(n+3)/2 là một hàm bậc hai, và hàm bậc hai đơn giản nhất là n^2 Chúng ta có thể chứng minh rằng f(n) = O(n^2) vì n(n+3)/2 ≤ n^2 với mọi n ≥ 3 (C=1, n0=3) Theo định nghĩa, nếu một thuật toán có độ phức tạp f(n) và f(n) = O(g(n)), thì thuật toán đó được coi là có độ phức tạp O(g(n)).
Mệnh đề: Cho f1(n)=O(g1(n)) và f2(n)=O(g2(n)) Khi đó:
Ví dụ 1.6: Cho đoạn chương trình sau (kData = x; p->Next = NULL;
//B2: Chen p vao cuoi danh sach if(Last==NULL) //Danh sach rong
First = Last = p; else //Danh sach khong rong {
//B2: Chen p vao sau q p->Next = q->Next; //1 q->Next = p; //2 }
NODE *p = First; for(int i=1;iNext; return p;
NODE *p = First; if(First==Last) //Danh sach chi co 1 nut
NODE *p = Last; if(First==Last) //Danh sach chi co 1 nut
//Tim den nut q dung truoc nut Last
NODE *q = First; while(q->Next!=Last) q = q->Next;
//q la nut cuoi danh sach q->Next = NULL;
} void Del(NODE *T) //xoa nut dung sau T {
NODE *p = T->Next; //nut can xoa T->Next = p->Next; delete p;
{ coutn; for(int i=1;iData!=x && p!=First); if(p!=First) return p;
//B3: Tim den nut cuoi danh sach q
NODE *q = First; while (q->Next != First) q = q->Next;
//B4: Chen p vao dau danh sach p->Next = First;
First = p; q->Next = First; //noi vong }
//B3: Tim den nut cuoi danh sach q
NODE *q = First; while (q->Next != First) q = q->Next;
//B4: Chen p vao cuoi danh sach q->Next = p; p->Next = First; //noi vong }
//B2: Chen p vao sau T p->Next = T->Next; //1 T->Next = p; //2
NODE *p = First; if(First->Next==First) //danh sach co 1 nut
//Tim toi nut cuoi danh sach
NODE *q = First; while(q->Next!=First) q = q->Next;
//cat nut p ra khoi danh sach
NODE *p = q->Next; q->Next = p->Next; delete p;
A3 DANH SÁCH LIÊN KẾT KÉP
#include using namespace std; class DoubleLinkList
DoubleLinkList() //Khoi tao danh sach rong
} void View() //Duyet danh sach
NODE *p = First; while(p) { coutNext = NULL; p->prev = NULL;
//B2: Chen p vao cuoi danh sach if(Last==NULL) //Danh sach rong
Last->Next = p; //1 p->prev = Last; //2 Last = p; //3 }
171 void Input() { int n; coutn; for(int i=1;i>x;
//B2: chen p vao sau T p->Next = T->Next; //1 p->prev = T; //2 T->Next->prev = p; //3 T->Next = p; //4 }
NODE *p = First; for(int i=1;iNext; return p;
//B1: dinh vi den nut can xoa
//B2: Xu ly truoc khi xoa if(First==Last) //danh sach chi co 1 nut
First = p->Next; //First qua nut ke tiep First->prev = NULL;
//B1: dinh vi den nut can xoa
//B2: Xu ly truoc khi xoa if(First==Last) //danh sach chi co 1 nut
Last = p->prev; //Last qua nut ke tiep Last->Next = NULL;
} void Del(NODE *T) //xoa nut dung sau nut T {
//B1: dinh vi den nut can xoa
//B2: xu ly truoc khi xoa
PHỤ LỤC B: CÀI ĐẶT STACK
B1 CÀI ĐẶT STACK BẰNG DANH SÁCH LIÊN KẾT
#include using namespace std; class Stack
//B2: Chen p vao dau danh sach p->Next = top; //1 top = p; //2 } int Pop() {
NODE *p = top; top = p->Next; int x = p->Data; delete p; return x;
} bool isEmpty() { return top==NULL;
//B2: Dua cac so du n%2 vao stack while(n>0)
//B3: Lay cac phan tu trong stack in ra man hinh while(!S.isEmpty())
B2 TÍNH GIÁ TRỊ BIỂU THỨC SỐ HỌC
#include using namespace std; bool Sosanh(string x,string y)
{ int a,b; if(x=="(") a=0; if(y=="(") b=0; if(x=="-") a=1; if(y=="-") b=1; if(x=="+") a=1; if(y=="+") b=1; if(x=="/") a=2; if(y=="/") b=2; if(x=="*") a=2; if(y=="*") b=2; return (a>=b);
} void toArrayString(string st, int &n,string a[])
//Tách chuỗi st thành mảng các chuỗi a[]
{ string temp = ""; if(st[0]=='+'|| st[0]=='-'|| st[0]=='*'|| st[0]=='/'|| st[0]=='(' || st[0]==')')
{ temp = temp + st[0]; st.erase(0,1); a[++n] = temp;
{ int i=0; while((i='0') &&(st[i]prev; //rear qua nut ke tiep rear->Next = NULL;
//Dua cac so du n%10 vao Queue while(n>0)
//Lay cac phan tu trong Queue in ra man hinh while(!Q.isEmpty())