Mata Kuliah: Struktur Data dan Algoritma
Pertemuan: 06
Topik: Queue
Waktu: 150 menit
Sifat: Open Book
Prinsip yang digunakan oleh struktur data Queue adalah…
Operasi untuk menambahkan elemen ke dalam queue disebut…
Pada queue, operasi penghapusan elemen dilakukan pada posisi…
Diberikan operasi queue berikut secara berurutan:
enqueue(5), enqueue(10), enqueue(15), dequeue(), enqueue(20), dequeue()
Apa isi queue setelah semua operasi selesai?
Pada circular queue dengan capacity 5, jika front = 3 dan rear = 1, berapa jumlah elemen dalam queue?
Masalah “false overflow” pada array linear queue terjadi ketika…
Formula untuk menghitung posisi rear berikutnya pada circular queue adalah…
Pada linked list implementation queue, pointer rear diperlukan agar operasi…
Deque (Double-Ended Queue) memungkinkan operasi…
Manakah yang merupakan aplikasi yang TEPAT untuk struktur data Queue?
Pada circular queue dengan capacity 6, operasi (rear + 1) % 6 ketika rear = 5 akan menghasilkan…
Kompleksitas waktu untuk operasi enqueue dan dequeue pada circular queue adalah…
Diberikan circular queue dengan capacity 4 dan kondisi awal kosong. Setelah operasi:
enqueue(A), enqueue(B), enqueue(C), dequeue(), dequeue(), enqueue(D), enqueue(E)
Nilai front dan rear adalah…
Input-Restricted Deque adalah deque yang…
Dalam implementasi queue menggunakan dua stack, operasi dequeue memiliki kompleksitas…
Pada STL C++, untuk melihat elemen paling depan queue tanpa menghapusnya digunakan fungsi…
Dalam simulasi round-robin scheduling, queue digunakan untuk…
Perbedaan utama antara std::queue dan std::deque di C++ adalah…
Pada linked queue, kondisi queue kosong ditandai dengan…
Untuk mengatasi false overflow pada array linear queue TANPA menggunakan circular array, solusinya adalah…
Jelaskan perbedaan mendasar antara Stack dan Queue! Berikan masing-masing 2 contoh aplikasi nyata!
Diberikan operasi queue berikut:
enqueue(15), enqueue(25), enqueue(35), front(), dequeue(), enqueue(45), rear(), size(), dequeue(), dequeue()
Tunjukkan isi queue dan output setiap operasi dalam bentuk tabel!
Gambarkan representasi queue menggunakan array linear dengan capacity 6 yang berisi elemen [20, 30, 40] dengan front = 1 dan rear = 3! Jelaskan apa yang terjadi jika dilakukan enqueue(50) dan dequeue()!
Jelaskan masalah “false overflow” pada implementasi queue dengan array linear! Mengapa masalah ini tidak terjadi pada circular queue? Sertakan ilustrasi!
Implementasikan fungsi enqueue() dan dequeue() untuk circular queue dalam C++! Jelaskan setiap baris kode dengan komentar!
Pada circular queue dengan capacity 7, jika kondisi awal front = 5 dan rear = 2 dengan elemen [_, _, 10, _, _, 30, 40] (indeks 0-6), tentukan: a. Jumlah elemen dalam queue b. Hasil operasi enqueue(50) c. Hasil 2 kali operasi dequeue() d. Kondisi akhir front dan rear
Implementasikan fungsi display() untuk linked queue yang menampilkan semua elemen dari front ke rear!
Bandingkan implementasi queue menggunakan array (circular) dan linked list dari aspek: a. Kompleksitas waktu setiap operasi b. Penggunaan memori c. Kelebihan dan kekurangan masing-masing d. Kapan sebaiknya menggunakan masing-masing implementasi
Implementasikan fungsi reverseQueue() yang membalik isi queue menggunakan stack tambahan! Analisis kompleksitas waktu dan ruang!
Implementasikan queue yang mendukung operasi getMin() dengan kompleksitas O(1)! Jelaskan strategi yang digunakan!
Implementasikan fungsi untuk memeriksa apakah sebuah queue membentuk palindrom! Contoh: Queue [1, 2, 3, 2, 1] adalah palindrom.
Buatlah implementasi priority queue sederhana menggunakan 3 queue biasa (HIGH, MEDIUM, LOW priority)! Sertakan fungsi enqueue dengan parameter prioritas dan dequeue yang mengambil dari prioritas tertinggi terlebih dahulu!
Jelaskan konsep Deque dan implementasikan fungsi insertFront() dan deleteRear() pada circular array deque!
Implementasikan queue menggunakan dua stack! Jelaskan mengapa kompleksitas amortized untuk dequeue adalah O(1)!
Buatlah simulasi antrian pelanggan di kasir supermarket dengan ketentuan:
Latar Belakang: Pusat Komando dan Kendali (Kodal) menerima berbagai jenis pesan dari unit-unit di lapangan. Pesan memiliki 3 tingkat prioritas sesuai standar NATO:
Kebutuhan Sistem:
Tugas:
MilitaryMessageSystemTemplate Kode:
#include <iostream>
#include <queue>
#include <string>
#include <ctime>
using namespace std;
enum Priority { FLASH, IMMEDIATE, ROUTINE };
struct Message {
int id;
string sender;
time_t timestamp;
Priority priority;
string content;
};
class MilitaryMessageSystem {
// Implementasikan di sini
};
int main() {
MilitaryMessageSystem system;
// Simulasi penerimaan dan pemrosesan pesan
return 0;
}
Latar Belakang: Pangkalan Udara TNI AU mengelola antrian pesawat untuk take-off dan landing. Sistem harus memprioritaskan:
Selain itu, runway memiliki kapasitas terbatas dan perlu dijadwalkan secara efisien.
Kebutuhan Sistem:
Tugas:
AirbaseQueueSystemKriteria Penilaian:
| No | Jawaban | Penjelasan Singkat |
|---|---|---|
| 1 | B | Queue menggunakan FIFO (First In First Out) |
| 2 | C | Enqueue adalah operasi menambah elemen ke queue |
| 3 | B | Dequeue mengambil elemen dari front |
| 4 | C | Setelah semua operasi: [15, 20] |
| 5 | C | (rear - front + capacity) % capacity + 1 = (1-3+5)%5+1 = 4 |
| 6 | C | False overflow = ada slot kosong di awal tapi tidak bisa enqueue |
| 7 | B | Formula circular: (rear + 1) % capacity |
| 8 | B | Pointer rear membuat enqueue O(1), tanpa perlu traverse |
| 9 | C | Deque = insert/delete di kedua ujung |
| 10 | C | BFS menggunakan queue untuk menjelajahi level per level |
| 11 | B | (5 + 1) % 6 = 0 (wrap-around ke awal) |
| 12 | D | Semua operasi circular queue O(1) |
| 13 | C | Trace: front=2 (setelah 2 dequeue), rear=1 (setelah wrap) |
| 14 | A | Input-Restricted: insert di satu ujung, delete di dua ujung |
| 15 | C | Setiap elemen dipindah max 2 kali, jadi amortized O(1) |
| 16 | C | std::queue menggunakan front() untuk melihat elemen depan |
| 17 | C | Round-robin menyimpan proses yang menunggu dalam queue |
| 18 | B | std::deque mendukung operasi di kedua ujung + random access |
| 19 | D | Queue kosong: front dan rear keduanya nullptr |
| 20 | C | Shift elements adalah solusi tanpa circular (tapi O(n)) |
Perbedaan Stack dan Queue:
| Aspek | Stack | Queue |
|---|---|---|
| Prinsip | LIFO (Last In First Out) | FIFO (First In First Out) |
| Penyisipan | Di top (push) | Di rear (enqueue) |
| Penghapusan | Di top (pop) | Di front (dequeue) |
| Akses | Hanya top | Front dan rear |
Contoh Aplikasi Stack:
Contoh Aplikasi Queue:
| Langkah | Operasi | Isi Queue | Output |
|---|---|---|---|
| 1 | enqueue(15) | [15] | - |
| 2 | enqueue(25) | [15, 25] | - |
| 3 | enqueue(35) | [15, 25, 35] | - |
| 4 | front() | [15, 25, 35] | 15 |
| 5 | dequeue() | [25, 35] | 15 |
| 6 | enqueue(45) | [25, 35, 45] | - |
| 7 | rear() | [25, 35, 45] | 45 |
| 8 | size() | [25, 35, 45] | 3 |
| 9 | dequeue() | [35, 45] | 25 |
| 10 | dequeue() | [45] | 35 |
Isi akhir: [45] dengan front = rear = 45

