TỔNG QUAN VỀ CẤU TRÚC DỮ LIỆU VÀ GIẢI THUẬT
CÁC KHÁI NIỆM
Trong máy tính, dữ liệu là thông tin được mã hóa thành định dạng dễ xử lý, đặc biệt là dưới dạng số nhị phân Trong lĩnh vực khoa học máy tính hiện đại và truyền thông, việc chuyển đổi thông tin thành dữ liệu số là rất quan trọng.
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 cách khác nhau để lưu trữ 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ữ trong các vùng nhớ liên tiếp thông qua mảng, hoặc có thể được lưu trữ ở 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 được lưu trữ theo cách khác nhau Ví dụ, cấu trúc chuỗi ký tự và cấu trúc mảng đều sử dụng các ô nhớ liên tiếp để lưu trữ thông tin.
1.1.3 Lựa chọn cấu trúc dữ liệu cho bài toán
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 là một bước quan trọng, từ đó xác lập giải thuật nhằm đạt được kết quả mong muốn.
Khi chọn một cấu trúc dữ liệu, cần xem xét các phép toán sẽ tác động lên nó Ngược lại, khi phân tích các phép toán, cần lưu ý rằng mỗi phép toán có thể chỉ hiệu quả với một số cấu trúc dữ liệu nhất định, trong khi có thể không hiệu quả với những 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 lệnh rõ ràng, xác định trình tự thao tác trên các đối tượng đầu vào (input) để đạt được kết quả mong muốn (output) sau một số bước thực hiện nhất định.
According to Donald Knuth in "The Art of Computer Programming," an algorithm is defined as a finite, deterministic, and efficient 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 để tạo ra các đại lượng đầu ra xác định, mà những đại lượng này chính là nghiệm hoặc kết quả của bài toán.
Giải thuật yêu cầu tính đúng đắn, nghĩa là với mỗi bộ dữ liệu đầu vào xác định, 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 rằng 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 cho kết quả giống nhau Hơn nữa, các bộ xử lý thuật toán 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 luôn mang lại kết quả như mong đợi.
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, vì nó xác định cách thức xử lý dữ liệu Giải thuật liên quan mật thiết đến đối tượng cần xử lý, trong khi cấu trúc dữ liệu là cách tổ chức và biểu diễn thông tin để lưu trữ và xử lý hiệu quả.
Theo lập trình có cấu trúc, Niklaus Wirth đã phát biểu một công thức quan trọng 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 điều chỉnh để phù hợp với cách tổ chức dữ liệu mới Ngược lại, quá trình phát triển và hoàn thiện giải thuật cũng giúp lập trình viên định hình cách tổ chức dữ liệu một cách hiệu quả, tiết kiệm tài nguyên hệ thống.
Quá trình giải bài toán trên máy tính đòi hỏi sự 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 xác lập các thuật toán tương ứng để đảm bảo hiệu quả trong việc giải quyết vấn đề.
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 giải thuật là rất quan trọng để kiểm tra xem nó 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 để thể hiện giải thuật một cách rõ ràng Mã giả không chỉ giúp tận dụng các khái niệm trong ngôn ngữ lập trình mà còn tạo điều kiện cho người cài đặt dễ dàng hiểu nội dung của giải thuật Tuy nhiên, mã giả vẫn bao gồm một phần ngôn ngữ tự nhiên, và do đó, nó sẽ phụ thuộc vào ngôn ngữ lập trình mà chúng ta đã vay mượn cú pháp và khái niệm.
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 sơ đồ khối là công cụ trực quan hiệu quả để diễn đạt giải thuật Việc sử dụng lưu đồ giúp tránh sự nhập nhằng về ngữ nghĩa trong các giải thích bằng lời, đồng thời cung cấp cái nhìn tổng quan về toàn bộ quá trình xử lý của một giải thuật.
Lưu đồ là một hệ thống gồm các nút với hình dạng đa dạng, thể hiện các chức năng khác nhau và được kết nối bằng các cung Nó được cấu thành từ 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 theo dạng 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 Quá trình này tiếp tục cho đến khi gặp nút kết thúc, lúc đó 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 là rất quan trọng Thời gian thực hiện có thể nhanh hoặc chậm tùy thuộc vào nhiều yếu tố, và việc phân tích 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 để đo lường hiệu quả thực thi Độ phức tạp thời gian của thuật toán được xác định thông qua số lượng phép toán tích cực, tức là những phép toán có thời gian thực thi không thấp hơn so với các phép toán khác, khi kích thước đầ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
Giải thuật có kích thước dữ liệu 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² Ta có thể chứng minh rằng f(n) = O(n²) vì n(n+3)/2 ≤ n² với mọi n ≥ 3 (C=1, n₀=3) Theo định nghĩa, nếu độ phức tạp của thuật toán là 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())