Skip to main content

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)EncapsulationrepresentsHiding andata object.implementation using restricted access through setter/getter.
  • EdgeAbstractionrepresentsHiding theimplementation relationshipdetails betweenwhile objects.
  • exposing
only

Graphsrelevant can be:

  • Directed or Undirected.interfaces.
  • WeightedInheritance or 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.

  1. Adjacency Matrix
    A 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 for dense graphs.
    • Edge lookup is efficient with O(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,return 1,batteryLevel; 0, 0, 1},
        {1, 0, 1, 1, 0},
        {0, 1, 0, 1, 0},
        {0, 1, 1, 0, 1},
        {1, 0, 0, 1, 0}}
    };
    
  2. image

  3. Access Specifiers in Encapsulation

    AdjacencyC++ List
    provides Eachthree vertexmain storesaccess specifiers that control how members of a listclass ofcan itsbe adjacent vertices (neighbors).accessed:

      1. Memory-efficientPublic forMembers sparse graphs,declared as onlypublic existingcan edgesbe areaccessed stored.
      2. from
      3. Edge lookup may take up to O(V)anywhere in the worstprogram case.
      4. (inside
    and
    vector<int>outside adj[5];the adj[0]class).push_back(1); adj[0].push_back(4);Commonly adj[1].push_back(0);used adj[1].push_back(2);for adj[1].push_back(3);interfaces, 
    such as getters, setters, or functions that need to be available to other classes.

1.3 Graph Traversal

Graph traversal can be performed in two main ways:


1.4 Shortest Path Algorithm: Dijkstra

  • Finds the shortest path from a source node to all other nodes in a positively 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;
    }
};
  1. 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
    }
};
  1. 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

  • Starts from

    Single 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. repeatedlyThis addingavoids thebloated minimum weight edgeclasses that connectshandle atoo newmany nodetasks toat theonce. tree.

  • For
  • Efficientexample, withinstead O(Eof loghaving V)one complexityclass usingthat amanages priorityboth queue.
  • appliance
  • Suitabledetails forand denselogging, graphs.
  • these
should be separated into two different classes.

#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’s
    Note Algorithm

    :
      Here,
    • Sortsboth all edges by weight, then adds them one by one as long as they do not form a cycle.
    • Uses Disjoint Set Union (DSU) to detectWashingMachine and preventRefrigerator cycles.
    • can
    • Timebe complexity: O(E log E).
    • More efficientsubstituted for sparseAppliance graphs.
    • without
    issues.
    #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:

    image

    • BFS (starting from node 0)
      Traversal per level (broad):
      0 → 1 → 2 → 3

    • DFS (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 to 0 = 0
      • Distance to 1 = 4
      • Distance to 2 = 1
      • Distance to 3 = 6