Gambar: Representasi Array Linear Queue dan operasi enqueue/dequeue
Penjelasan:
Masalah False Overflow:
False overflow terjadi ketika array linear queue dianggap penuh padahal masih ada slot kosong di awal array.

Gambar: Perbandingan False Overflow pada Linear Queue vs Solusi Circular Queue
Penjelasan:
Masalah pada Linear Queue:
Solusi Circular Queue:
(index + 1) % capacity memungkinkan wrap-aroundrear = (4 + 1) % 5 = 0 → rear kembali ke index 0template <typename T>
class CircularQueue {
private:
T* data; // Array untuk menyimpan elemen
int frontIndex; // Indeks elemen depan
int rearIndex; // Indeks elemen belakang
int capacity; // Kapasitas maksimum
int count; // Jumlah elemen saat ini
public:
// Konstruktor
CircularQueue(int size) {
capacity = size;
data = new T[capacity];
frontIndex = 0;
rearIndex = -1; // Belum ada elemen
count = 0;
}
// Menambahkan elemen ke rear
void enqueue(const T& item) {
// Cek apakah queue penuh
if (count == capacity) {
throw overflow_error("Queue penuh!");
}
// Hitung posisi rear berikutnya secara circular
// Formula: (current + 1) % capacity
rearIndex = (rearIndex + 1) % capacity;
// Simpan elemen di posisi rear baru
data[rearIndex] = item;
// Tambah counter
count++;
}
// Menghapus dan mengembalikan elemen dari front
T dequeue() {
// Cek apakah queue kosong
if (count == 0) {
throw underflow_error("Queue kosong!");
}
// Simpan elemen yang akan dihapus
T item = data[frontIndex];
// Gerakkan front ke posisi berikutnya secara circular
frontIndex = (frontIndex + 1) % capacity;
// Kurangi counter
count--;
// Kembalikan elemen yang dihapus
return item;
}
};

