Xác định các yêu cầu của bài toán, xác định các lớp, các thuộc tính, các phương thức của lớp...13... Xác định các yêu cầu của bài toán, xác định các lớp, các thuộc tính, các phương thức
Trang 1TRƯỜNG ĐẠI HỌC GIAO THÔNG VẬN TẢIKHOA CÔNG NGHỆ THÔNG TIN
-o0o -CẤU TRÚC DỮ LIỆU VÀ GIẢI THUẬT
Bài tập lớn môn học
Đề tài: Xây dựng đồ thị vô hướng
Xây dựng cây nhị phân HeapGiảng viên hướng dẫn :
TS Hoàng Văn ThôngSinh viên thực hiện:
Phạm Thu Quỳnh – 223630708Lớp:
Khoa học máy tính K63Hà Nội tháng 11 năm 2023
Trang 2MỤC LỤC
BÀI 18 1
1.Đề bài 1
2.Phân tích bài toán 1
2.1 Xác định các yêu cầu của bài toán, xác định các lớp, các thuộc tính, các phương thức của lớp 1
2.Phân tích bài toán 13
2.1 Xác định các yêu cầu của bài toán, xác định các lớp, các thuộc tính, các phương thức của lớp 13
Trang 3BÀI 18
1 Xây dựng cấu trúc dữ liệu ngăn xếp 2 Xây dựng lớp biểu diễn đồ thị vô hướng có trọng số bằng ma trận kề có các phương thức:
a Nhập đồ thị từ file b Ghi đồ thị ra file c Duyệt đồ thị theo chiều sâu (DFS) d Tìm đường đi ngắn nhất giữa 2 đỉnh bất kỳ 3 Viết hàm main thực hiện các công việc trên
2.1 Xác định các yêu cầu của bài toán, xác định các lớp, các thuộc tính, các phương thức của lớp
2.1.1 Yêu cầu bài toán
1 Xây dựng cấu trúc dữ liệu ngăn xếp Input:
Thêm phần tử vào ngăn xếp Lấy phần tử từ ngăn xếp
Output:
Hiển thị ngăn xếp Kết quả sau thực hiện các phép toán trên ngăn xếp: Ví dụ, nếu ngăn xếp
được sử dụng để thực hiện các phép toán số học hoặc xử lý chuỗi, kết quả cuối cùng sẽ được hiển thị
2 Xây dựng lớp biểu diễn đồ thị vô hướng có trọng số bằng ma trận kề Input:
Nhập đồ thị từ file Duyệt đồ thị theo chiều sâu (DFS) Tìm đường đi ngắn nhất giữa 2 đỉnh bất kỳ
1
Trang 4 Ghi đồ thị ra file: Lưu trữ đồ thị vô hướng có trọng số vào một file. Duyệt đồ thị theo chiều sâu (DFS): Xuất ra thứ tự duyệt đồ thị theo chiều
sâu từ một đỉnh xuất phát. Tìm đường đi ngắn nhất giữa 2 đỉnh bất kỳ: Xuất ra đường đi ngắn nhất
từ 2 đỉnh bất kì và độ dài đường đi của chúng.2.1.2 Xác định và mô tả chức năng của các lớp, các thuộc tính, các phương thức của các lớp
a) Class Stack
Class Stack ở trên được xây dựng trên lớp mẫu template class trong C++, đại diện cho một cấu trúc dữ liệu ngăn xếp
Chức năng nó thực hiện các hoạt động cơ bản của ngăn xếp dựa trên cấu :
trúc dữ liệu liên kết đơn.Dưới đây là mô tả về chức năng của các thuộc tính, phương thức của lớp
void push(const T& x) Thêm một phần tử vào đỉnh ngăn
xếpvoid pop() Loại bỏ phần tử đầu tiên của ngăn
xếpT top() Trả về giá trị phần tử trên cùng của
ngăn xếpbool empty () const Kiểm tra ngăn xếp rỗng hay không
int size() const Trả về số lượng phần tử trong ngăn
xếp
2
Trang 5Dưới đây là mô tả về chức năng của các thuộc tính, phương thức của lớp.
Ma trận kề biểu diễnđồ thị
Phương thức Graph(int vertices) Hàm tạo với số
lượng đỉnh được chỉđịnh
void readGraph_file(string file_name) Đọc đồ thị từ filevoid writeGraph_file(string file_name)
const
Ghi đồ thị ra filevoid addEdge(int u, int v, float weight) Thêm một cạnh với
trọng số giữa 2 đỉnhvoid addVertex(int name) Thêm một đỉnh mới
vào đồ thịfloat getWeight(int u, int v) const Trả về trọng số của
cạnh giữa 2 đỉnhvoid dfs (int startVertex) const Duyệt đồ thị theo
chiều sâuvector<float> dijkstra(int startVertex, Tìm đường đi ngắn
3
Trang 6int endVertex, float& distance) const nhất giữa 2 đỉnh bất
kì
3.1 Class Stack
#ifndef STACK_H#define STACK_H#include<iostream>#include<stdexcept>using namespace std;template <typename T>class Stack{
private: struct Node { T data; Node* next; Node(const T& data, Node* next = nullptr) : data(data), next (next){} };
Node* head; public: Stack() : head(nullptr){} ~Stack(){
while(head){ Node* temp = head; head = head->next; delete temp; }
} void push(const T& x){ head = new Node (x,head); }
void pop(){ if (head){ Node* temp = head; head = head->next; delete temp; }
4
Trang 7throw out_of_range ("Stack is empty."); }
} T top() const{ if (head){ return head->data; } else{
throw out_of_range ("Stack is empty."); }
} bool empty() const{ return head == nullptr; }
int size () const{ int count = 0; Node* current = head; while(current){ count++; current = current->next; }
return count; } };#endif // STACK_H
3.2 Class Graph
- Phương thức khởi tạo:/*CONSTRUCTOR*/
Graph(){ int numVertices = 0; vector<vector<float>> adjacencyMatrix (numVertices, vector<float>(numVertices, 0)); }
Graph(int vertices) : numVertices(vertices), adjacencyMatrix(vertices, vector<float>(vertices, 0)){}
- Phương thức duyệt đồ thị theo chiều sâu dfs:void dfs(int startVertex) const {
5
Trang 8stack<int>stack; stack.push(startVertex); visited[startVertex] = true; while (!stack.empty()){ int currentVertex = stack.top(); cout << currentVertex << " "; stack.pop();
for (int i = 0; i < numVertices; i++){ if (adjacencyMatrix[currentVertex][i] != 0 && !visited[i]){ visited[i] = true;
while(!Q.empty()){ int currentVertex = Q.top().second; Q.pop();
if (currentVertex == endVertex) break;
for (int i = 0; i < numVertices; i++){ if(adjacencyMatrix[currentVertex][i] != 0 && !visited[i]){ visited[currentVertex] = true;
int nextVertex = i; float weight = adjacencyMatrix[currentVertex][i]; if (distance[nextVertex] > distance[currentVertex] + weight ){ distance[nextVertex] = distance[currentVertex] + weight; Q.push({distance[nextVertex], nextVertex});
temp[nextVertex] = currentVertex;
6
Trang 9} } } vector<float> result; int vertex = endVertex; while( vertex != startVertex){ result.push_back(vertex); vertex = temp[vertex]; }
result.push_back(vertex); distance = distance[endVertex]; return result;
}
Thuật toán Dijkstra tìm đường đi ngắn nhất từ một đỉnh xuất phát đến tất cảcác đỉnh còn lại trên đồ thị có trọng số không âm Dưới đây là tóm tắt tư tưởng của thuật toán:
Khởi tạo và thiết lập:
Khởi tạo một vector distance để lưu trữ khoảng cách từ đỉnh xuất phát tới các đỉnh còn lại Ban đầu, khoảng cách tới đỉnh xuất phát được đặt là 0, còn các đỉnh còn lại được đặt là vô cùng lớn
Sử dụng một hàng đợi ưu tiên (priority queue) để lựa chọn đỉnh có khoảng cách ngắn nhất tiếp theo để xét
Lặp qua các đỉnh:
Lặp qua tất cả các đỉnh.Mỗi lần lấy một đỉnh ra từ hàng đợi ưu tiên, kiểm tra tất cả các đỉnh kề với đỉnh đó
Nếu khoảng cách từ đỉnh xuất phát tới đỉnh kề thông qua đỉnh hiện tại ngắnhơn khoảng cách hiện tại của đỉnh kề đó, cập nhật khoảng cách
Trang 10Sau khi kết thúc thuật toán, vector distance sẽ chứa khoảng cách ngắn nhất từ đỉnh xuất phát đến tất cả các đỉnh còn lại trên đồ thị.
Một số phương thức khác (readGraph_file, writeGraph_file, addEdge, addVertex, getWeight)
Xem đầy đủ code tại link sau đây:Link: https://ideone.com/Q7E7hC- Cài đặt class Graph trong hàm main#include<bits/stdc++.h>
#include"SettingGraph.cpp"int main (){
Graph Undirected; string input_file; cout << "Enter file's name to read: "; getline(cin,input_file);
Undirected.readGraph_file(input_file);
string output_file; cout << "Enter file's name to write: "; getline(cin,output_file);
Undirected.writeGraph_file(output_file); cout << Undirected << endl;
cout << "Depth-First Search (DFS) traversal of a graph: "; Undirected.dfs(0);
cout << endl;
int startVertex, endVertex; float distance = 0; cout << "Enter the start vertext: "; cin >> startVertex; cout << "Enter the end vertex: "; cin >> endVertex; vector<float> shortestPath = Undirected.dijkstra(startVertex, endVertex, distance); cout << "The shortest way between 2 vertices: ";
for(int i = shortestPath.size() - 1; i >= 0; i ) { cout << shortestPath[i];
if (i) cout << " > "; }
cout << "\nThe total weight is: " << distance;}
8
Trang 114.Phân tích thời gian chạy của từng phương thức có trong các lớp
~Stack() O(n) Vì duyệt qua danh sách liên kết của các
node và xóa từng node, có độ phức tạp thời gian tuyến tính
void push(const T& x)
O(1) Thao tác thêm một node mới vào đầu
stack, có độ phức tạp cố định.void pop() O(1) Xóa phần tử đầu tiên (node trên cùng),
thao tác này không phụ thuộc vào số lượng phần tử trong stack, có độ phức tạp cố định
T top() O(1) Truy cập dữ liệu của node ‘head’, có
độ phức tạp cố định.bool empty () const O(1) Kiểm tra xem con trỏ ‘head’ có phải
nullptr không, có độ phức tạp cố định
int size() const O(1) Duyệt qua toàn bộ danh sách liên kết
để đếm số node, có độ phức tạp thời gian tuyến tính
Tổng quát, hầu hết các phương thức (push, pop, top, empty) có độ phức tạpthời gian là O(1) Destructor (~Stack()), phương thức size() cũng thực hiện trongthời gian tuyến tính vì nó duyệt qua toàn bộ danh sách liên kết để thực hiện yêu cầu
9
Trang 124.2 Class Graph
Graph(int vertices) O(V^2) Khởi tạo đồ thị với số đỉnh cho
trước, tạo ma trận vuông kích thước VxV
void readGraph_file(string file_name)
O(V^2) Đọc ma trận kề kích thước VxV
void writeGraph_file(string file_name) const
O(V^2) Ghi ma trận kề vào một file
void addEdge(int u, int v, float weight)
O(1) Thao tác này gán trọng số cho 2
đỉnh, có độ phức tạp cố định.void addVertex(int
name)
O(V^2) Cần cập nhật ma trận mới với kích
thước (V+1)x(V+1).float getWeight(int u, int
v) const
O(1) Trả về trọng số giữa 2 đỉnh, có độ
phức tạp cố định.void dfs (int startVertex)
const
O(V^2) Phương thức này thực hiện duyệt
đồ thị theo chiều sâu (DFS) từ đỉnh startVertex và hiển thị các đỉnh đã duyệt Độ phức tạp thời gian trong trường hợp tốt là O(V +E), trong đó V là số đỉnh và E là sốcạnh của đồ thị
Tuy nhiên, trong trường hợp xấu nhất, nếu đồ thị là một đồ thị liên thông, độ phức tạp thời gian có thểlà O(V^2) nếu sử dụng ma trận kề
10
Trang 13để lưu trữ đồ thị.vector<float>
dijkstra(int startVertex, int endVertex, float& distance) const
O((V+E)logV) Trong trường hợp xấu nhất, việc
duyệt qua tất cả các đỉnh và cạnh, cập nhật các khoảng cách và thêm vào hàng đợi ưu tiên
Độ phức tạp có thể giảm xuống O((V+E)logV) với sự sử dụng của hàng đợi ưu tiên, nhưng trong trường hợp xấu nhất khi mọi cạnh được duyệt qua, độ phức tạp có thểlà O(V^2)
Điều quan trọng là độ phức tạp của các thuật toán này phụ thuộc vào cấu trúc đồ thị Trong trường hợp tốt nhất, khi đồ thị đơn giản, độ phức tạp có thể là O(V) hoặc thậm chí là O(1) nếu có rất ít cạnh Tuy nhiên, trong trường hợp xấu nhất, khi đồ thị phức tạp và đầy đủ, độ phức tạp có thể lên đến O(V^2) do việc duyệt qua tất cả các cạnh
Trang 142 Hãy cài đặt cấu trúc dữ liệu cây nhị phân trên có các thao tác push(x) thêm phần tử vào cây và pop() lấy phần tử gốc khỏi cây, top() lấy giá trị của nút gốc sao cho luôn luôn đảm bảo tính chất cây heap
Áp dụng cấu trúc cây này như một hàng đợi ưu tiên thực hiện các việc sau: - Nhập vào 1 dãy số xuất ra dãy giảm dần
- Giải quyết bài toán nối thanh kim loại http://laptrinhonline.club/problem/Tèonoidam Công việc cơ khí thật là mệt nhọc, muốn nối một thanh kim loại độ dài a với một thanh kim loại độ dài b thì kinh phí để thuê nối tốn mất a+b đơn vị tiền tệ Hiện nay Tèo cần nối n thanh kim loại lần lượng có độ dài là a1, a2, an thành một đoạn theo bạn Tèo nên bốtrí thế nào để tổng số tiền phải trả là ít nhất
Input: Dòng đầu chứa số nguyên dương n (1<=n<=105 ) Dòng tiếp theo là n số nguyên dương tương ứng là độ dài các thanh muốn nối (1<=ai<=103 )
Output: Một số nguyên dương là số kinh phí ít nhất phải trả Ví dụ :
Input 38 4 6Output 28 Giải thích: Nếu ta nối thanh 8 với thanh 4 tốn chi phí là 8+4=12 sau khi nốixong còn 2 thanh độ dài 12 và 6 nối lại với nhau tốn 12+6=18 tổng chi phí nối là12+18=30 Nếu ta nối 4 với 6 trước tốn 10 và còn 2 thanh 10 và 8 nối lại với nhau tốn 18 do đó tổng kinh phí ít hơn chỉ còn 28
Gợi ý: Bằng cách nhập vào dãy a là độ dài thanh mỗi bước lấy ra 2 thanh ngắn nhất nối với nhau rồi lại thêm thanh nối được vào
12
Trang 152.Phân tích bài toán
2.1 Xác định các yêu cầu của bài toán, xác định các lớp, các thuộc tính, các phương thức của lớp
2.1.1 Yêu cầu bài toánVấn đề này yêu cầu triển khai một cây heap để giải quyết bài toán nối thanh kim loại với chi phí ít nhất Để giải quyết vấn đề này, chúng ta cần triển khai một cấu trúc dữ liệu Heap có thể thực hiện các thao tác push(), pop(), và top()
Cấu trúc Heap:
push(x): Thêm phần tử vào cây heap và đảm bảo tính chất của cây heap.pop(): Lấy phần tử gốc (phần tử lớn nhất hoặc nhỏ nhất tùy theo loại heap) khỏi cây heap và cập nhật lại cây để duy trì tính chất heap
top(): Trả về giá trị của nút gốc mà không loại bỏ nó
Áp dụng cây heap như một hàng đợi ưu tiên:
Nhập vào dãy số và xuất ra dãy số giảm dần sẽ yêu cầu sắp xếp theo thứ tự ngược lại sau khi đã đưa vào Heap
Sử dụng cấu trúc Heap để giải quyết bài toán nối thanh kim loại với chi phíít nhất
2.1.2 Xác định và mô tả chức năng của các lớp, các thuộc tính, các phương thức của các lớp
- Class Heap
Class Heap được xây dựng trên lớp mẫu template class trong C++, tuân theo nguyên tắc của một max-heap sử dụng một vector để lưu trữ các phần tử, trong đó phần tử lớn nhất luôn ở gốc và mỗi nút cha lớn hơn hoặc bằng nút con của nó
Dưới đây là mô tả về chức năng của các thuộc tính, phương thức của lớp
Thuộc tính vector<T> heap Dùng một vector để lưu dữ liệu
Phương thức riêng void heapify(int i) Cải tạo cây thành cây max-heap
13
Trang 16khôngint size() const Trả về số lượng phần tử của cây
if (right < n && heap[right] > heap[largest]) largest = right;
if(largest != i){ swap(heap[largest],heap[i]); heapify(largest); }
}Thao tác Heapify hỗ trợ duy trì tính chất của heap bắt đầu từ chỉ số i Phương thức này so sánh nút với các nút con của nó và đổi chỗ với nút con lớn hơn nếu cần, sau đó gọi đệ quy heapify trên nút con bị ảnh hưởng
- Các phương thức khác: void push(T x){
heap.push_back(x);
14
Trang 17int roof = (leaf - 1)/2; while(leaf > 0 && heap[leaf] > heap[roof]){ swap(heap[leaf], heap[roof]); leaf = roof;
roof = (leaf - 1)/2; }
} void pop(){ if(heap.empty()) throw out_of_range ("Heap is empty."); heap[0] = heap.back();
heap.pop_back(); heapify(0); }
T top() { if (heap.empty()) throw out_of_range ("Heap is empty."); return heap.front();
} bool empty(){ return heap.empty(); }
int size(){ return heap.size(); }
3.2 Hàm main
Ở hàm main thực hiện các yêu cầu sau:Nhập vào dãy số và xuất ra dãy số giảm dần sẽ yêu cầu sắp xếp theo thứ tự ngược lại sau khi đã đưa vào Heap
Sử dụng cấu trúc Heap để giải quyết bài toán nối thanh kim loại với chi phíít nhất
#include<iostream>#include"SettingHeap.cpp"using namespace std;int main (){ //IN DAY GIAM DAN int n;
cout << "Nhap so phan tu cua day: "; cin >> n;
15