You are on page 1of 30

TRƯỜNG ĐẠI HỌC GIAO THÔNG VẬN TẢI

PHÂN HIỆU TẠI TP. HỒ CHÍ MINH

BÁO CÁO BÀI TẬP LỚN


MÔN HỌC: CẤU TRÚC DỮ LIỆU VÀ GIẢI THUẬT

Giảng viên: TRẦN THỊ DUNG


Sinh viên : VÕ VĂN TUẤN
Lớp: CQ.62.KTĐTVT
MSSV: 6251020094
Khóa: 62

Tp. Hồ Chí Minh, năm 2022


CHƯƠNG 1. MỞ ĐẦU
I. VAI TRÒ CỦA CẤU TRÚC DỮ LIỆU
1. Tổ chức biểu diễn các đối tượng thực tế:
- Dữ liệu thực tế:
o Đa dạng, phong phú, phức tạp.
o Có quan hệ với nhau.
- Tổ chức thành cấu trúc:
o Phản ánh dữ liệu chính xác với thực tế.
o Dễ dàng xử lý trong máy tính.
2. Xây dựng các thao tác xử lý dữ liệu:
- Dựa trên yêu cầu của bài toàn để xác định các thao tác xử lý
vấn đề trên máy tính để đưa ra kết quả đúng.

Yêu cầu Thao tác xử lý Kết quả mong


muống
bài toán
3. Vai trò CTDL trong 1 đề án tin học:
Cấu trúc dữ liệu+ giải thuật= Program
4. Tiêu chuẩn đánh giá cấu trúc dữ liệu:
- Phản ánh đúng thực tế.
- Phù hợp với thao tác xử lý.
- Tiết kiệm tài nguyên.
5. Đánh giá độ phức tạp của giải thuật:
- Tính đúng đắn.
- Tính hiệu quả.
6. Đánh giá thuật toán:
- Một vấn đề được giải quyết bằng nhiều thuật toán khác nhau.
- Đối với từng thuật toán:
o Khác nhau về không gian: Tài nguyên bộ nhớ sử dụng.
o Khác nhau về thời gian chạy thuật toán.
- Thời gian chạy:
o Kỹ năng lập trình.
o Tốc độ thực hiện các phép toán.
o Dữ liệu vào
7. Thời gian chạy thuật toán:
- Thời gian chạy của một thuật toán phụ thuộc vào dữ liệu vào.
- Có 2 cách đo thời gian chạy thuật toán:
o Đo bằng thực nghiệm. (không phù hợp với chương trình đơn
giản)
o Đo bằng toán học. (thực tế dễ sử dụng hơn)
8. Độ phức tạp về thời gian.
- T(n)= số lượng phép toán sơ cấp cần thực hiện.
- Mỗi phép toán sơ cấp được thực hiện trong một khoảng thời gian cố
định.
- Chỉ xét tốc độ tăng của hảm T(n).
9. Định nghĩa ký hiệu ô lớn (big O):
- f(n) = O(g(n)) nếu tồn tại các hằng số dương c và n0 sao cho f(n) <=
c*g(n) với mọi n >=n0.
10.Các cấp độ thời gian chạy:
Ký hiệu O(1) O(log2n) O(n) O(nlog2n) O(na)
Ý nghĩa Nếu T(n) Độ phức Độ phức Độ phức Độ phức
là hằng số tạp dạng tạp tuyến tạp tuyến tạp đa
(T(n)=C) logarit tính tính logarit thức
CHƯƠNG 2: NGÔN NGỮ LẬP TRÌNH C++
I. GIỚI THIỆU NGÔN NGỮ LẬP TRÌNH C++
- Ngôn ngữ lập trình C++ là ngôn ngữ dựa theo ngôn ngữ C. Vì vậy, cú
pháp của C++ giống với cú pháp của C và có thêm một số mở rộng:
o Nhập (cin), xuất (cout).
o Hàm có đối mặc định, đối tham chiếu.
o Nạp chồng hàm.
o Hàm mẫu.
o Lớp.
1. Nhập xuất dữ liệu:
- Nhập dữ liệu kiểu số: cin>>biến1>>biến2>>…>>biếnn;
Exam: float x,y;
int a,b;
cin>>x>>y>>a>>b;
- Nhập dữ liệu kiểu xâu ký tự:
cin.ignore(1);
cin.get(Tênbiến, n); //n là số ký tự tối đa
Exam: char A[10];
char B[20];
cin.ignore(1);
cin.get(A,10);
cin.ignore(1);
cin.get(B,15);
- Xuất dữ liệu:
cout<<Biểu thưc 1>>Biểu thức 2>>…>>Biểu thức n;
Exam: #include”iostream.h”
int main(){
float x, y;
cout<<“Nhap x=“;
cin>>x;
cout<<“Nhap y=“;
cin>>y;
cout<<“x+y=“<<x+y;
cout<<“x-y=“<<x-y;
}
II. CƠ BẢN VỀ LỚP TRONG C++
1. LẬP TRÌNH HƯỚNG THỦ TỤC VÀ HƯỚNG ĐỐI TƯỢNG
- Cả hai cách tiếp cận đều thực hiện theo phương pháp tinh chỉnh từng
bước.
- Tiếp cận hướng thủ tục (function oriented):
o Tập trung vào các hàm.
o Các cấu trúc dữ liệu (ở mức ngôn ngữ lập tình) được định nghĩa
sớm.
o Các cấu trúc dữ liệu khó thay đổi
- Tiếp cận hướng đối tượng (Object oriented):
o Tập trung vào các đối tượng.
o Các cấu trúc dữ liệu trừu tượng được định nghĩa sớm.
o Cấu trúc dữ liệu chi tiết mức ngôn ngữ chưa được định nghĩa.
o Cấu trúc dữ liệu dễ thay đổi hơn

