Vector là mảng động của thư viện STL, giúp lưu trữ dữ liệu linh hoạt, tự thay đổi kích thước và rất hay dùng trong bài toán mảng, đồ thị, quy hoạch động.
1. Linh hoạt
Không cần biết trước chính xác số phần tử. Có thể thêm/xóa trong lúc chạy chương trình.
2. Truy cập nhanh
Truy cập phần tử bằng chỉ số như mảng: v[i], độ phức tạp O(1).
3. Dễ dùng với STL
Kết hợp tốt với sort, reverse, lower_bound, đồ thị danh sách kề.
Đặt vấn đề: vì sao cần vector?
Giả sử một bài toán có nhiều hàng dữ liệu, mỗi hàng có số phần tử khác nhau. Nếu dùng mảng tĩnh quá nhỏ thì thiếu ô nhớ; nếu khai báo quá lớn thì dễ tràn bộ nhớ.
Không nên: khai báo mảng quá lớn như char a[1000001][1000001], vì số ô nhớ có thể lên tới 1012.
Nên dùng:vector hoặc vector<vector<T>> để cấp phát đúng theo dữ liệu thực tế.
Khái niệm
Vector là mảng động trong C++. Các phần tử của vector nằm liên tiếp trong bộ nhớ, có thể truy cập bằng chỉ số và có thể tự mở rộng khi thêm phần tử mới.
Tiêu chí
Mảng tĩnh
Vector
Kích thước
Cố định sau khi khai báo
Có thể thay đổi khi chạy
Thêm phần tử
Khó, phải tự quản lý
Dùng push_back(x)
Xóa phần tử cuối
Phải tự giảm biến đếm
Dùng pop_back()
Truy cập
a[i]
v[i] hoặc v.at(i)
Khai báo và khởi tạo vector
Ví dụ khai báo vector 1 chiều
#include <bits/stdc++.h>
using namespace std;
int main() {
vector<int> v1; // vector rỗng
vector<int> v2 = {10, 2, 10, 5};
vector<int> v3(5); // 5 phần tử, mặc định bằng 0
vector<int> v4(5, 7); // 5 phần tử, mỗi phần tử bằng 7
return 0;
}
Cú pháp tổng quát:vector<kiểu_dữ_liệu> tên_vector;
Nhập và xuất vector 1 chiều
Ta có thể nhập từng giá trị rồi dùng push_back(x) để đưa vào cuối vector.
Nhập n số vào vector và in ra
#include <bits/stdc++.h>
using namespace std;
int main() {
vector<int> a;
int n;
cin >> n;
for (int i = 1; i <= n; i++) {
int x;
cin >> x;
a.push_back(x); // thêm x vào cuối vector
}
for (int i = 0; i < n; i++) {
cout << a[i] << " ";
}
return 0;
}
Lưu ý: vector đánh số từ 0. Phần tử đầu tiên là a[0], phần tử cuối là a[a.size()-1].
Duyệt vector bằng iterator
begin() trỏ tới phần tử đầu tiên, end() trỏ tới vị trí sau phần tử cuối.
Duyệt bằng iterator
#include <bits/stdc++.h>
using namespace std;
int main() {
vector<int> a = {4, 7, 2, 9};
for (auto it = a.begin(); it != a.end(); ++it) {
cout << *it << " ";
}
return 0;
}
Cách ngắn hơn: dùng vòng lặp for-each for (int x : a).
Duyệt bằng for-each
for (int x : a) {
cout << x << " ";
}
Vector 2 chiều
Vector 2 chiều có thể hiểu là vector mà mỗi phần tử của nó lại là một vector.
Khai báo vector 2 chiều n × m
#include <bits/stdc++.h>
using namespace std;
int main() {
int n = 4, m = 5;
// n hàng, mỗi hàng có m phần tử int, ban đầu bằng 0
vector<vector<int>> vec(n, vector<int>(m));
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
vec[i][j] = i + j + 1;
}
}
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
cout << vec[i][j] << " ";
}
cout << '\n';
}
return 0;
}
Vector 2 chiều có số phần tử mỗi hàng khác nhau
Nhập n hàng, mỗi hàng m phần tử
#include <bits/stdc++.h>
using namespace std;
int main() {
int n, m;
cin >> n >> m;
vector<vector<int>> a(n);
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
int x;
cin >> x;
a[i].push_back(x);
}
}
for (int i = 0; i < n; i++) {
for (int x : a[i]) {
cout << x << " ";
}
cout << '\n';
}
return 0;
}
Với đồ thị có n đỉnh, ta thường dùng vector<int> adj[MAXN] hoặc vector<vector<int>> adj(n+1) để lưu danh sách các đỉnh kề.
Lưu đồ thị bằng vector
#include <bits/stdc++.h>
using namespace std;
int main() {
int n, m;
cin >> n >> m;
vector<vector<int>> adj(n + 1);
for (int i = 1; i <= m; i++) {
int u, v;
cin >> u >> v;
adj[u].push_back(v); // đồ thị có hướng u -> v
}
for (int u = 1; u <= n; u++) {
cout << "Ke cua " << u << ": ";
for (int v : adj[u]) cout << v << " ";
cout << '\n';
}
return 0;
}
Lỗi thường gặp khi dùng vector
1
Truy cập khi vector chưa có phần tử. Ví dụ: vector<int> v; v[0] = 5; là sai vì vector đang rỗng.
2
Nhầm chỉ số từ 1 và từ 0. Vector đánh số từ 0, nên vòng lặp thường là for (int i=0; i<v.size(); i++).
3
Dùng insert/erase quá nhiều ở giữa vector. Các thao tác này có thể O(n), nếu làm quá nhiều sẽ chậm.
4
So sánh int với size_t. v.size() trả về kiểu không âm. Khi cần, có thể ép kiểu: int n = (int)v.size();.
Bài tập luyện tập vector
Các bài dưới đây phù hợp để học sinh luyện cách đọc dữ liệu không cố định, xử lý dãy bằng vector và lưu đồ thị bằng danh sách kề.
📝 Câu 1. DEMSO.CPP
Cho một dãy gồm số lượng số lẻ. Hãy tìm số nằm ở chính giữa dãy.
Dữ liệu: File DEMSO.INP gồm nhiều dòng, mỗi dòng là một số trong đoạn [0, 105]. Số lượng số không quá 106 và luôn là số lẻ.
Kết quả: Ghi ra DEMSO.OUT số nằm ở chính giữa.
DEMSO.INP
1
2
8
6
9
DEMSO.OUT
8
Gợi ý: Đọc đến hết file bằng while(cin >> x), đưa vào vector rồi in a[a.size()/2].
📝 Câu 2. KHACNHAU.CPP
Cho một dãy số, mỗi số nằm trong đoạn từ 0 đến 109. Hãy đếm có bao nhiêu số khác nhau trong dãy.
Dữ liệu: File KHACNHAU.INP gồm một dòng là một dãy số, không quá 106 số.
Kết quả: Ghi ra KHACNHAU.OUT số lượng giá trị khác nhau.
KHACNHAU.INP
1 5 3 4 2 4 5 7 8
KHACNHAU.OUT
7
Gợi ý: Lưu vào vector, sắp xếp, sau đó dùng unique hoặc tự đếm khi giá trị thay đổi.
📝 Câu 3. CANHNHAU.CPP
Cho danh sách các con đường hai chiều giữa các ngôi nhà. Hãy khôi phục danh sách những ngôi nhà kề với từng ngôi nhà.
Dữ liệu: Dòng đầu gồm N, M. M dòng tiếp theo gồm u, v là một con đường hai chiều nối u và v.
Kết quả: In N dòng. Dòng i gồm các ngôi nhà kề với i, kết thúc bằng số 0.
CANHNHAU.INP
5 6
3 5
5 2
3 4
4 1
1 2
5 1
CANHNHAU.OUT
4 2 5 0
5 1 0
5 4 0
3 1 0
3 2 1 0
Gợi ý: Dùng vector<vector<int>> adj(n+1). Với mỗi cạnh u-v, thêm v vào adj[u] và u vào adj[v].
📝 Câu 4. LIENTHONG.CPP
Một đất nước có N khu dân cư và M con đường một chiều. Cần chia các khu dân cư thành số nhóm nhỏ nhất sao cho trong mỗi nhóm, từ một khu bất kỳ có thể đi đến các khu còn lại trong nhóm.
Dữ liệu: Dòng đầu gồm N, M. M dòng sau gồm u, v là đường một chiều từ u đến v. N ≤ 100, M ≤ 200.
Kết quả: Ghi ra số K nhỏ nhất.
LIENTHONG.INP
6 7
1 3
2 1
5 3
3 2
4 6
6 5
6 4
LIENTHONG.OUT
3
Gợi ý: Đây là bài toán đếm số thành phần liên thông mạnh. Có thể dùng Floyd-Warshall với N nhỏ, hoặc Tarjan/Kosaraju.
📝 Câu 5. SEQDIV.CPP
Cho hai số nguyên dương n và k. Hãy đếm số lượng dãy a1, a2, ..., an sao cho mỗi ai nằm trong đoạn [1, k] và với hai phần tử kề nhau, một số chia hết cho số còn lại nhưng không được xảy ra đồng thời hai chiều.
Dữ liệu: File SEQDIV.INP gồm hai số n và k.
Kết quả: Ghi ra SEQDIV.OUT số lượng dãy thỏa mãn, lấy phần dư cho 109+7.
SEQDIV.INP
4 3
SEQDIV.OUT
8
Gợi ý: Dùng quy hoạch động. Gọi dp[i][x] là số dãy độ dài i kết thúc bằng x. Chuyển từ y sang x nếu x và y thỏa điều kiện chia hết một chiều.