Pada bagian ini kita membangun fondasi yang akan dirender: kelas model untuk linked list, binary tree, dan graph berarah. Model hanya bertanggung jawab menyimpan data dan pointer — tidak ada kode cetak di sini. Kita pakai std::vector sebagai backing storage untuk linked list dan graph agar contoh tetap sederhana, sementara binary tree menggunakan std::unique_ptr agar ownership node jelas.

Model Linked List dengan Vector Backing #

Untuk keperluan visualisasi, kita tidak perlu mengimplementasikan linked list dengan pointer manual. Yang kita butuhkan adalah urutan elemen yang akan direntetakan [a] -> [b] -> [c] -> null. Sebuah std::vector<std::string> sudah cukup:

cpp
#include <memory>
#include <string>
#include <vector>

struct LinkedList {
    std::vector<std::string> values;
};

LinkedList buildSampleList() {
    return LinkedList{ {"a", "b", "c", "d"} };
}

Model ini menyimpan nilai sebagai std::string agar fleksibel menampung angka maupun label. Renderer pada Bagian 3 tinggal membaca values secara berurutan.

Model Binary Tree dengan unique_ptr #

Binary tree memerlukan pointer karena struktur bercabang tidak dapat dipetakan ke array 1D tanpa rumus indeks yang merepotkan untuk visualisasi. Kita pakai std::unique_ptr agar kepemilikan node jelas dan tidak ada kebocoran memori:

cpp
struct TreeNode {
    std::string label;
    std::unique_ptr<TreeNode> left;
    std::unique_ptr<TreeNode> right;

    explicit TreeNode(std::string l) : label(std::move(l)) {}
};

struct BinaryTree {
    std::unique_ptr<TreeNode> root;
};

Fungsi pembantu makeTree mempermudah pembentukan tree tanpa harus menulis std::make_unique berulang:

cpp
using NodePtr = std::unique_ptr<TreeNode>;

NodePtr node(std::string label, NodePtr l = nullptr, NodePtr r = nullptr) {
    auto n = std::make_unique<TreeNode>(std::move(label));
    n->left  = std::move(l);
    n->right = std::move(r);
    return n;
}

Model Graph Berarah dengan Adjacency List #

Graph berarah kita simpan sebagai std::unordered_map<int, std::vector<int>>. Key adalah node, value adalah daftar tetangga yang ditunjuk:

cpp
#include <unordered_map>

struct Graph {
    std::unordered_map<int, std::vector<int>> adj;
    int nodeCount = 0;

    void addNode() { adj[nodeCount++]; }
    void addEdge(int u, int v) { adj[u].push_back(v); }
};

Kode Lengkap Bagian 2 #

Berikut program utuh yang membangun ketiga model dan mencetak ringkasan teks untuk verifikasi bahwa struktur terbentuk dengan benar. Renderer visual menyusul pada Bagian 3.

cpp
#include <iostream>
#include <memory>
#include <string>
#include <vector>
#include <unordered_map>

struct LinkedList {
    std::vector<std::string> values;
};

LinkedList buildSampleList() {
    return LinkedList{ {"a", "b", "c", "d"} };
}

struct TreeNode {
    std::string label;
    std::unique_ptr<TreeNode> left;
    std::unique_ptr<TreeNode> right;
    explicit TreeNode(std::string l) : label(std::move(l)) {}
};

using NodePtr = std::unique_ptr<TreeNode>;

NodePtr node(std::string label, NodePtr l = nullptr, NodePtr r = nullptr) {
    auto n = std::make_unique<TreeNode>(std::move(label));
    n->left  = std::move(l);
    n->right = std::move(r);
    return n;
}

struct BinaryTree {
    NodePtr root;
};

BinaryTree buildSampleTree() {
    auto leftSub  = node("2", node("4"), node("5"));
    auto rightSub = node("3", node("6"), node("7"));
    return BinaryTree{ node("1", std::move(leftSub), std::move(rightSub)) };
}

struct Graph {
    std::unordered_map<int, std::vector<int>> adj;
    int nodeCount = 0;
    void addNode() { adj[nodeCount++]; }
    void addEdge(int u, int v) { adj[u].push_back(v); }
};

Graph buildSampleGraph() {
    Graph g;
    g.addNode(); g.addNode(); g.addNode();   // node 0, 1, 2
    g.addEdge(0, 1); g.addEdge(0, 2);
    g.addEdge(1, 2);
    g.addEdge(2, 0);                          // cycle
    return g;
}

void printSummary(const LinkedList& l, const BinaryTree& t, const Graph& g) {
    std::cout << "Linked list values: ";
    for (const auto& v : l.values) std::cout << v << " ";
    std::cout << "\nBinary tree root label: " << t.root->label << "\n";
    std::cout << "Graph adjacency:\n";
    for (int i = 0; i < g.nodeCount; ++i) {
        std::cout << "  " << i << " -> ";
        for (int nb : g.adj.at(i)) std::cout << nb << " ";
        std::cout << "\n";
    }
}

int main() {
    auto l = buildSampleList();
    auto t = buildSampleTree();
    auto g = buildSampleGraph();
    printSummary(l, t, g);
    return 0;
}

Output yang Dihasilkan:

text
Linked list values: a b c d
Binary tree root label: 1
Graph adjacency:
  0 -> 1 2
  1 -> 2
  2 -> 0

Bedah Baris Penting #

  1. NodePtr node(...): fungsi pembantu membangun TreeNode dengan dua anak opsional. std::move memindahkan kepemilikan unique_ptr ke dalam node baru — setelah pemanggilan, pemanggil tidak bisa lagi mengakses l atau r karena sudah berpindah.
  2. buildSampleTree(): tree dibangun dari bawah ke atas. Sub-tree kiri dan kanan dibuat lebih dulu, lalu dijadikan anak root pada saat node("1", ...) dipanggil.
  3. g.addEdge(2, 0): edge balik ini membuat cycle 0 → 1 → 2 → 0. Cycle ini akan menjadi kasus uji penting pada Bagian 4.
  4. g.adj.at(i): kita pakai .at() alih-alih [] karena [] pada unordered_map non-const akan menyisipkan entri kosong bila key tidak ada. Dengan .at(), bila ada bug pada addNode, program crash segera — bukan diam-diam menambah entri hantu.

Kompilasi dan Pengujian #

Simpan kode sebagai model.cpp dan kompilasi:

bash
g++ -std=c++17 -O2 -Wall -Wextra -o model.bin model.cpp
./model.bin

Bila output sesuai dengan blok di atas, ketiga model terbentuk dengan benar. Pada Bagian 3 kita membangun renderer yang mengubah model ini menjadi ASCII art.