Nguyễn Duy Khánh Linh – 20204839 TRƯỜNG ĐẠI HỌC BÁCH KHOA HÀ NỘI BÁO CÁO KỸ THUẬT LẬP TRÌNH Thực hành sử dụng các cấu dữ liệu cơ bản để giải quyết các bài toán cụ thể BUỔI 4 – TUẦN 41 NGUYỄN DUY KHÁNH[.]
Trang 1TRƯỜNG ĐẠI HỌC BÁCH KHOA HÀ NỘI
BÁO CÁO
KỸ THUẬT LẬP TRÌNH
Thực hành sử dụng các cấu dữ liệu cơ bản để
giải quyết các bài toán cụ thể
Trang 2MỤC LỤC
Bài thực hành 4 – tuần 41: Thực hành sử dụng các cấu trúc dữ liệu cơ bản để giải quyết các bài toán cụ thể
Phần 1 Bài tập thực hành 4
Bài tập 1: Đảo ngược một danh sách liên kết đơn 4
Hãy hoàn thiện các hàm thao tác trên một danh sách liên kết: Thêm một phần tử vào đầu danh sách liên kết; In danh sách; Đảo ngược danh sách liên kết (yêu cầu độ phức tạp thời gian O(N) và chi phí bộ nhớ dùng thêm O(1)) 4
Bài tập 2: Tính diện tích tam giác 6
Một điểm trong không gian 2 chiều được biểu diễn bằng pair Hãy viết hàm double area(Point a, Point b, Point c) tính diện tích tam giác theo tọa độ 3 đỉnh Trong đó, Point là kiểu được định nghĩa sẵn trong trình chấm như sau: using Point = pair; 6
Bài tập 3: Tính tích có hướng của 2 vector 7
Một vector trong không gian 3 chiều được biểu diễn bằng tuple Hãy viết hàm Vector cross_product(Vector a, Vector b) tính tích có hướng của 2 vector Trong đó Vector là kiểu dữ liệu được định nghĩa sẵn trong trình chấm như sau: using Vector = tuple; 7
Bài tập 4: Thao tác với vector 8
Cho hai vector, hãy xóa hết các phần tử chẵn, sắp xếp giảm dần các số trong cả 2 vector và trộn lại thành một vector cũng được sắp xếp giảm dần 8
Bài tập 5: 11
Viết hàm thực hiện thuật toán DFS không sử dụng đệ quy trên đồ thị biểu diễn bằng danh sách kề vector< list > Đồ thị có n đỉnh được đánh số từ 1 đến n Thuật toán DFS xuất phát từ đỉnh 1 Các đỉnh được thăm theo thứ tự ưu tiên từ trái sang phải trong danh sách kề Yêu cầu hàm trả ra thứ tự các đỉnh được thăm (những đỉnh không thể thăm từ đỉnh 1 thì không phải in ra) 11
Bài tập 6: 13
Viết hàm thực hiện thuật toán BFS không sử dụng đệ quy trên đồ thị biểu diễn bằng danh sách kề vector< list > Đồ thị có n đỉnh được đánh số từ 1 đến n Thuật toán BFS xuất phát từ đỉnh 1 Các đỉnh được thăm theo thứ tự ưu tiên từ trái sang phải trong danh sách kề Yêu cầu hàm trả ra thứ tự các đỉnh được thăm (những đỉnh không thể thăm từ đỉnh 1 thì không phải in ra) 13
Bài tập 7: 15
Viết các hàm thực hiện các phép giao và hợp của hai tập hợp được biểu diễn bằng set 15
Bài tập 8: 16 Viết các hàm thực hiện các phép giao và hợp của hai tập hợp mờ được biểu diễn bằng map Trong
đó mỗi phần tử được gán cho một số thực trong đoạn [0 1] biểu thị độ thuộc của phần tử trong tập hợp, với độ thuộc bằng 1 nghĩa là phần tử chắc chắn thuộc vào tập hợp và ngược lại độ thuộc bằng 0 nghĩa là phần tử chắc chắn không thuộc trong tập hợp Phép giao và hợp của 2 tập hợp
Trang 3được thực hiện trên các cặp phần tử bằng nhau của 2 tập hợp, với độ thuộc mới được tính bằng
phép toán min và max của hai độ thuộc 16
Bài tập 9: 19
Cài đặt thuật toán Dijkstra trên đồ thị vô hướng được biểu diễn bằng danh sách kề sử dụng priority_queue Cụ thể, bạn cần cài đặt hàm vector dijkstra(const vector< vector< pair > >&adj) nhận đầu vào là danh sách kề chứa các cặp pair biểu diễn đỉnh kề và trọng số tương ứng của cạnh Đồ thị gồm n đỉnh được đánh số từ 0 tới n-1 Hàm cần trả vector chứa n phần tử lần lượt là khoảng cách đường đi ngắn nhất từ đỉnh 0 tới các đỉnh 0, 1, 2, , n-1 19
Phần 2: Bài tập về nhà 22
Bài tập 10: Search Engine 22
Xây dựng một máy tìm kiếm (search engine) đơn giản 22
Cho N văn bản và Q truy vấn Với mỗi truy vấn, cần trả về văn bản khớp với truy vấn đó nhất 22
Bài tập 11 Bảo vệ lâu đài 28
Bức tường bao quanh một lâu đài nọ được cấu thành từ nn đoạn tường được đánh số từ 1 đến nn Quân giặc lên kế hoạch tấn công lâu đài bằng cách gửi aiai tên giặc đánh vào đoạn tường thứ ii Để bảo vệ lâu đài có tất cả ss lính 28
Do các đoạn tường có chất lượng khác nhau nên khả năng bảo vệ tại các đoạn tường cũng khác nhau Cụ thể tại đoạn tường thứ ii, mỗi lính có thể đẩy lùi tấn công của kiki tên giặc 28
Giả sử đoạn tường thứ ii có xixi lính Khi đó nếu số tên giặc không vượt quá xi×kixi×ki thì không có tên giặc nào lọt vào được qua đoạn tường này Ngược lại sẽ có ai−xi×kiai−xi×ki tên giặc lọt vào lâu đài qua đoạn tường này 28
Yêu cầu hãy viết chương trình phân bố lính đứng ở các đoạn tường sao cho tổng số lính là ss và tổng số lượng tên giặc lọt vào lâu đài là nhỏ nhất 28
Bài tập 12: Lược đồ 31
Cho một lược đồ gồm nn cột chữ nhật liên tiếp nhau có chiều rộng bằng 1 và chiều cao lần lượt là các số nguyên không âm h1,h2,…,hnh1,h2,…,hn Hãy xác định hình chữ nhật có diện tích lớn nhất có thể tạo thành từ các cột liên tiếp 31
Dữ liệu vào: Dòng thứ nhất chứa số nguyên dương nn (1≤n≤1061≤n≤106) Dòng thứ hai chứa nn số nguyên không âm h1,h2,…,hnh1,h2,…,hn cách nhau bởi dấu cách (0≤hi≤109) 31
Kết quả: In ra số nguyên duy nhất là diện tích hình chữ nhật lớn nhất có thể tạo thành từ các cột liên tiếp của lược đồ 32
Bài tập 13: Đếm xâu con 34
Cho một xâu nhị phân độ dài nn Hãy viết chương trình đếm số lượng xâu con chứa số ký tự 0 và số ký tự 1 bằng nhau 34
Dữ liệu vào: Một dòng duy nhất chứa một xâu nhị phân độ dài nn (1≤n≤106) 34
Kết quả: Ghi ra một số nguyên duy nhất là số lượng xâu con có số ký tự 0 và số ký tự 1 bằng nhau 34
Trang 5Bài thực hành 4: Thực hành sử dụng các cấu trúc dữ liệu cơ bản để giải quyết các bài toán cụ thể
Phần 1 Bài tập thực hành
Bài tập 1: Đảo ngược một danh sách liên kết đơn
Hãy hoàn thiện các hàm thao tác trên một danh sách liên kết: Thêm một phần tử vào đầu danh sách liên kết; In danh sách; Đảo ngược danh sách liên kết (yêu cầu độ phức tạp thời gian O(N) và chi phí bộ nhớ dùng thêm O(1))
// đẩy một phần tử mới lên đầu danh sách
Node* prepend(Node* head, int data) {
Node* tmp = new Node(data);
tmp->next = head;
return tmp;
}
// in ra noi dung tren mot dong
void print(Node* head) {
Node *tmp = head;
while(tmp != NULL){
Trang 6//trả lại phần tử đứng đầu mới của danh sách đảo ngược
Node* reverse(Node* head) {
Node* prev = NULL;
Node* next = NULL;
Node* head = NULL;
for (int i = 0; i < n; ++i){
Trang 7Bài tập 2: Tính diện tích tam giác
Một điểm trong không gian 2 chiều được biểu diễn bằng pair Hãy viết hàm double area(Point a, Point b, Point c) tính diện tích tam giác theo tọa độ 3 đỉnh Trong đó, Point là kiểu được định nghĩa sẵn trong trình chấm như sau: using Point = pair;
Bài làm #include <iostream>
using Point = pair<double, double>;
//hàm trả về diện tích tam giác
double area(Point a, Point b, Point c) {
double ab, bc, ca; // canh cua tam gia abc
//tính độ dài cạnh giữa các điểm
ab = sqrt(pow((a.first-b.first), 2) + pow((a.second-b.second), 2));
Trang 8cout << "HO VA TEN: Nguyen Duy Khanh Linh\nMSSV: 20204839\n";
cout << setprecision(2) << fixed;
cout << area({1, 2}, {2.5, 10}, {15, -5.25}) << endl;
return 0;
}
Bài tập 3: Tính tích có hướng của 2 vector
Một vector trong không gian 3 chiều được biểu diễn bằng tuple Hãy viết hàm Vector
cross_product(Vector a, Vector b) tính tích có hướng của 2 vector Trong đó Vector là kiểu dữ liệu được định nghĩa sẵn trong trình chấm như sau: using Vector = tuple;
using Vector = tuple<double, double, double>;
//Hàm trả về tích có hướng của hai vector
Vector cross_product(Vector a, Vector b) {
Trang 9//áp dụng công thức tính tích vô hướng
double x = get<1>(a) * get<2>(b) - get<1>(b) * get<2>(a);
double y = get<2>(a) * get<0>(b) - get<2>(b) * get<0>(a);
double z = get<0>(a) * get<1>(b) - get<0>(b) * get<1>(a);
return Vector (x, y, z);
}
int main() {
cout << "HO VA TEN: Nguyen Duy Khanh Linh\nMSSV: 20204839\n";
cout << setprecision(2) << fixed;
Bài tập 4: Thao tác với vector
Cho hai vector, hãy xóa hết các phần tử chẵn, sắp xếp giảm dần các số trong cả 2 vector và trộn lại thành một vector cũng được sắp xếp giảm dần.
Trang 10//In ra vector
void print_vector(const vector<int> &a) {
for (int v : a) cout << v << ' ';
//hàm biến đổi vector chỉ còn phần tử lẻ
void delete_even(vector<int> &a) {
//sử dụng lệnh sort() để sắp xếp các phần tử trong vector
void sort_decrease(vector<int> &a) {
sort(a.begin(), a.end(), reduce);
}
//hàm trả về vector gồm các phần tử của vector a, b và đã được sắp xếp
vector<int> merge_vectors(const vector<int> &a, const vector<int> &b) {
Trang 12cout << "Decreasingly sorted c: ";
Trang 13//nếu đỉnh tmp chưa được thăm thì đánh dấu đã thăm và in ra màn hình
Trang 17void print_set(const std::set<T> &a) {
for (const T &x : a) {
std::cout << "Union: "; print_set(c);
std::cout << "Intersection: "; print_set(d);
Trang 18phần tử chắc chắn không thuộc trong tập hợp Phép giao và hợp của 2 tập hợp được thực hiện trên các cặp phần tử bằng nhau của 2 tập hợp, với độ thuộc mới được tính bằng phép toán min và max của hai
//nếu tồn tại cặp phần tử bằng nhau, độ thuộc bằng max của hai độ thuộc
if(x.first == y.first && tmp == 0) {
c.insert({x.first, max(x.second, y.second)});
Trang 19for (const auto &x : a) {
std::cout << "(" << x.first << ", " << x.second << ") ";
}
cout << "}";
std::cout << std::endl;
}
Trang 20std::map<int, double> c = fuzzy_set_union(a, b);
std::map<int, double> d = fuzzy_set_intersection(a, b);
std::cout << "Union: "; print_fuzzy_set(c);
std::cout << "Intersection: "; print_fuzzy_set(d);
}
Bài tập 9:
Cài đặt thuật toán Dijkstra trên đồ thị vô hướng được biểu diễn bằng danh sách kề sử dụng
priority_queue Cụ thể, bạn cần cài đặt hàm vector dijkstra(const vector< vector< pair > >&adj) nhận đầu vào là danh sách kề chứa các cặp pair biểu diễn đỉnh kề và trọng số tương ứng của cạnh Đồ thị gồm n đỉnh được đánh số từ 0 tới n-1 Hàm cần trả vector chứa n phần tử lần lượt là khoảng cách đường đi ngắn nhất từ đỉnh 0 tới các đỉnh 0, 1, 2, , n-1
Trang 21//xây dựng thứ tự ưu tiên cho hàng đợi
struct compare{
bool operator() (pair<int, int> a, pair<int, int> b){
return a.second > b.second;
}
};
//hàm trả về vector chứa khoảng các đường đi ngắn nhất từ đỉnh 0 tới các đỉnh còn lại
vector<int> dijkstra(const vector< vector< pair<int, int> > >&adj) {
//khởi tạo khoảng cách từ đỉnh 0 tới các đỉnh còn lại
vector<int> dist(adj.size());
int dist_size = dist.size();
for(int i=0; i<dist_size; i++){
dist[i] = INT_MAX;
}
dist[0] = 0; //khoảng cách từ 0 tới 0 bẳng 0
//khởi tạo giá trị hàng đợi ưu tiên
priority_queue<pair<int, int>, vector< pair<int, int> >, compare> q;
for(int i=0; i<dist_size; i++){
q.push({i, dist[i]});
}
while(!q.empty()){
pair<int, int> u_pair = q.top(); q.pop();
int u = u_pair.first; //lấy đỉnh ưu tiên nhất trong q-khoảng cách tới đỉnh 0 nhỏ nhất
//tính lại khoảng cách nhỏ nhất từ đỉnh 0 tới các đỉnh kề của đỉnh u
for(auto v_pair : adj[u]){
int v = v_pair.first;
int weight = v_pair.second;
if(dist[v] > dist[u] + weight){
Trang 22dist[v] = dist[u] + weight;
vector< vector< pair<int, int> > > adj(n);
auto add_edge = [&adj] (int u, int v, int w) {
Trang 23add_edge(7, 8, 7);
vector<int> distance = dijkstra(adj);
for (int i = 0; i < distance.size(); ++i) {
cout << "distance " << 0 << "->" << i << " = " << distance[i] << endl;
}
return 0;
}
Phần 2: Bài tập về nhà
Bài tập 10: Search Engine
Xây dựng một máy tìm kiếm (search engine) đơn giản
Cho N văn bản và Q truy vấn Với mỗi truy vấn, cần trả về văn bản khớp với truy vấn đó nhất
Sử dụng phương pháp tính điểm TF-IDF:
f(t,d)f(t,d) là số lần xuất hiện của từ tt trong văn bản dd
maxf(d)maxf(d) là giá trị lớn nhất của f(t,d)f(t,d) với mọi tt
Trang 24• Dòng thứ i trong N dòng tiếp theo thể hiện văn bản i, mỗi dòng là một dãy các từ ngăn cách nhau bởi dấu phẩy
• Dòng tiếp theo chứa số Q
• Dòng thứ i trong Q dòng tiếp theo thể hiện truy vấn thứ i, mỗi dòng là một dãy các từ ngăn cách nhau bởi dấu phẩy
Output: Gồm Q dòng, dòng thứ i là chỉ số của văn bản khớp với truy vấn thứ ii nhất Nếu có nhiều văn
bản có điểm số bằng nhau, in ra văn bản có chỉ số nhỏ nhất
Giới hạn:
N≤1000N≤1000
Q≤1000Q≤1000
Số từ trong mỗi văn bản không quá 10001000
Số từ trong mỗi truy vấn không quá 1010
Độ dài mỗi từ không quá 1010
Bài làm
#include<bits/stdc++.h>
using namespace std;
int n, q;
vector< vector<string> > vector_train;
vector< vector<string> > vector_test;
vector<int> f_max; // tần xuất suất hiện của từ xuất hiện nhiều nhất trong văn bản i
map<string, int> df; //số văn bản từ word có xuất hiện
map<pair<string, int>, int> fe; // trong văn bản i, từ word xuất hiện bao nhiêu lần <word, i> = int // split string
vector<string> split_string(string str){
vector<string> vt;
while (!str.empty()){
string tmp = str.substr(0, str.find(","));
int pos = tmp.find(" ");
if(pos > tmp.size()) vt.push_back(tmp);
else {
while(pos <= tmp.size()){
Trang 25if (str.find(",") > str.size()) break;
else str.erase(0, str.find(",") + 1);
Trang 26map<string, int>::iterator ite = m.find(word_tmp);
if(ite == m.end()){ //nếu từ này xuất hiện trong từ điển mini
m.insert({word_tmp, 1});
} else ite->second += 1; //nếu không thì
max_f = max(m[word_tmp], max_f);
Trang 27int frequence_word_int_document_i(string word, int i){
if(fe.find({word, i}) != fe.end()){ //nếu đã có trong kho thì lấy ra để trả về luôn
return fe[{word, i}];
}
int index = 0;
vector<string> str_tmp = vector_train[i];
for(string v : str_tmp if(word == v) index++;
fe.insert({{word, i}, index});
return index;
}
//tính số văn bản có xuất hiện từ word
int count_document_contain_word(string word){
if(df.find(word) != df.end()){ //nếu đã có trong kho thì lấy ra để trả về luôn
Trang 28vector<string> list_word_train_doc = vector_train[i];
double score = 0;
for(string word : list_word){
if(find(list_word_train_doc.begin(), list_word_train_doc.end(), word) == list_word_train_doc.end()){ //nếu từ này không xuất hiện trong văn bản
continue;
} else {
int ftd = frequence_word_int_document_i(word, i);
int dft = count_document_contain_word(word);
int maxfd = f_max[i];
double tf_word = 0.5 + 0.5 * ((double) ftd / maxfd);
double idf_word = log2((double) n / dft);
score += tf_word * idf_word;
Trang 29for(int i=0; i<q; i++) cout << search_engine(vector_test[i]) << endl;
return 0;
}
Bài tập 11 Bảo vệ lâu đài
Bức tường bao quanh một lâu đài nọ được cấu thành từ nn đoạn tường được đánh số từ 1 đến nn Quân giặc lên kế hoạch tấn công lâu đài bằng cách gửi aiai tên giặc đánh vào đoạn tường thứ ii Để bảo
Yêu cầu hãy viết chương trình phân bố lính đứng ở các đoạn tường sao cho tổng số lính là ss và tổng số lượng tên giặc lọt vào lâu đài là nhỏ nhất
Bài làm