Concept & Theory
1. Theory
1. Theoretical Background
1.1 Definition of GraphOOP
AObject-Oriented GraphProgramming (OOP) is a programming paradigm that uses the concept of objects.
An object combines data structure(attributes) consistingand of:behavior (methods) into a single unit.
The four main pillars of OOP:
Vertex (node)Encapsulation →representsHidingandataobject.implementation using restricted access through setter/getter.EdgeAbstraction →representsHidingtheimplementationrelationshipdetailsbetweenwhileobjects.exposing
Graphsrelevant can be:
DirectedorUndirected.interfaces.WeightedInheritanceor→ Derived classes can inherit attributes and methods from a base class.UnweightedPolymorphism → The ability of objects to take many forms (e.g., method overriding).
1.2 Graph RepresentationEncapsulation
ThereEncapsulation areis two main representationsone of graphsthe four fundamental principles of Object-Oriented Programming (OOP).
It refers to the process of wrapping attributes (data members) and behaviors (methods) inside a class and restricting direct access to the data from outside.
Instead of accessing attributes directly, developers use getter and setter functions.
This provides better data security, controlled access, and modularity in programming:software design.
Adjacency MatrixA 2D matrix [V][V], where V is the number of vertices. Each element indicates whether an edge exists between two nodes (commonly 1 for an edge, 0 for no edge).Suitable fordense graphs.Edge lookup is efficient withO(1)time complexity.
class Device { private: int
graph[5][5]batteryLevel; // accessible only via setter/getter public: void setBattery(int b) { batteryLevel = b; } int getBattery() {{0,return1,batteryLevel;0, 0, 1}, {1, 0, 1, 1, 0}, {0, 1, 0, 1, 0}, {0, 1, 1, 0, 1}, {1, 0, 0, 1, 0}} };Access Specifiers in Encapsulation
AdjacencyC++List
providesEachthreevertexmainstoresaccess specifiers that control how members of alistclassofcanitsbeadjacent vertices (neighbors).accessed:Memory-efficientPublicforMemberssparse graphs,declared asonlypublicexistingcanedgesbeareaccessedstored.from Edge lookup may take up toO(V)anywhere in theworstprogramcase.(inside
such as getters, setters, or functions that need to be available to other classes.vector<int>outsideadj[5];theadj[0]class).push_back(1);adj[0].push_back(4);Commonlyadj[1].push_back(0);usedadj[1].push_back(2);foradj[1].push_back(3);interfaces,
1.3 Graph Traversal
Graph traversal can be performed in two main ways:
Breadth First Search (BFS)Uses a queue.Explores nodes level by level (broad exploration).Effective in unweighted graphs to find the shortest path from one node to another.
voidclassBFS(int start, vector<int> adj[], int V)Phone {vector<bool> visited(V, false); queue<int> q; visited[start] = true; q.push(start); while (!q.empty()) { int u = q.front(); q.pop(); cout << u << " "; for (int v : adj[u]) { if (!visited[v]) { visited[v] = true; q.push(v); } } } }Depth First Search (DFS)Uses recursion or a stack.Explores as deep as possible before backtracking.Useful for detecting cycles, performing topological sort, or finding connected components.
void DFSUtil(int u, vector<int> adj[], vector<bool>& visited) { visited[u] = true; cout << u << " "; for (int v : adj[u]) { if (!visited[v]) { DFSUtil(v, adj, visited); } } }public: voidDFS(int start, vector<int> adj[], int V) { vector<bool> visited(V, false); DFSUtil(start, adj, visited); }
1.4 Shortest Path Algorithm: Dijkstra
Finds the shortest path from a source node to all other nodes in apositively weighted graph.Uses a priority queue (min-heap) to efficiently select edges with the smallest weights.Time complexity:O((V + E) log V).
void dijkstra(int V, vector<pair<int,int>> adj[], int src) {
vector<int> dist(V, INT_MAX);
dist[src] = 0;
priority_queue<pair<int,int>, vector<pair<int,int>>, greater<pair<int,int>>> pq;
pq.push({0, src});
while (!pq.empty()) {
int u = pq.top().second;
pq.pop();
for (auto [v, w] : adj[u]) {
if (dist[u] + w < dist[v]) {
dist[v] = dist[u] + w;
pq.push({dist[v], v});
}
}
}
cout << "Jarak terpendek dari " << src << ":\n";
for (int i = 0; i < V; i++call() {
cout << "Ke Calling..." << i << " = " << dist[i] << endl;
}
};
- Protected Members declared as protected are only accessible within the class itself and its derived (child) classes. They are not accessible from outside the class. Commonly used in inheritance when you want child classes to access attributes, but still prevent external direct access.
class Gadget {
protected:
int warrantyYears;
};
class Laptop : public Gadget {
public:
void setWarranty(int w) {
warrantyYears = w; // accessible here
}
};
- Private Members declared as private are only accessible within the class itself. They are completely hidden from outside access, including derived classes. Commonly used for sensitive data that must be fully encapsulated.
class BankAccount {
private:
double balance;
public:
void deposit(double amount) {
balance += amount;
}
double getBalance() {
return balance;
}
};
1.3 Abstraction
Abstraction allows a programmer to display only the essential features without revealing implementation details. In C++, this is often done with abstract classes and pure virtual functions.
class Vehicle {
public:
virtual void move() = 0; // pure virtual → must be overridden by derived class
};
1.4 Pure Virtual Function
- Normal virtual function → has a default implementation, can be overridden.
- Pure virtual function → has no implementation and makes a class abstract.
Objects cannot be created from an abstract class; only from its derived classes.
#include <iostream>
using namespace std;
// Abstract base class
class Device {
public:
// Pure virtual function → no implementation here
virtual void start() = 0;
};
// Derived class Smartphone
class Smartphone : public Device {
public:
void start() override {
cout << "Smartphone is starting with touch screen..." << endl;
}
};
// Derived class Laptop
class Laptop : public Device {
public:
void start() override {
cout << "Laptop is starting with power button..." << endl;
}
};
int main() {
Device* d1 = new Smartphone();
Device* d2 = new Laptop();
d1->start(); // Output: Smartphone is starting with touch screen...
d2->start(); // Output: Laptop is starting with power button...
delete d1;
delete d2;
return 0;
}
1.5 MinimumRelevant SpanningSOLID Tree (MST)Principles
Prim’s Algorithm
StartsfromSingle Responsibility Principle (SRP) The Single Responsibility Principle (SRP) states that a class should only have one reason to change, meaning it should focus on a single
node,responsibility.repeatedlyThisaddingavoidsthebloatedminimum weight edgeclasses thatconnectshandleatoonewmanynodetaskstoattheonce.tree.For Efficientexample,withinsteadO(EofloghavingV)onecomplexityclassusingthatamanagesprioritybothqueue.appliance Suitabledetailsforanddenselogging,graphs.these
#include <bits/stdc++.hiostream>
#include <string>
using namespace std;
const// intSRP: INFAppliance =handles 1e9;only voidappliance-related primMST(intdata
n,class vector<vector<pair<int, int>>> &adj)Appliance {
vector<int>private:
key(n,string INF);name;
vector<bool>public:
inMST(n,Appliance(string false);n) vector<int>: parent(n,name(n) -1){}
string getName() { return name; }
};
// MulaiSeparate dariLogger node 0
key[0] = 0;
priority_queue<pair<int, int>, vector<pair<int, int>>, greater<>> pq;
pq.push({0, 0}); // {weight, node}
whileclass (!pq.empty())not mixed inside Appliance)
class Logger {
intpublic:
uvoid =log(string pq.top().second;
pq.pop();
if (inMST[u]) continue;
inMST[u] = true;
for (auto &[v, w] : adj[u])message) {
if (!inMST[v] && w < key[v]) {
key[v] = w;
pq.push({key[v], v});
parent[v] = u;
}
}
}
cout << "Edges[LOG]: in" MST<< (Prim)message << endl;
}
};
int main() {
Appliance fridge("Refrigerator");
Logger logger;
logger.log(fridge.getName() + " is running...");
return 0;
}
Note :\n"; In this SRP example, the Appliance class is responsible only for appliance data, while the Logger class is responsible for logging activities. Each class has one clear responsibility.
Liskov Substitution Principle (intLSP)
iThe =Liskov 1;Substitution iPrinciple (LSP) states that objects of a derived class should be substitutable for objects of their base class without breaking program behavior. In practice, this means that if a base class reference is used to call a function, it should work correctly with any derived class.
#include <iostream>
n;using i++namespace std;
// Base class
class Appliance {
public:
virtual void start() {
cout << parent[i]"Starting a generic appliance..." << endl;
}
};
// Derived class WashingMachine
class WashingMachine : public Appliance {
public:
void start() override {
cout << "Washing -machine is starting the wash cycle..." << iendl;
}
};
// Derived class Refrigerator
class Refrigerator : public Appliance {
public:
void start() override {
cout << "\n";Refrigerator is cooling..." << endl;
}
};
int main() {
intAppliance* na1 = 5;new vector<vector<pair<int,WashingMachine();
intAppliance* a2 = new Refrigerator();
a1->>> adj(n)start(); // contohWorks grafas berbobota (undirected)WashingMachine
adj[0].push_back({1, 2}a2->start(); adj[1].push_back({0,// 2});Works adj[0].push_back({3,as 6});a adj[3].push_back({0,Refrigerator
6});delete adj[1].push_back({2,a1;
3});delete adj[2].push_back({1,a2;
3});return adj[1].push_back({3, 8});
adj[3].push_back({1, 8});
adj[1].push_back({4, 5});
adj[4].push_back({1, 5});
adj[2].push_back({4, 7});
adj[4].push_back({2, 7});
primMST(n, adj);0;
}
Kruskal’sNote Algorithm
: Note Algorithm
- Here,
Sortsbothall edges by weight, then adds them one by one as long as they do not form a cycle.UsesDisjoint Set Union (DSU)to detectWashingMachine andpreventRefrigeratorcycles.can Timebecomplexity:O(E log E).More efficientsubstituted forsparseAppliancegraphs.without
#includeThis <bits/stdc++.h>shows usingLSP namespacebecause std;the structderived Edgeclasses {behave intcorrectly u,when v, w;
bool operator<(const Edge &other) const {
return w < other.w;
}
};
struct DSU {
vector<int> parent, rank;
DSU(int n) {
parent.resize(n);
rank.resize(n, 0);
iota(parent.begin(), parent.end(), 0);
}
int find(int x) {
if (x != parent[x])
parent[x] = find(parent[x]);
return parent[x];
}
bool unite(int a, int b) {
a = find(a);
b = find(b);
if (a == b) return false;
if (rank[a] < rank[b]) swap(a, b);
parent[b] = a;
if (rank[a] == rank[b]) rank[a]++;
return true;
}
};
void kruskalMST(int n, vector<Edge> &edges) {
sort(edges.begin(), edges.end());
DSU dsu(n);
cout << "Edgesused in MSTplace (Kruskal):\n";of forthe (autobase &e : edges) {
if (dsu.unite(e.u, e.v)) {
cout << e.u << " - " << e.v << "\n";
}
}
}
int main() {
int n = 5;
vector<Edge> edges = {
{0, 1, 2}, {0, 3, 6}, {1, 2, 3},
{1, 3, 8}, {1, 4, 5}, {2, 4, 7}
};
kruskalMST(n, edges);
}class.
2. Example of Graph Traversal
Example of an undirected weighted graph:
BFS (starting from node 0)Traversal per level (broad):0 → 1 → 2 → 3DFS (starting from node 0)Traversal as deep as possible before backtracking:0 → 1 → 3 → 2(order may vary depending on implementation, e.g., 0 → 1 → 2 → 3)Dijkstra (shortest path from node 0)Distance to0= 0Distance to1= 4Distance to2= 1Distance to3= 6