Bila kita memetakan graph ke dalam memori, pertanyaan pertama yang muncul: bagaimana cara cepat menjawab “apakah ada edge dari u ke v?” Adjacency matrix menjawab pertanyaan ini dalam O(1) — sebuah matriks V×V di mana sel (u,v) bernilai 1 (ada edge) atau 0 (tidak ada). Keunggulan ini dibayar dengan memori O(V²), yang menjadi masalah saat graph memiliki jutaan vertex tetapi sedikit edge. Pada Chapter ini kita akan mengimplementasikan adjacency matrix untuk directed, undirected, maupun weighted graph, lalu menganalisis kapan pendekatan ini layak dipakai.
Struktur Adjacency Matrix #
Adjacency matrix A untuk graph G = (V, E) didefinisikan sebagai matriks V×V dengan:
A[u][v] = 1bila terdapat edge(u, v)pada directed graph, atau{u, v}pada undirected graph.A[u][v] = 0bila tidak ada edge.
Untuk weighted graph, A[u][v] menyimpan bobot edge; tidak ada edge dilambangkan dengan ∞ (misal 1e9 atau 1e15).
Beberapa property langsung dari representasi ini:
- Undirected graph: matriks simetris —
A[u][v] == A[v][u]. - Directed graph: matriks tidak perlu simetris.
- Self-loop:
A[u][u] = 1(atau bobot loop pada weighted). - In-degree
v: jumlah kolomvyang bernilai 1. - Out-degree
u: jumlah barisuyang bernilai 1.
Implementasi Dasar #
Kode: Adjacency Matrix untuk Directed Graph #
#include <iostream>
#include <vector>
class AdjMatrix {
public:
explicit AdjMatrix(int n) : n_(n), mat_(n, std::vector<int>(n, 0)) {}
void add_edge(int u, int v) { mat_[u][v] = 1; }
bool has_edge(int u, int v) const { return mat_[u][v] == 1; }
int out_degree(int u) const {
int deg = 0;
for (int v = 0; v < n_; ++v) if (mat_[u][v]) deg++;
return deg;
}
int in_degree(int v) const {
int deg = 0;
for (int u = 0; u < n_; ++u) if (mat_[u][v]) deg++;
return deg;
}
int v_count() const { return n_; }
void print() const {
std::cout << " ";
for (int j = 0; j < n_; ++j) std::cout << j << ' ';
std::cout << '\n';
for (int i = 0; i < n_; ++i) {
std::cout << i << " : ";
for (int j = 0; j < n_; ++j) std::cout << mat_[i][j] << ' ';
std::cout << '\n';
}
}
private:
int n_;
std::vector<std::vector<int>> mat_;
};
int main() {
// Directed graph: 0->1, 0->2, 1->2, 2->0
AdjMatrix g(3);
g.add_edge(0, 1);
g.add_edge(0, 2);
g.add_edge(1, 2);
g.add_edge(2, 0);
std::cout << "Adjacency matrix (directed):\n";
g.print();
std::cout << "\nEdge check:\n";
std::cout << " has_edge(0,1) = " << g.has_edge(0, 1) << '\n';
std::cout << " has_edge(1,0) = " << g.has_edge(1, 0) << '\n';
std::cout << " has_edge(2,0) = " << g.has_edge(2, 0) << '\n';
std::cout << "\nDegree:\n";
for (int u = 0; u < g.v_count(); ++u) {
std::cout << " vertex " << u
<< " | in=" << g.in_degree(u)
<< " out=" << g.out_degree(u) << '\n';
}
return 0;
}Output yang Dihasilkan:
Adjacency matrix (directed):
0 1 2
0 : 0 1 1
1 : 0 0 1
2 : 1 0 0
Edge check:
has_edge(0,1) = 1
has_edge(1,0) = 0
has_edge(2,0) = 1
Degree:
vertex 0 | in=1 out=2
vertex 1 | in=1 out=1
vertex 2 | in=2 out=1- Baris
mat_(n, std::vector<int>(n, 0)): inisialisasi matriksn×nberisi 0. Inisialisasi ini membutuhkan O(V²) waktu dan memori. - Baris
void add_edge(int u, int v): untuk directed graph, hanya setmat_[u][v] = 1. Untuk undirected, tambahkanmat_[v][u] = 1juga. - Baris
bool has_edge(int u, int v): akses langsung sel matriks — O(1). Inilah keunggulan utama adjacency matrix. - Baris
int out_degree(int u): loop sepanjang barisu— O(V). Pada adjacency list ini bisa O(1) bila kita simpan degree terpisah. - Baris
int in_degree(int v): loop sepanjang kolomv— O(V). Pada adjacency list kita perlu loop seluruh adjacency list untuk menghitung in-degree, O(V+E). - Hasil
vertex 2 | in=2 out=1: ada 2 edge masuk ke vertex 2 (dari 0 dan 1) dan 1 edge keluar (ke 0).
Weighted Adjacency Matrix #
Untuk weighted graph, ganti tipe elemen dari int (0/1) menjadi long long yang menyimpan bobot. Konvensi:
mat[u][v] = w— edge dengan bobotw.mat[u][v] = INF— tidak ada edge (gunakanINFbesar agar penjumlahan tetap aman, misal1e15).mat[u][u] = 0— jarak ke diri sendiri = 0.
Kode: Weighted Adjacency Matrix #
#include <iostream>
#include <vector>
class WeightedMatrix {
public:
static constexpr long long INF = 1000000000000000LL; // 1e15
explicit WeightedMatrix(int n)
: n_(n), mat_(n, std::vector<long long>(n, INF)) {
for (int i = 0; i < n; ++i) mat_[i][i] = 0;
}
void add_edge(int u, int v, long long w) { mat_[u][v] = w; }
long long weight(int u, int v) const { return mat_[u][v]; }
bool has_edge(int u, int v) const { return u != v && mat_[u][v] != INF; }
void print() const {
std::cout << " ";
for (int j = 0; j < n_; ++j) std::cout << j << " ";
std::cout << '\n';
for (int i = 0; i < n_; ++i) {
std::cout << " " << i << " : ";
for (int j = 0; j < n_; ++j) {
if (mat_[i][j] == INF) std::cout << "INF ";
else if (mat_[i][j] < 10) std::cout << mat_[i][j] << " ";
else std::cout << mat_[i][j] << " ";
}
std::cout << '\n';
}
}
private:
int n_;
std::vector<std::vector<long long>> mat_;
};
int main() {
WeightedMatrix g(4);
g.add_edge(0, 1, 5);
g.add_edge(1, 2, 2);
g.add_edge(0, 2, 9);
g.add_edge(2, 3, 7);
g.add_edge(1, 3, 12);
std::cout << "Weighted adjacency matrix:\n";
g.print();
auto print_w = [&](int u, int v) {
long long w = g.weight(u, v);
if (w == WeightedMatrix::INF) std::cout << "INF";
else std::cout << w;
};
std::cout << "\nEdge check:\n";
std::cout << " weight(0,1) = "; print_w(0, 1); std::cout << '\n';
std::cout << " weight(1,0) = "; print_w(1, 0); std::cout << " (INF = no edge)\n";
std::cout << " weight(0,0) = "; print_w(0, 0); std::cout << " (self = 0)\n";
return 0;
}Output yang Dihasilkan:
Weighted adjacency matrix:
0 1 2 3
0 : 0 5 9 INF
1 : INF 0 2 12
2 : INF INF 0 7
3 : INF INF INF 0
Edge check:
weight(0,1) = 5
weight(1,0) = INF (INF = no edge)
weight(0,0) = 0 (self = 0)- Baris
static constexpr long long INF = 1e15;: konstantaINFdibuat static member agar dapat diakses dari luar kelas sebagaiWeightedMatrix::INF. Nilai1e15cukup besar sehinggaINF + 1e9tidak overflowlong long(maksimum ~9.2×10¹⁸). - Baris
mat_(n, std::vector<long long>(n, INF)): inisialisasi seluruh sel keINF, lalu set diagonal ke 0. Pada weighted graph, jarak ke diri sendiri selalu 0 (bukan 1, bukan INF). - Baris
bool has_edge(int u, int v) const { return u != v && mat_[u][v] != INF; }: definisi “ada edge” mengecualikan diagonal —mat[u][u] = 0bukan edge, melainkan jarak ke diri sendiri. - Baris
if (mat_[i][j] == INF) std::cout << "INF ";: cetak literalINFagar matriks mudah dibaca manusia. Tanpa ini, angka1e15akan tampil sebagai1000000000000000yang memenuhi layar. - Lambda
print_w: helper untuk mencetak weight atauINFbergantian — menghindari duplikasi logika di tiga baris berikutnya. - Hasil
weight(1,0) = INF: tidak ada edge dari 1 ke 0; matriks tidak simetris karena graph directed.
Kompleksitas dan Kapan Dipakai #
Tabel berikut merangkum operasi-operasi penting pada adjacency matrix:
| Operasi | Kompleksitas | Catatan |
|---|---|---|
| Inisialisasi | O(V²) | alokasi V×V sel |
Cek edge (u,v) | O(1) | akses sel tunggal |
| Tambah edge | O(1) | satu assignment |
| Hapus edge | O(1) | satu assignment |
Iterasi neighbor u | O(V) | scan sepanjang baris |
Out-degree u | O(V) | scan baris |
In-degree v | O(V) | scan kolom |
| Memori | O(V²) | terlepas dari jumlah edge |
Batasan Praktis #
Memori O(V²) menjadi penghalang utama:
- V = 1000 → 1 juta sel → ~4 MB (int) atau ~8 MB (long long). Masih oke.
- V = 10⁴ → 100 juta sel → ~800 MB. Hampir tidak feasible untuk kompetisi.
- V = 10⁵ → 10 milyar sel → ~80 GB. Tidak mungkin.
Sebagai pedoman praktis: adjacency matrix aman dipakai hingga V ≤ 2000. Di atas itu, sebaiknya beralih ke adjacency list.
Ringkasan #
- Adjacency matrix adalah matriks
V×VdenganA[u][v] = 1(ada edge) atau0(tidak ada). - Untuk weighted graph,
A[u][v]menyimpan bobot; tidak ada edge =INF;A[u][u] = 0. - Operasi cek edge O(1) — keunggulan utama adjacency matrix.
- Operasi iterasi neighbor O(V) — kekurangan utama; adjacency list O(degree).
- Memori O(V²) — cocok untuk dense graph kecil-sedang (
V ≤ 2000), tidak feasible untuk sparse graph besar. - Pilihan natural untuk algoritma Floyd-Warshall, transitive closure, dan matrix-based graph algorithms.