Exam: Lập chương trình nhập vào tọa độ các đỉnh của 1 tam giác bất kỳ trong mặt
phẳng. Tính diện tích và chu vi của tam giác đó. In kết quả lên màn hình.
- Tiếp cận hướng thủ tục:
o Xây dựng các hàm:
typedef struct Tamgiac{
float xA, yA, xB,yB, xC, yC;
}
void Nhap(Tamgiac &t){
cout<<“Nhap toa do dinh thu nhat:”;
cin>>t.xA>>t.yA;
cout<<“Nhap toa do dinh thu hai:”;
cin>>t.xB>>t.yB;
cout<<“Nhap toa do dinh thu ba:”;
cin>>t.xC>>t.yC;
}
- Tiếp cận hướng đối tượng:
o Xây dựng lớp class:
class Tamgiac{
private:
float xA, yA, xB,yB, xC, yC;
public:
void Nhap();
float Dientich();
float Chuvi();
};
2. KHÁI NIỆM LỚP – KHAI BÁO LỚP
- Lớp là một khái niệm mở rộng của cấu trúc dữ liệu, nó có thể chứa
đựng cả dữ liệu và các hàm.
- Đối tượng (object) là một thể hiện của lớp. Trong lập trình lớp được
xem như là một kiểu dữ liệu, đối tượng là các biến.
EX: class class_name{
access_speccifier_1:
member1;
access_speccifier_2:
member2;

};

*NOTE:
- class_name: tên của lớp.
- access_specifier: các dặc tả (private, protected, public)
- member: các thành phần của lớp (thuộc tính hoặc các hàm)
EX: Khai báo lớp biểu diễn các sinh viên cho các phương thức tính tổng
điểm trung bình và xếp hạng.
class SinhVien{
private:
char Ten;
int Tuoi;
float diemToan,diemLy,diemHoa;
public:
void nhap();
void xuat();
void DiemTB();
void xephangsv();
};
3. CÀI ĐẶT CÁC PHƯƠNG THỨC.
- Ta có thể cài đặt các phương thức bên trong lớp hoặc bên ngoài lớp.
- Cài đặt phương thức bên ngoài lớp:
DataType class_name::Func_name([Argument_list]){
Các câu lệnh;
}
EX:
class chuongtrinh{
private:
int x,y;
public:
void Nhap();
};
void chuongtrinh::Nhap(){
cout<<”nhap so x: “<<endl;
cin>>x;
cout<<”nhap so y: “<<endl;
cin>>y;
}

4. CON TRỎ THIS.


- Tất cả các phương thức của lớp đều có một đối ẩn là một con trỏ (this)
có kiểu là kiểu lớp chứa phương thức đó.
EX:
class A {
private:

public:
DataType method();
};
Truy cập đến các thành phần của lớp từ con trỏ this:
- Cách 1: this->property, this->method([arg]);
- Cách 2: property, method([arg]);
EX:
void Tamgiac::Nhap(){
cout<<“Nhap toa do dinh thu nhat:”;
cin>>this->xA>>this->yA;
cout<<“Nhap toa do dinh thu hai:”;
cin>> this-> xB>> this-> yB;
cout<<“Nhap toa do dinh thu ba:”;
cin>> this-> xC>> this->yC;
}
void main() {
Tamgiac t, t1;
t.Nhap();
t1.Nhap();
}
5. PHƯƠNG THỨC CỦA LỚP LÀ CÁC TOÁN TỬ.
- Các nhóm toán tử:
o Toán tử một ngôi: --, ++, -
o Toán tử hai ngôi: +, -, *, /, …..
o Toán tử so sánh: >, >=, <, <=, !, !=, …
o Toán tử nhập xuất (>>, <<)
o Toán tử new
6. CÀI ĐẶT CÁC PHƯƠNG THỨC TOÁN TỬ.
- Toán tử một ngôi:
class classname{
private:

public:
DataType operator sign();
DataType operator sign();

};
(sign là dấu toán tử : ++,--)
- Toán tử hai ngôi:
class classname{
private:

public:
DataType operator sign(DataType1 argN);
DataType operator sign(DataType1 argN);

};
DataType là kiểu trả lại của toán tử
DataType1 là kiểu của đối tượng mà toán tử tác động
sign: là dấu toán (ví dụ: +, -, *, /, … )
- Toán tử nhập xuất:
o Toán tử nhập:
istream& operator >>(istream &is, classname &obj)
{
cout<<“Thông báo:”;
is>>obj.property;

return is;
}
o Toán tử xuất:
ostream& operator <<(ostream &os, classname &obj)
{
os<< “Thông báo:” << obj.property;

return os;
}