Gambar: Trace operasi pada Circular Queue dengan capacity 7
Penjelasan Jawaban:
a. Jumlah elemen awal: 3 elemen (30, 40, 10 dalam urutan queue)
b. Setelah enqueue(50):
c. Setelah 2x dequeue():
d. Kondisi akhir: front = 0, rear = 3, jumlah elemen = 2 (yaitu 10 di idx 2 dan 50 di idx 3)
template <typename T>
class LinkedQueue {
private:
struct Node {
T data;
Node* next;
Node(T val) : data(val), next(nullptr) {}
};
Node* frontPtr;
Node* rearPtr;
int count;
public:
// Fungsi display() - menampilkan semua elemen
void display() const {
// Cek jika queue kosong
if (frontPtr == nullptr) {
cout << "Queue kosong" << endl;
return;
}
cout << "Queue (front -> rear): [";
// Traverse dari front ke rear
Node* current = frontPtr;
while (current != nullptr) {
cout << current->data;
// Tambah koma jika bukan elemen terakhir
if (current->next != nullptr) {
cout << ", ";
}
// Pindah ke node berikutnya
current = current->next;
}
cout << "]" << endl;
cout << "Size: " << count << endl;
}
};
Perbandingan Array (Circular) vs Linked List:
| Aspek | Circular Array | Linked List |
|---|---|---|
| a. Kompleksitas Waktu | ||
| - enqueue | O(1) | O(1) |
| - dequeue | O(1) | O(1) |
| - front/rear | O(1) | O(1) |
| - isEmpty | O(1) | O(1) |
| b. Penggunaan Memori | ||
| - Total | O(capacity) - fixed | O(n) - sesuai jumlah elemen |
| - Per elemen | Hanya data | Data + pointer (8 byte) |
| - Overhead | Slot kosong jika tidak penuh | Pointer overhead |
| c. Kelebihan | Cache-friendly, no pointer overhead | Ukuran dinamis, tidak perlu resize |
| d. Kekurangan | Ukuran fixed, bisa waste memory | Cache-unfriendly, pointer overhead |
Kapan menggunakan masing-masing:
#include <stack>
#include <queue>
template <typename T>
void reverseQueue(queue<T>& q) {
stack<T> tempStack;
// Langkah 1: Pindahkan semua elemen dari queue ke stack
while (!q.empty()) {
tempStack.push(q.front());
q.pop();
}
// Langkah 2: Pindahkan kembali dari stack ke queue
// Karena stack LIFO, urutan akan terbalik
while (!tempStack.empty()) {
q.push(tempStack.top());
tempStack.pop();
}
}
// Contoh penggunaan
int main() {
queue<int> q;
q.push(1); q.push(2); q.push(3); q.push(4);
// Queue: [1, 2, 3, 4]
reverseQueue(q);
// Queue: [4, 3, 2, 1]
return 0;
}
Analisis Kompleksitas:
#include <queue>
#include <deque>
class MinQueue {
private:
queue<int> mainQueue; // Queue utama
deque<int> minDeque; // Menyimpan kandidat minimum
public:
void enqueue(int value) {
mainQueue.push(value);
// Hapus elemen dari belakang minDeque yang lebih besar
// dari value baru (tidak mungkin jadi minimum)
while (!minDeque.empty() && minDeque.back() > value) {
minDeque.pop_back();
}
// Tambahkan value ke minDeque
minDeque.push_back(value);
}
int dequeue() {
if (mainQueue.empty()) {
throw runtime_error("Queue kosong!");
}
int value = mainQueue.front();
mainQueue.pop();
// Jika elemen yang dihapus adalah minimum saat ini
if (value == minDeque.front()) {
minDeque.pop_front();
}
return value;
}
int getMin() const {
if (minDeque.empty()) {
throw runtime_error("Queue kosong!");
}
return minDeque.front(); // O(1)!
}
};
Strategi:
#include <queue>
#include <stack>
bool isQueuePalindrome(queue<int> q) {
stack<int> s;
int n = q.size();
// Copy semua elemen ke stack
queue<int> tempQ = q;
while (!tempQ.empty()) {
s.push(tempQ.front());
tempQ.pop();
}
// Bandingkan queue (front→back) dengan stack (back→front)
while (!q.empty()) {
if (q.front() != s.top()) {
return false; // Bukan palindrom
}
q.pop();
s.pop();
}
return true; // Palindrom!
}
// Contoh
int main() {
queue<int> q1, q2;
// Palindrom: 1, 2, 3, 2, 1
q1.push(1); q1.push(2); q1.push(3); q1.push(2); q1.push(1);
// Bukan palindrom: 1, 2, 3, 4, 5
q2.push(1); q2.push(2); q2.push(3); q2.push(4); q2.push(5);
cout << "Q1 palindrom? " << (isQueuePalindrome(q1) ? "Ya" : "Tidak") << endl; // Ya
cout << "Q2 palindrom? " << (isQueuePalindrome(q2) ? "Ya" : "Tidak") << endl; // Tidak
return 0;
}
#include <queue>
#include <string>
#include <iostream>
using namespace std;
enum Priority { HIGH = 0, MEDIUM = 1, LOW = 2 };
template <typename T>
class SimplePriorityQueue {
private:
queue<T> highQueue;
queue<T> mediumQueue;
queue<T> lowQueue;
public:
// Enqueue dengan prioritas
void enqueue(const T& item, Priority priority) {
switch (priority) {
case HIGH:
highQueue.push(item);
cout << "[HIGH] Added: " << item << endl;
break;
case MEDIUM:
mediumQueue.push(item);
cout << "[MEDIUM] Added: " << item << endl;
break;
case LOW:
lowQueue.push(item);
cout << "[LOW] Added: " << item << endl;
break;
}
}
// Dequeue dari prioritas tertinggi yang tersedia
T dequeue() {
if (!highQueue.empty()) {
T item = highQueue.front();
highQueue.pop();
cout << "[HIGH] Processed: " << item << endl;
return item;
} else if (!mediumQueue.empty()) {
T item = mediumQueue.front();
mediumQueue.pop();
cout << "[MEDIUM] Processed: " << item << endl;
return item;
} else if (!lowQueue.empty()) {
T item = lowQueue.front();
lowQueue.pop();
cout << "[LOW] Processed: " << item << endl;
return item;
} else {
throw runtime_error("Priority queue kosong!");
}
}
bool isEmpty() const {
return highQueue.empty() && mediumQueue.empty() && lowQueue.empty();
}
int size() const {
return highQueue.size() + mediumQueue.size() + lowQueue.size();
}
};
int main() {
SimplePriorityQueue<string> pq;
pq.enqueue("Task A", LOW);
pq.enqueue("Task B", HIGH);
pq.enqueue("Task C", MEDIUM);
pq.enqueue("Task D", HIGH);
pq.enqueue("Task E", LOW);
cout << "\n--- Processing ---\n";
while (!pq.isEmpty()) {
pq.dequeue();
}
return 0;
}
Output:
[LOW] Added: Task A
[HIGH] Added: Task B
[MEDIUM] Added: Task C
[HIGH] Added: Task D
[LOW] Added: Task E
--- Processing ---
[HIGH] Processed: Task B
[HIGH] Processed: Task D
[MEDIUM] Processed: Task C
[LOW] Processed: Task A
[LOW] Processed: Task E
Konsep Deque: Deque (Double-Ended Queue) adalah struktur data linear yang memungkinkan operasi insert dan delete di kedua ujung (front dan rear).
template <typename T>
class CircularDeque {
private:
T* data;
int frontIndex;
int rearIndex;
int capacity;
int count;
public:
CircularDeque(int size) {
capacity = size;
data = new T[capacity];
frontIndex = 0;
rearIndex = capacity - 1;
count = 0;
}
// Insert di depan
void insertFront(const T& item) {
if (count == capacity) {
throw overflow_error("Deque penuh!");
}
// Geser front ke belakang (circular)
frontIndex = (frontIndex - 1 + capacity) % capacity;
data[frontIndex] = item;
count++;
}
// Delete dari belakang
T deleteRear() {
if (count == 0) {
throw underflow_error("Deque kosong!");
}
T item = data[rearIndex];
// Geser rear ke depan (circular)
rearIndex = (rearIndex - 1 + capacity) % capacity;
count--;
return item;
}
// Operasi lain: insertRear, deleteFront, getFront, getRear, isEmpty, isFull
};
#include <stack>
#include <stdexcept>
using namespace std;
template <typename T>
class QueueUsingTwoStacks {
private:
stack<T> stackIn; // Untuk enqueue
stack<T> stackOut; // Untuk dequeue
// Transfer dari stackIn ke stackOut
void transfer() {
while (!stackIn.empty()) {
stackOut.push(stackIn.top());
stackIn.pop();
}
}
public:
void enqueue(const T& item) {
stackIn.push(item); // Selalu O(1)
}
T dequeue() {
if (isEmpty()) {
throw underflow_error("Queue kosong!");
}
// Transfer hanya jika stackOut kosong
if (stackOut.empty()) {
transfer();
}
T item = stackOut.top();
stackOut.pop();
return item;
}
bool isEmpty() const {
return stackIn.empty() && stackOut.empty();
}
};
Mengapa Amortized O(1):
Analisis per elemen:
Total: setiap elemen mengalami 3 operasi O(1) sepanjang hidupnya.
Meskipun satu operasi dequeue bisa O(n) saat transfer, jika kita amortize (bagi rata) ke semua operasi, setiap dequeue rata-rata O(1).
Contoh:
Total 5 dequeue, total work = 5 (transfer) + 5 (pop) = 10 = 2n Amortized per dequeue = 10/5 = 2 = O(1)
#include <iostream>
#include <queue>
#include <string>
#include <cstdlib>
#include <ctime>
using namespace std;
struct Customer {
int id;
int serviceTime; // 1-5 menit
int arrivalTime;
};
class SupermarketSimulation {
private:
queue<Customer> cashier1;
queue<Customer> cashier2;
int customerCount;
int currentTime;
int totalWaitTime1, totalWaitTime2;
int servedCount1, servedCount2;
int remainingTime1, remainingTime2; // Sisa waktu layanan customer saat ini
public:
SupermarketSimulation() {
customerCount = 0;
currentTime = 0;
totalWaitTime1 = totalWaitTime2 = 0;
servedCount1 = servedCount2 = 0;
remainingTime1 = remainingTime2 = 0;
}
void customerArrives() {
Customer c;
c.id = ++customerCount;
c.serviceTime = rand() % 5 + 1; // 1-5 menit
c.arrivalTime = currentTime;
// Pilih kasir dengan antrian lebih pendek
if (cashier1.size() <= cashier2.size()) {
cashier1.push(c);
cout << "[T=" << currentTime << "] Customer #" << c.id
<< " (service: " << c.serviceTime << " min) → Cashier 1 "
<< "(queue: " << cashier1.size() << ")" << endl;
} else {
cashier2.push(c);
cout << "[T=" << currentTime << "] Customer #" << c.id
<< " (service: " << c.serviceTime << " min) → Cashier 2 "
<< "(queue: " << cashier2.size() << ")" << endl;
}
}
void processOneMinute() {
// Process Cashier 1
if (remainingTime1 > 0) {
remainingTime1--;
}
if (remainingTime1 == 0 && !cashier1.empty()) {
Customer c = cashier1.front();
cashier1.pop();
int waitTime = currentTime - c.arrivalTime;
totalWaitTime1 += waitTime;
servedCount1++;
cout << "[T=" << currentTime << "] Cashier 1 finished Customer #"
<< c.id << " (waited: " << waitTime << " min)" << endl;
if (!cashier1.empty()) {
remainingTime1 = cashier1.front().serviceTime;
}
}
// Process Cashier 2
if (remainingTime2 > 0) {
remainingTime2--;
}
if (remainingTime2 == 0 && !cashier2.empty()) {
Customer c = cashier2.front();
cashier2.pop();
int waitTime = currentTime - c.arrivalTime;
totalWaitTime2 += waitTime;
servedCount2++;
cout << "[T=" << currentTime << "] Cashier 2 finished Customer #"
<< c.id << " (waited: " << waitTime << " min)" << endl;
if (!cashier2.empty()) {
remainingTime2 = cashier2.front().serviceTime;
}
}
currentTime++;
}
void startFirstCustomers() {
if (!cashier1.empty() && remainingTime1 == 0) {
remainingTime1 = cashier1.front().serviceTime;
}
if (!cashier2.empty() && remainingTime2 == 0) {
remainingTime2 = cashier2.front().serviceTime;
}
}
bool hasCustomers() {
return !cashier1.empty() || !cashier2.empty() || remainingTime1 > 0 || remainingTime2 > 0;
}
void printStatistics() {
cout << "\n=== STATISTICS ===" << endl;
cout << "Cashier 1: Served " << servedCount1 << " customers, "
<< "Avg wait: " << (servedCount1 ? (float)totalWaitTime1/servedCount1 : 0) << " min" << endl;
cout << "Cashier 2: Served " << servedCount2 << " customers, "
<< "Avg wait: " << (servedCount2 ? (float)totalWaitTime2/servedCount2 : 0) << " min" << endl;
}
};
int main() {
srand(time(0));
SupermarketSimulation sim;
cout << "=== CUSTOMER ARRIVALS ===" << endl;
// 10 customers datang di waktu berbeda
for (int i = 0; i < 10; i++) {
sim.customerArrives();
if (i < 9 && rand() % 2 == 0) {
// Kadang 2 customer datang bersamaan
sim.customerArrives();
i++;
}
}
sim.startFirstCustomers();
cout << "\n=== PROCESSING ===" << endl;
while (sim.hasCustomers()) {
sim.processOneMinute();
}
sim.printStatistics();
return 0;
}
Rubrik Penilaian:
| Kriteria | Bobot | Deskripsi |
|---|---|---|
| Struktur Data | 30% | Penggunaan 3 queue untuk 3 prioritas |
| Prioritas Handling | 25% | FLASH → IMMEDIATE → ROUTINE |
| FIFO per Kategori | 15% | Pesan dalam kategori sama diproses sesuai urutan masuk |
| Statistik | 15% | Total pesan, rata-rata waktu tunggu |
| Kode & Dokumentasi | 15% | Komentar, struktur kode, naming |
Rubrik Penilaian:
| Kriteria | Bobot | Deskripsi |
|---|---|---|
| Implementasi Queue | 30% | Antrian take-off dan landing terpisah |
| Priority Handling | 25% | Emergency > Combat > Training > Regular |
| Landing Priority | 15% | Landing selalu diprioritaskan dari take-off |
| Simulasi Lengkap | 20% | 20 pesawat, log operasi, statistik |
| Analisis Kompleksitas | 10% | Waktu dan ruang |
This repository is licensed under the Creative Commons Attribution 4.0 International (CC BY 4.0). Commercial use is permitted, provided attribution is given to the author.
© 2026 Anindito