CHƯƠNG 3. DANH SÁCH LIÊN KẾT


1. Danh sách liên kết đơn:
- Các nút (node) được cài đặt bao gồm:
o Phần tử lưu trữ trong nó
o Một liên kết đến nút kế tiếp
- Sử dụng môt con trỏ header, trỏ vào node đầu danh sách và con trỏ
trailer trỏ vào node cuối danh sách.

2. Cấu trúc của một Node


- Các thuộc tính:
o Element elem;
o Node *next;
- Các phương thức
o Node *getnext() :Trả lại địa chỉ của nút kế tiếp
o Element getElem() : Trả lại giá trị phần tử lưu trữ trong nút
o void setNext(Node *) : Gán địa chỉ cho thuộc tính next
o void setElem(Element e) : Gán giá trị e cho thuộc tính elem
3. cấu trúc danh sách liên kết đơn
- Các thuộc tính:
o Node *header
o Node *trailer
- Các phương thức chung:
o int size(),
o bool empty()
- Các phương thức truy cập:
o front()
o back()
- Các phương thức cập nhật:
o void push_front(T e)
o void push_back(T e)
o void pop_front()
o void pop_back()
- Bộ lặp xuôi:
o begin
o end
o =
o !=
o ++
o *
- Chèn và xóa
o insert
o erase
a. Thêm vào đầu:
- Hình ảnh phép toán push_front(), phép toán trả lại vị trí q

b. Thêm vào cuối:


- Hình ảnh phép toán push_back(), phép toán trả lại vị trí q

c. Chèn:
- Hình ảnh phép toán insert(p, X), phép toán trả lại vị trí q
d. Xóa:

4. Cấu trúc danh sách liên kết kép


- Các thuộc tính:
o Node *header
o Node *trailer
- Các phương thức chung:
o int size(),
o bool empty()
- Các phương thức truy cập:
o front()
o back()
- Các phương thức cập nhật:
o void push_front(T e)
o void push_back(T e)
o void pop_front()
o void pop_back()
- Bộ lặp xuôi:
o Begin
o End
o =
o !=
o ++
- Chèn và xóa
o Insert
o Erase
- Các phương thức cập nhật:
o void push_front(T e)
o void push_back(T e)
o void pop_front()
o void pop_back()
- Bộ lặp xuôi:
o begin
o end
o =
o !=
o ++
o *
- Bộ lặp ngược:
o rbegin
o rend
o =
o !=
o ++
o *
a. Thêm vào đầu:
- Hình ảnh phép toán push_front(X), phép toán trả lại vị trí q
b. Thêm vào cuối:
- phép toán push_back( X), phép toán trả lại vị trí q

c. Chèn:
- phép toán Insert(p, X), phép toán trả lại vị trí q

- thuật toán chèn:


Algorithm insert (p,e): //Bổ sung phần tử e vào phần tử nút p
Tạo ra một nút mới q
q->setElement(e) //Đặt gia trị e vào nút q
q->setNext(p->getNext())//liên kết với phần tử sau nó
p->getNext()->setPrev(q)//Liên kết phần tử sau p với q
q->setPrev(p)//liên kết q với phần tử trước nó
p->setNext(q)//liên kết p với q
return q//trả lại vị trí của q
d. Xóa
Algorithm erase(p):
//kết nối phần tử trước p với phần tử sau p
p->getPre()->setNext(p->getNext())
//kết nối phần tử sau p với phần tử trước p
p->getNext()->setPre(p->getPre())
//bỏ kết nối p với phần tử trước nó
p->setPre(NULL)
p->setNext(NULL)
delete p

CHƯƠNG 4. CẤU TRÚC NGĂN XẾP (STACK)


- Stack là cách tổ chức lưu trữ các đối tượng dưới dạng một danh sách
tuyến tính mà việc bổ sung đối tượng và lấy các đối tượng ra được
thực hiện ở cùng một đầu của danh sách.
- Stack được gọi là danh sách kiểu LIFO (Last In First Out - vào sau ra
trước)
1. Các vấn đề cần nghiên cứu
- Cấu trúc dữ liệu trừu tượng Stack (ADT Stack)
- Những ứng dụng của Stack
- Cài đặt Stack dựa trên mảng
- Sự phát triển stack dựa trên mảng
2. Cấu trúc dữ liệu trừu tượng (ADT- Abtract Data Type)
- Các thành phần
o Dữ liệu được lưu trữ
o Các phép toán trên dữ liệu
o Các điều kiện xảy ra lỗi kết hợp với các phép toán
3. Cấu trúc dữ liệu trừu tượng Stack
- Stack ADT lưu trữ các đối tượng bất kỳ
- Bổ sung và lấy ra các phần tử theo kiểu “Vào sau ra trước” – “Last In
First Out”
- Các phép toán chính:
o push(Object o): bổ sung đối tượng o vào Stack
o pop(): lấy ra và trả lại phần tử được bổ sung vào cuối cùng của
Stack
- Các phép toán bổ trợ
o top() trả lại tham chiếu đến phần tử được bổ sung vào cuối cùng
của Stack
o size(): trả lại số phần tử hiện lưu trữ trong Stack
o empty(): trả lại giá trị kiểu boolean để xác định Stack có lưu trữ
phần tử nào hay không
4. Một số ứng dụng của Stack
- Các ứng dụng trực tiếp:
o Lưu lại các trang Web đã thăm trong một trình duyệt
o Thứ tự Undo trong một trình soạn thảo
o Lưu chữ các biến khi một hàm gọi tới hàm khác, và hàm được
gọi lại gọi tới hàm khác, và cứ tiếp tục như vậy.
- Các ứng dụng gián tiếp:
o Cấu trúc dữ liệu bổ trợ cho một số thuật toán
o Là một thành phần của những cấu trúc dữ liệu khác
5. Cài đặt Stack bằng mảng
- Cách đơn giản nhất cài đặt một Stack là sử dụng một mảng
- Chúng ta thực hiện bổ sung phần tử vào từ trái qua phải
- Sử dụng một biến t lưu chỉ số của phẩn tử ở đỉnh của Stack

CHƯƠNG 5. HÀM (FRUNCTION)


1. Khái niệm fruction
- Hàm (function) là một đoạn chương trình có tên, đầu vào và đầu ra.
Hàm có chức năng giải quyết một số vấn đề chuyên biệt cho chương
trình chính. Hàm được gọi nhiều lần với các tham số khác nhau.
- Hàm là một đơn vị độc lập của chương trình, không cho phép xây
dựng một hàm bên trong một hàm khác.
- Một chương trình bao gồm một hoặc nhiều hàm. Trong đó, hàm
main() là thành phần bắt buộc của chương trình.
2. Cú pháp khai báo hàm và viết hàm
- Cú pháp:
<kiểu trả về> <tên hàm>([danh sách tham số]){
<các câu lệnh>;
<return[giá trị]>;
}
- Trong đó:
o <kiểu trả về>: (char, int, float, …..). Nếu không trả về thì void.
o <tên hàm>:theo quy tắc đặt tên định danh.
o <Danh sách tham số>: giống khai báo biến, cách nhau dấu “,”.
o <giá trị>:trả về cho hàm qua lệnh return
- Các bước viết hàm:
o Tên hàm.
o Hàm sẽ thực hiện công việc gì.
o Các đầu vào (nếu có).
o Đầu ra (nếu có).
- Để sử dụng hàm thì cần định nghĩa hàm trước và gọi hàm.

EX:
#include <iostream>
using namespace std;
int max_value(int a, int b)
{
if (a > b)
return a;
else
return b;
}
int main(){
int x1, y1, x2, y2, x3, y3;
cin >> x1 >> y1 >> x2 >> y2 >> x3 >> y3;
// Lời gọi hàm.
cout << "Max giữa x1 và y1 là: " << max_value(x1, y1) << endl;
cout << "Max giữa x2 và y2 là: " << max_value(x2, y2) << endl;
cout << "Max giữa x3 và y3 là: " << max_value(x3, y3);

return 0;
}

CHƯƠNG 5. TÌM KIẾM (SERCH)

1. Tìm kiếm tuần tự (sequence search)


- Thuật toán :
o Xuất phát từ phần tử đầu của dãy, thực hiện so sánh khóa của
nó với k. Nếu trùng nhau thì dừng lại, nếu không trùng thì lặp
lại với phần tử tiếp theo.
o Quá trình dừng lại khi tìm thấy hoặc không còn phần tử nào
nữa. Khi đó thông báo không tìm thấy.
- Chương trình:
while (i<n){
if (S[i].key == k)
return i;
i = i+1; //Chuyển sang phần tử kế tiếp
}
return -1;
- Thời gian chạy: O(n)

2. Tìm kiếm nhị phân (Binary search)


- Tìm kiếm nhị phân trên mảng:
o Thuật toán tìm kiếm nhị phân được thiết kế dựa trên chiến lược
loại trừ
o Thuật toán: So sánh khóa k với khóa của phần tử ở giữa dãy.
 Nếu trùng thì thông báo tìm thấy và dừng
 Nếu k> thì gọi đệ qui tìm trên nửa cuối dãy
 Nếu k< thì gọi đệ qui tìm trên nửa đầu dãy
 Quá trình tìm nếu phải tìm trong dãy rỗng thì dừng lại và
thông báo không tìm thấy
- Chương trình:
i = 1;
j = n;
while(i<=j){
mid = (i+j) / 2;
if (k==S[mid].Key
return mid;
else
if (k < S[mid].Key)
j = mid – 1;
else
i = mid+1;
}
return -1;
3. Bảng băm
a. Cấu trúc hàm băm
- Hàm băm có dạng như sau: h : K → 0..m-1

- Trong đó:
o h được gọi là hàm băm (hash function)
o K là tập giá trị khóa
o 0..m-1 là bảng địa chỉ (là các số nguyên)
o m là kích thước của bảng
- Yêu cầu khi xây dựng hàm băm:
o Hàm phải dải đều các địa chỉ trên bảng địa chỉ
o Hàm băm phải được tính toán đơn giản.
b. Một số phương pháp xây dựng hàm băm
- Phương pháp chia
o Để tính địa chỉ dải của đối tượng ta lấy giá trị khóa chia cho
kích thước của bảng. Địa chỉ dải là phần dư của phép chia đó.
H(K) = K % m
o Yêu cầu:
 hàm h phải dải đều các đối tượng trên bảng một cách
ngẫu nhiên. Để có được điều đó h phải phụ thuộc vào m.
 Phụ thuộc vào m
 Thông thường người ta chọn m là một số nguyên tố nhỏ
hơn gần với (10,100, 1000,...) nhất.
- Phương pháp nhân
o Giá trị khóa được phân ra thành nhiều đoạn bằng nhau
 Người ta sử dụng hai kỹ thuật phân đoạn sau đây:
 Tách: Tách các đoạn ra và mỗi đoạn được xếp thành một
hàng, dóng lề trái hoặc lề phải.
 Gấp: Gấp các đoạn lại theo đường biên tương tự như gấp
giấy, các chữ rơi vào cùng một chỗ được đặt thành hàng
thẳng nhau.
c. Bảng băm - Hash table
- Một bảng băm là một cấu trúc dựa trên mảng để lưu trữ các phần tử,
mỗi phần tử là một cặp Khóa-Giá trị (key-value)
- Các thành phần cấu thanh lên bảng băm:
o Mảng chứa
o Mỗi phần tử mảng quản lý một danh sách các phần tử có khóa
qua ánh xạ h cho cùng một địa chỉ.
o Hàm băm h(k) - Hash function, h(k)
o Mã băm

EX: Giả sử có hàm h(k) = k % 5 Có các giá trị: 11, 21, 44, 23, 41,
4, 34, 12

d. Cấu trúc dữ liệu bảng băm


- Thuộc tính:
o Mảng (mỗi phần tử mảng lưu một danh sách các phần tử)
o m: kích thước mảng
- Các phương thức:
o Node *Add(Key, Object)
o void Remove(Key)
o Node *Find(Key)
o bool Contains(Key)
o int Count()

CHƯƠNG 6. SẮP XẾP (Sorting)

1. Sắp xếp nổi bọt


- Thực hiện chuyển dần các phân tử có giá trị khóa nhỏ về đầu dãy, các
phần tử có khóa lớn về cuối dãy.
- Thuật toán

o Lần lặp thứ 1:

 B1: so sánh 1 với 5

 B2: so sánh 5 với 4

 B3: so sánh 5 với 2

 B4: so sánh 5 với 8


o Lần lặp thứ 2:

 B1: so sánh 4 với 2

 B2: so sánh 4 với 5

 B3: so sánh 5 với 8


o Lần lặp thứ 3:

 B1: so sánh 4 với 5

 B2; so sánh 5 với 8


- Chương trình:

for (int i=0;i<x-2;i++){

for (int j =y; j>i; j--){

if (i<j){
swap(i,j);

2. Sắp xêp chọn (selection sort)


- Ý tưởng: Chọnphần tử có khóa nhỏ nhất trong các phần tử còn lại
chuyển nó về đầu và loại bỏ nó khỏi dãy.
- Ví dụ sắp xếp theo thứu tự tăng dần:

o B1: chọn phần tử nhỏ nhất -1 đặt vào vị trí thứ nhất

o B2: chọn phần tử nhỏ thứ 2 là 0 đặt vào vị trí hai

o B3: chọn phần tử nhỏ thứ 3 là 3 đặt vào vị trí ba

o B4: chọn phần tử nhỏ thứ 4 là 6 đặt vào vị trí tư và phần tử lớn
nhất được đẩy về cuối

- Chương trình:
 int i, j, idx, temp;
   for (i = 0; i < N-1; i++)
   {
       idx = i;
       for (j = i+1; j < N; j++)
       {
           if (arr[i] < arr[j])
           {
               idx = j;
    swap(&arr[i], &arr[idx]);
           }
       }
   }
}
3. Sắp xếp nhanh – Quick sort
- Thuật toán:
o Chọn điểm đánh dấu cho mảng, ở đây mình sẽ chọn điểm đánh
sấu là số cuối cùng của mảng.
o Tạo hai biến là trái và phải để trỏ tới bên trái và bên phải của
danh sách.
o Thực hiện so sánh các phần tử với điểm đánh dấu. Nếu phần tử
nhỏ hơn điểm đánh dấu thì dịch chuyển qua bên trái và ngược
lại.
o Sau khi dịch chuyển thực hiện công việc sắp xếp các phần tử
trong mảng con mới, trước khi tiếp tục phân đoạn tiếp theo.
- Chương trình :
void quickSort(int a[], int l, int r){
int p = a[(l+r)/2];
int i = l, j = r;
while (i < j){
while (a[i] < p){
i++;
}
while (a[j] > p){
j--;
}
if (i <= j){
int temp = a[i];
a[i] = a[j];
a[j] = temp;
i++;
j--;
}
}
if (i < r){
quickSort(a, i, r);
}
if (l < j){
quickSort(a, l, j);
}
}
4. Sắp xếp trộn – Merge sort
- Ý tưởng:
o Tìm chỉ số nằm giữa mảng đề chia mảng thành 2 nửa.
o Gọi đệ quy hàm mergeSort cho nửa đầu tiền.
o Gọi đệ quy hàm mergeSort cho nửa thứ hai.
o Gộp 2 nửa mảng đã sắp xếp ở trên.
- Chương trình:
void merge(int arr[], int l, int m, int r)
{
    int i, j, k;
    int n1 = m - l + 1;
    int n2 =  r - m;
    int L[n1], R[n2];//tạo 2 mảng tạm thời để chứa các phần tử sau khi chia
    // Copy dữ liệu sang các mảng tạm
    for (i = 0; i < n1; i++)
        L[i] = arr[l + i];
    for (j = 0; j < n2; j++)
        R[j] = arr[m + 1+ j];
    // khởi tạo các giá trị ban đầu
    i = 0;
    j = 0;
    k = l;
    //gộp hai mảng tạm thời vào mảng arr
    while (i < n1 && j < n2)
    {
        if (L[i] <= R[j])
        {
            arr[k] = L[i];
            i++;
        }
        else
        {
            arr[k] = R[j];
            j++;
        }
        k++;
    }
    // Copy các phần tử còn lại của mảng L vào arr nếu có
    while (i < n1)
    {
        arr[k] = L[i];
        i++;
        k++;
    }
    // Copy các phần tử còn lại của mảng R vào arr nếu có
    while (j < n2)
    {
        arr[k] = R[j];
        j++;
        k++;
    }
}
// l là chỉ số trái và r là chỉ số phải của mảng cần được sắp xếp
void mergeSort(int arr[], int l, int r)
{
    if (l < r)
    {
        int m = l+(r-l)/2;
        // Gọi hàm đệ quy tiếp tục chia đôi từng nửa mảng
        mergeSort(arr, l, m);
        mergeSort(arr, m+1, r);
  
        merge(arr, l, m, r);
    }
}
5. Sắp xếp vun đống – Heap sort
- là một cấu trúc dữ liệu dạng cây, trong đó các nút trên cây được sắp xếp
theo một thứ tự ràng buộc nhất định giữa khóa của nút cha và khóa của
nút con (thường là nút cha nhỏ hơn hoặc lớn hơn nút con). Nút ở gốc
của Heap luôn luôn là nút có mức ưu tiên cao nhất, nghĩa là lớn nhất
hoặc nhỏ nhất.
- giải thuật:
o Chia mảng ban đầu thành hai mảng con: Một mảng bao gồm
các phần tử đã sắp xếp và một mảng bao gồm các phần tử còn
lại chưa được sắp xếp. Ban đầu, mảng đã sắp xếp là mảng rỗng,
mảng chưa sắp xếp chính là mảng ban đầu.
o Tìm phần tử nhỏ nhất/lớn nhất trong mảng chưa sắp xếp và đưa
nó vào cuối mảng đã sắp xếp (tùy vào cách sắp xếp là tăng dần
hay giảm dần). Đây chính là bước cần sử dụng đến cấu trúc dữ
liệu Heap.
- Các bước chạy lệnh:
EX: sắp xếp dãy số Cho mảng a=(2,3,5,6,4,1,7), Ở đây n = 7. Các
phần tử từ a[4] đến a[7] là lá
o Bước 1: Tạo đống
 Vun cây gốc a[3] ta được mảng a=(2,3,7,6,4,1,5)
 Vun cây gốc a[2] ta được mảng a=(2,6,7,3,4,1,5)
 Vun cây gốc a[1] ta được mảng a=(7,6,5,3,4,1,2)
Bây giờ a=(7,6,5,3,4,1,2) đã là đống.
o Bước 2: Sắp xếp vun đống
 Đổi chỗ a[1] với a[7]: a=(2,6,5,3,4,1,7) và vun lại mảng
a[1..6] ta được mảng a=(6,4,5,3,2,1,7)
 Đổi chỗ a[1] với a[6]: a=(1,4,5,3,2,6,7) và vun lại mảng
a[1..5] ta được mảng a=(5,4,1,3,2,6,7)
 Đổi chỗ a[1] với a[5]: a=(1,4,2,3,5,6,7) và vun lại mảng
a[1..4] ta được mảng a=(4,3,1,2,5,6,7)
 Đổi chỗ a[1] với a[4]: a=(1,3,2,4,5,6,7) và vun lại mảng
a[1..3] ta được mảng a=(3,2,1,4,5,6,7)
 Đổi chỗ a[1] với a[3]: a=(2,1,3,4,5,6,7) và vun lại mảng
a[1..2] ta được mảng a=(2,1,3,4,5,6,7)
 Đổi chỗ a[1] với a[2]:a=(1,2,3,4,5,6,7) Mảng còn lại chỉ
một phần tử.
 Quá trình sắp xếp đã xong.
- Chương trình:
void heapify(int arr[], int n, int i)
{
int largest = i; // khoi tao largest nhu la root
int l = 2 * i + 1; // left = 2*i + 1
int r = 2 * i + 2; // right = 2*i + 2

// Neu nut con trai lon hon so voi root


if (l < n && arr[l] > arr[largest])
largest = l;

// Neu nut con phai lon hon so voi root


if (r < n && arr[r] > arr[largest])
largest = r;

// Neu root khong phai la lon nhat


if (largest != i)
{
swap(arr[i], arr[largest]);

// De quy lai ham heapify


heapify(arr, n, largest);
}
}

// Ham vun dong


void heapSort(int arr[], int n)
{
// Tao mot dong (Sap xep lai mang)
for (int i = n / 2 - 1; i >= 0; i--)
heapify(arr, n, i);

// Trích xuất từng một phần tử một từ heap


for (int i = n - 1; i >= 0; i--)
{
// Di chuyen root ve cuoi cung
swap(arr[0], arr[i]);

// goi ham heapify


heapify(arr, i, 0);
}
}

void printArray(int arr[], int n)


{
for (int i = 0; i<n; ++i)
cout << arr[i] << " ";
cout << "\n";
}

int main()
{
int arr[] = { 12, 11, 13, 5, 6, 7 };
int n = sizeof(arr) / sizeof(arr[0]);
heapSort(arr, n);
cout << "Sorted array is \n";
printArray(arr, n);
cin.get();
getchar();
return 0;
}
CHƯƠNG 7: VECTOR
1. Khái niệm về vector
- Vector là một mảng động có thể thay đổi kích thước,không nhất thiết phải
khai báo kích thước cố định như mảng tĩnh.Nó có thể tự động tăng hay
giảm kích thước khi ta xóa hoặc chèn phần tử khác vào vector

2. Một số điểm nổi bật của vector


Không cần phải khai báo kích thước của mảng vì vector có thể tự động tăng
kích thước lên.
- Nếu bạn nâng thêm 1 phần tử vào vector đã đầy rồi, thì nó sẽ tự động tăng
kích thước của nó để dành chỗ cho giá trị mới này.
- Vector còn giúp bạn biết số lượng các phần tử mà bạn đã lưu trong đó.
- Dùng số phần tử âm vẫn được trong vector.
3. Cú pháp của một vector:
Vector< kiểu dữ liệu >tên vector.
- Để sử dụng được cấu trúc dữ liệu vector ta phải khai báo thư viện
vector
o Hàm push_back(): Là hàm cho phép ta đẩy 1 phần tử vào vị trí
sau cùng của vector.
o Hàm pop_back(): Là hàm cho phép ta xóa phần tử cuối cùng
của vector.
o Hàm size: Là hàm cho phép ta kiểm tra số lượng phần tử trong
vector.
Ví dụ:
#include<iostream>
#include<vector>
using namespace std;
int main(){
vector<int>v;
v.push_back(10);
v.push_back(20);
v.push_back(30);
v.push_back(50);
v.pop_back();
cout<<v.size()<<endl;
for(int i=0;i<v.size();i++){
cout<<v[i]<<endl;
}
}
CHƯƠNG 8. CÂY
1. Tổng quát:
- Cây là một tập các nút với quan hệ cha-con (parent-child) giữa các nút.
Trong đó có một nút được gọi là gốc và nó không có cha.
- Các ứng dụng:
o Tổ chức biểu đồ
o Hệ thống file
o Các môi trường lập trình …
2. Một số khái niệm:
- Gốc (root): gốc là nút không có nút cha ( vd: A)
- Nút trong: Nút có ít nhất một nút con (Vd: A, B, C, F)
- Nút ngoài (lá): nút không có nút con (Vd: E, I, J, K, G, H, D)
- Đô sâu của một nút: Nút gốc có độ sâu là 0, nếu nút cha có độ sâu là
h thì nút con có độ sâu là h+1
- Chiều cao của cây: là giá trị lớn nhất của độ sâu của tất cả các nút (3)
- Cây con: Cây bao gồm một số nút của một cây ban đầu

3. Cấu trúc dữ liệu cây


- Định nghĩa Cấu trúc dữ liệu cây là một cấu trúc dữ liệu phi tuyến, trừu
tượng, phân cấp có quan hệ cha con giữa hai node kề nhau gồm:
o Một node gốc không có cha
o Và các cây con của nó sao cho 1 node bất kỳ đều có duy nhất
một đường đi tới gốc do mỗi node có duy nhất 1 cha.
- Các phương thức chung:
o int size()
o int isEmpty()
- Các phương duyệt cây:
o void preorder(Node*)
o void inorder(Node*)
o void postorder(Node*)
- Các phương thức truy cập:
o Địa chỉ root()
- Các phương thức truy vấn:
o int isInternal(Node*)
o int isExternal(Node*)
o int isRoot(Node*)
- Thêm vào đó là những phương thức cập nhật được định nghĩa trong
các cấu trúc dữ liệu tạo Tree ADT (Node tạo cây)
- Phương thức thêm phần tử vào cây.
o void insert(Node* parent, Element e)
- Phương thức xóa phần tử
o void remove(Node*);
4. Các phương thức duyệt
a. Preorder (tiền thứ tự) : Gốc rồi đến các cây con
- Duyệt cây là cách đi thăm các nút của cây theo một hệ thống
- Duyệt theo thứ tự trước, tức là: nút cha được thăm trước sau đó thăm
các nút con, cháu, …

Algorithm preOrder(v)

If(v!=null)

visit(v)

for mỗi nút con w của v

preorder (w)

b. Inorder (trung thứ tự) : Con cả đến Gốc rồi các con còn lại
- Duyệt theo thứ tự giữa, tức là: nút con được thăm trước sau đó thăm
nút cha
- Ứng dụng: Tính toán không gian sử dụng bởi các files và
các thư mục con

Algorithm inOrder(v)
If(v!=null)
w = con cả của v
inOrder(w)
visit(v)
for mỗi nút con w1#w của v
inOrder (w1)

c. Postorder (Hậu thứ tự) : Các con rồi đến gốc


- Duyệt theo thứ tự sau, tức là: nút con được thăm trước sau đó thăm nút
cha
- Ứng dụng: Tính toán không gian sử dụng bởi các files và các thư mục
con

Algorithm postOrder(v)

If(v!=null)

for mỗi nút con w của v

postOrder (w)

visit(v)

5. Cây nhị phân (Binary tree)


- Cây nhị phân là một cây có các tính chất sau:
o Mỗi một nút trong có nhiều nhất 2 nút con
o Các nút con của một nút là một cặp có thứ tự
- Chúng ta gọi con của một nút trong là con trái và con phải
- Định nghĩa cây nhị phân bằng đệ qui:
o Cây nhị phân là:
 Một cây chỉ có một nút hoặc
 Là cây mà nút gốc của nó có cặp nút con có thứ tự, mỗi
một nút con là gốc của một cây nhị phân
- Ứng dụng:
o Biểu diễn các biểu thức toán học
o Quá trình quyết định
o Tìm kiếm
6. Cây biểu thức
- Cây nhị phân biểu diễn một biểu thức toán học
o Các nút trong: là các toán tử (operators)
o Các nút ngoài: các toán hạng (operands)
- Ví dụ: Cây biểu thức cho biểu thức (2 × (a − 1) + (3 × b))

7. Cây quyết định (Decision tree)


- Cây kết hợp với một quá trình quyết định
o Các nút trong: Các câu hỏi với câu trả lời yes/no
o Các nút ngoài: các quyết định
- Ví dụ: Cây quyết định tuyển nhân viên

8. Một số định nghĩa


- Cây nhị phân hoàn chỉnh: Là cây nhị phân mà tất cả các nút trong của
nó đều có đủ hai nút con
- Cây nhị phân đầy đủ: là cây nhị phân hoàn chỉnh và tất cả các lá đều ở
cùng mức

You might also like