// STUHUB · C++ DATA STRUCTURES

Data Structures Exam Cheat Sheet (C++ Quick Reference)

Night-before reference for a data structures exam: declarations, complexity tables, the rule that decides each answer, and worked traces in C++.

Introduction

Scannable, not readable. Find a fact, use it, move on. Every code block here was compiled with g++ -std=c++17 -Wall; every printed value is real program output.

The sheet runs in the exam's own order: array, singly linked list, stack, queue (linked and circular), tree and BST, graph, sorting, searching — then three worked traces (infix to postfix, BFT queue state, Dijkstra) and a twelve-item list for the last twenty minutes.

1. Array — declarations

Asked: pointer-arithmetic output, 2D indexing, array length.

cpp
int a[5] = {10, 20, 30, 40, 50};
int* p = a;              // array name decays to &a[0]
int m[2][3] = {{1,2,3},{4,5,6}};
int len = sizeof(a) / sizeof(a[0]);

1. Array — outputs, complexity, and the rule

ExpressionSame asOutput
*(p + 2)a[2]30
*(a + 4)a[4]50
*(*(m + 1) + 2)m[1][2]6
sizeof(a)/sizeof(a[0])element count5
OperationComplexity
Access by indexO(1)
Search (unsorted)O(n)
Insert / delete at positionO(n) (shifting)

The rule: *(ptr + n) advances by n * sizeof(element), not n bytes. Row-major address: base + (i * COLS + j) * sizeof(element).

Costliest mistake: sizeof(arr)/sizeof(arr[0]) gives the wrong answer inside a function — the parameter is a pointer, so sizeof returns the pointer size. Only valid where the array is declared.

2. Singly linked list — the node

Asked: insert, delete by value, in-place reverse, count nodes.

cpp
struct Node {
    int data;
    Node* next;
    Node(int v) : data(v), next(nullptr) {}
};

2. Singly linked list — insert, delete, reverse

Insert at the front, delete by value, and reverse in place.

cpp
void insertFront(Node*& head, int v) {
    Node* n = new Node(v);
    n->next = head;          // link new node first
    head = n;                // then move head
}

bool deleteValue(Node*& head, int v) {
    if (head == nullptr) return false;
    if (head->data == v) {               // deleting the head is a separate case
        Node* t = head; head = head->next; delete t; return true;
    }
    Node* prev = head;
    while (prev->next != nullptr && prev->next->data != v) prev = prev->next;
    if (prev->next == nullptr) return false;
    Node* t = prev->next;
    prev->next = t->next;
    delete t;
    return true;
}

void reverseList(Node*& head) {
    Node* prev = nullptr;
    Node* cur  = head;
    while (cur != nullptr) {
        Node* nxt = cur->next;   // 1. save
        cur->next = prev;        // 2. flip
        prev = cur;              // 3. advance prev
        cur  = nxt;              // 4. advance cur
    }
    head = prev;                 // do not forget this line
}

2. Singly linked list — output and cost table

Real output from the compiled demo:

built:    5 -> 10 -> 20 -> 30
del 20:   5 -> 10 -> 30
reversed: 30 -> 10 -> 5
count = 3
OperationSinglyDoublyArray
Insert/delete at headO(1)O(1)O(n)
Insert at tail (with tail ptr)O(1)O(1)O(1) amortised
Delete a given nodeO(n) (need prev)O(1)O(n)
Access k-th elementO(n)O(n)O(1)
Extra memory per node1 pointer2 pointers0

The rule: in reversal, save next before overwriting cur->next. Order is save → flip → advance → advance.

Costliest mistake: forgetting head = prev; at the end of reverse — the list is correctly reversed in memory but head still points at what is now the tail, so printing shows one node.

3. Stack — array implementation

LIFO. Array version, top starts at -1.

OperationComplexity
push / pop / peek / isEmptyO(1)

Precedence (highest first): ^* /+ -. ( has lowest precedence once on the stack.

cpp
const int MAX = 100;
class ArrayStack {
    int arr[MAX];
    int top;
public:
    ArrayStack() : top(-1) {}
    bool isEmpty() const { return top == -1; }
    bool isFull()  const { return top == MAX - 1; }
    void push(int v) { if (isFull()) return; arr[++top] = v; }   // pre-increment
    int  pop()       { if (isEmpty()) return -1; return arr[top--]; } // post-decrement
    int  peek() const{ return isEmpty() ? -1 : arr[top]; }
    int  size() const{ return top + 1; }
};

3. Stack — postfix evaluation

Postfix evaluation:

cpp
int evaluatePostfix(const string& exp) {
    stack<int> s;
    for (char c : exp) {
        if (isdigit(c)) {
            s.push(c - '0');
        } else {
            int op2 = s.top(); s.pop();   // RIGHT operand pops first
            int op1 = s.top(); s.pop();   // LEFT operand pops second
            switch (c) {
                case '+': s.push(op1 + op2); break;
                case '-': s.push(op1 - op2); break;
                case '*': s.push(op1 * op2); break;
                case '/': s.push(op1 / op2); break;
            }
        }
    }
    return s.top();
}

3. Stack — the operand-order trap

Compiled output: evaluatePostfix("62/3-42*+") prints 8 (6/2 = 3, 3−3 = 0, 4×2 = 8, 0+8 = 8).

Costliest mistake: popping the operands in the wrong order. The first value popped is the right operand. Gets - and / wrong every time and + and * right, so it hides until it costs marks.

4a. Queue — linked-list queue

FIFO. Insert at rear, remove from front.

cpp
struct QNode { int data; QNode* next; QNode(int v) : data(v), next(nullptr) {} };

class LinkedQueue {
    QNode* qFront; QNode* qRear; int count;
public:
    LinkedQueue() : qFront(nullptr), qRear(nullptr), count(0) {}
    bool isEmpty() const { return qFront == nullptr; }
    void enqueue(int v) {
        QNode* n = new QNode(v);
        if (qRear == nullptr) { qFront = qRear = n; }   // first node: both pointers
        else { qRear->next = n; qRear = n; }
        count++;
    }
    bool dequeue(int& out) {
        if (qFront == nullptr) return false;
        QNode* t = qFront;
        out = t->data;
        qFront = qFront->next;
        if (qFront == nullptr) qRear = nullptr;         // <-- the line people drop
        delete t;
        count--;
        return true;
    }
};

4a. Queue — the dangling rear pointer

Costliest mistake: not resetting qRear = nullptr when the last element leaves. The queue reports empty, but qRear dangles at freed memory and the next enqueue writes through it.

4b. Queue — circular queue

The array version, with every index advance taken modulo capacity.

cpp
class CircularQueue {
    int* arr; int capacity, front, rear;
public:
    CircularQueue(int cap) : capacity(cap), front(0), rear(0) { arr = new int[cap]; }
    ~CircularQueue() { delete[] arr; }
    bool isEmpty() const { return front == rear; }
    bool isFull()  const { return (rear + 1) % capacity == front; }
    int  size()    const { return (rear - front + capacity) % capacity; }
    bool enqueue(int v) {
        if (isFull()) return false;
        arr[rear] = v;
        rear = (rear + 1) % capacity;
        return true;
    }
    bool dequeue(int& out) {
        if (isEmpty()) return false;
        out = arr[front];
        front = (front + 1) % capacity;
        return true;
    }
};

4b. Queue — the four formulas and a real trace

The formulas — memorise these four:

TestSacrificed-slot versionCounter version
Emptyfront == rearcount == 0
Full(rear + 1) % capacity == frontcount == capacity
Advance rearrear = (rear + 1) % capacitysame
Advance frontfront = (front + 1) % capacitysame
Usable slotscapacity - 1capacity

Real trace, capacity = 5:

start             front=0 rear=0 size=0 empty=Y full=N
enq 10,20,30,40   front=0 rear=4 size=4 empty=N full=Y
enqueue(50) accepted? no
deq twice         front=2 rear=4 size=2 empty=N full=N
enq 50,60         front=2 rear=1 size=4 empty=N full=Y

Note rear wrapped from 4 to 1 — that is the whole point.

Why circular at all (the essay answer): a simple linear array queue suffers false overflow. After dequeues, front moves forward and leaves free slots at the start, but once rear reaches MAX - 1 the queue refuses new elements even though the array is mostly empty. Fixing it by shifting everything down is O(n) per dequeue. A circular queue wraps the indices with % capacity and reuses those slots in O(1).

Costliest mistake: if you do not sacrifice a slot and do not keep a counter, front == rear means both "empty" and "full" and the two states are indistinguishable.

5. Tree / BST — terminology

Get these exact.

TermDefinition
Degree of a nodenumber of children
Degree of the treemaximum degree of any node
Leaf / external nodedegree 0
Internal nodedegree ≥ 1
Level of root0 (some texts use 1 — state your convention)
Depth of a nodeedges from root down to that node; root depth = 0
Height of a nodeedges on longest path from that node down to a leaf; leaf height = 0
Height of the treeheight of the root = maximum depth of any node
Empty treeheight -1 (edge convention)
Max nodes at level L2^L
Max nodes in tree of height h2^(h+1) − 1

Depth counts downward from the root, height counts upward from the leaves. They are equal only at the root.

cpp
struct TreeNode {
    int data;
    TreeNode* left;
    TreeNode* right;
    TreeNode(int v) : data(v), left(nullptr), right(nullptr) {}
};

5. Tree / BST — operations

Insert, search, height, leaf count, the three depth-first traversals, and level order.

cpp
TreeNode* insertBST(TreeNode* root, int v) {
    if (root == nullptr) return new TreeNode(v);       // base case creates the node
    if (v < root->data) root->left  = insertBST(root->left, v);
    else                root->right = insertBST(root->right, v);
    return root;                                       // re-attach on the way back up
}

bool searchBST(TreeNode* root, int target) {
    if (root == nullptr) return false;
    if (root->data == target) return true;
    if (target < root->data) return searchBST(root->left, target);
    return searchBST(root->right, target);
}

int height(TreeNode* r) {                    // edge convention
    if (r == nullptr) return -1;
    return 1 + max(height(r->left), height(r->right));
}

int countLeaves(TreeNode* r) {
    if (r == nullptr) return 0;
    if (r->left == nullptr && r->right == nullptr) return 1;
    return countLeaves(r->left) + countLeaves(r->right);
}

void inorder(TreeNode* r)  { if (!r) return; inorder(r->left);  cout << r->data << " "; inorder(r->right); }
void preorder(TreeNode* r) { if (!r) return; cout << r->data << " "; preorder(r->left); preorder(r->right); }
void postorder(TreeNode* r){ if (!r) return; postorder(r->left); postorder(r->right); cout << r->data << " "; }

void levelOrder(TreeNode* root) {            // uses a QUEUE, not recursion
    if (!root) return;
    queue<TreeNode*> q; q.push(root);
    while (!q.empty()) {
        TreeNode* cur = q.front(); q.pop();
        cout << cur->data << " ";
        if (cur->left)  q.push(cur->left);
        if (cur->right) q.push(cur->right);
    }
}

5. Tree / BST — insert rule, real output, complexity

BST insert rule: value < node->data → go left, otherwise go right. New nodes always become leaves; nothing already in the tree moves.

Inserting 50, 30, 70, 20, 40, 60, 80, 35 gives this real output:

inorder:    20 30 35 40 50 60 70 80
preorder:   50 30 20 40 35 70 60 80
postorder:  20 35 40 30 60 80 70 50
levelorder: 50 30 70 20 40 60 80 35
height(edges)=3  leaves=4  search(40)=found  search(45)=not found
OperationBalanced BSTDegenerate (sorted input)
Search / insert / deleteO(log n)O(n)
Any traversalO(n)O(n)

The check that catches errors: in-order on a BST must come out in ascending order. If it does not, your tree is wrong — redo it before answering anything else.

Costliest mistake: mixing up height and depth, and off-by-one from the two conventions. Say which one you are using ("height in edges, empty tree = −1") and stay consistent across the whole answer.

6. Graph — representation

RepresentationSpaceEdge exists?List neighbours
Adjacency matrixO(V²)O(1)O(V)
Adjacency listO(V + E)O(deg v)O(deg v)
cpp
// adjacency list — preferred for sparse graphs
vector<vector<int>> adj(V);
void addEdgeUndirected(vector<vector<int>>& adj, int u, int w) {
    adj[u].push_back(w);
    adj[w].push_back(u);        // BOTH directions
}
void addArcDirected(vector<vector<int>>& adj, int u, int w) {
    adj[u].push_back(w);        // one direction only
}

6. Graph — degree

GraphRule
Undirecteddegree(v) = edges touching v; a self-loop counts 2; Σ degrees = 2E
Directedin-degree = arcs arriving; out-degree = arcs leaving; total = in + out
Directed sumΣ in-degrees = Σ out-degrees = E

For the directed graph A→B, A→C, C→B, B→D, C→D, C→E, D→E (7 arcs):

VertexInOutTotal
A022
B213
C134
D213
E202
Σ7714

Use Σin = Σout = E as your instant self-check.

6. Graph — breadth-first traversal

Breadth-first traversal is driven by a queue.

cpp
vector<bool> visited(V, false);
queue<int> q;
visited[start] = true;      // mark WHEN ENQUEUEING
q.push(start);
while (!q.empty()) {
    int u = q.front(); q.pop();
    cout << u << " ";
    for (int w : adj[u]) {
        if (!visited[w]) { visited[w] = true; q.push(w); }
    }
}

6. Graph — the BFT marking trap

Costliest mistake: marking a vertex visited when you dequeue it instead of when you enqueue it. A vertex with two unvisited-at-the-time neighbours then gets pushed twice and appears twice in the output.

DFS is the same code with a stack instead of a queue (or recursion).

6. Graph — Dijkstra

Relaxation step — the one line the whole question turns on:

if dist[u] + weight(u, v) < dist[v] then dist[v] = dist[u] + weight(u, v) and parent[v] = u

Loop: pick the unsettled vertex with the smallest dist, mark it settled (its distance is now final), relax all its outgoing edges, repeat.

cpp
vector<int> dist(V, INT_MAX), parent(V, -1);
vector<bool> done(V, false);
dist[src] = 0;
for (int it = 0; it < V; it++) {
    int u = -1;
    for (int i = 0; i < V; i++)
        if (!done[i] && dist[i] != INT_MAX && (u == -1 || dist[i] < dist[u])) u = i;
    if (u == -1) break;
    done[u] = true;
    for (auto& e : adj[u]) {                 // e = {neighbour, weight}
        int v = e.first, w = e.second;
        if (!done[v] && dist[u] + w < dist[v]) { dist[v] = dist[u] + w; parent[v] = u; }
    }
}

6. Graph — Dijkstra complexity and limits

VersionComplexity
Array scan for the minimumO(V²)
Binary heap / priority queueO((V + E) log V)

Costliest mistake: using Dijkstra on a graph with a negative edge weight. Once a vertex is settled its distance is never revisited, so a negative edge can produce a wrong answer. Say "Bellman-Ford" if negative weights appear.

6. Graph — Kruskal (MST)

  1. Sort all edges by weight, ascending.
  2. Take the next cheapest edge; keep it only if it joins two different components (no cycle).
  3. Stop at V − 1 edges.

Cycle detection with union-find. Contrast: Prim's grows one tree from a start vertex (vertex-based); Kruskal's picks globally cheapest edges (edge-based). Both give the same total weight.

7. Sorting

Bubble, selection and insertion, written the way they are marked.

cpp
void bubbleSort(int a[], int n) {
    for (int i = 0; i < n - 1; i++) {
        bool swapped = false;
        for (int j = 0; j < n - 1 - i; j++)
            if (a[j] > a[j + 1]) { swap(a[j], a[j + 1]); swapped = true; }
        if (!swapped) break;                 // makes best case O(n)
    }
}

void selectionSort(int a[], int n) {
    for (int i = 0; i < n - 1; i++) {
        int minIdx = i;
        for (int j = i + 1; j < n; j++) if (a[j] < a[minIdx]) minIdx = j;
        if (minIdx != i) swap(a[i], a[minIdx]);
    }
}

void insertionSort(int a[], int n) {
    for (int i = 1; i < n; i++) {
        int key = a[i], j = i - 1;
        while (j >= 0 && a[j] > key) { a[j + 1] = a[j]; j--; }
        a[j + 1] = key;
    }
}

7. Sorting — comparison table and a real trace

AlgorithmBestAverageWorstSpaceStable
Bubble (with flag)O(n)O(n²)O(n²)O(1)Yes
SelectionO(n²)O(n²)O(n²)O(1)No
InsertionO(n)O(n²)O(n²)O(1)Yes
MergeO(n log n)O(n log n)O(n log n)O(n)Yes
QuickO(n log n)O(n log n)O(n²)O(log n)No

Real bubble-sort passes on 5 1 4 2 8:

pass 1: 1 4 2 5 8
pass 2: 1 2 4 5 8
pass 3: 1 2 4 5 8      (no swap -> stop early)

Costliest mistake: the inner bound. It is j < n - 1 - i, not j < n - 1. Without - i you re-compare the already-sorted tail; with j <= n - 1 you read a[n] off the end.

8. Searching

Binary search, in the form that survives marking.

cpp
int binarySearch(const int a[], int n, int target) {
    int low = 0, high = n - 1;
    while (low <= high) {                       // <=, not <
        int mid = low + (high - low) / 2;       // overflow-safe form
        if (a[mid] == target) return mid;
        else if (a[mid] < target) low  = mid + 1;
        else                      high = mid - 1;
    }
    return -1;
}

8. Searching — comparison and the loop-bound trap

AlgorithmBestWorstRequires
Linear searchO(1)O(n)nothing
Binary searchO(1)O(log n)sorted array, random access

On 2 5 8 12 16 23 38 56 72 91 the compiled program prints: index of 23 = 5, index of 24 = -1, linear index of 56 = 7.

Costliest mistake: while (low < high) or low = mid / high = mid. The first misses the last candidate; the second loops forever when low and high are adjacent.

Worked trace A — infix to postfix: P - Q / (R + S) * T

Rules: operand → output. ( → push. ) → pop to output until (, discard (. Operator → while the stack top is an operator of higher or equal precedence, pop it to output; then push. End → pop everything.

ReadActionStack (bottom→top)Output
PoperandP
-stack empty, push-P
Qoperand-PQ
/- is lower, push-/PQ
(push-/(PQ
Roperand-/(PQR
+top is (, push-/(+PQR
Soperand-/(+PQRS
)pop +, discard (-/PQRS+
*/ equal precedence → pop it; - lower → push *-*PQRS+/
Toperand-*PQRS+/T
endpop *, then -PQRS+/T*-

Answer: PQRS+/T*- (verified by the compiled program).

Two traps: ( is never sent to the output, and equal precedence pops (left-associative), which is what makes / come out before *.

Worked trace B — BFT queue state, start = 0

Undirected graph: 0–1, 0–2, 1–3, 1–4, 2–4, 3–5, 4–5.

StepDequeueNewly visited (enqueued)Queue after (front→back)
000
101, 21 2
213, 42 3 4
32— (4 already visited)3 4
4354 5
545
65empty

Visit order: 0 1 2 3 4 5. Step 3 is the mark: 2's neighbour 4 was already marked at step 2, so it is not enqueued again.

Worked trace C — Dijkstra table, source A

Directed, weighted: A→B 4, A→C 2, C→B 1, B→D 5, C→D 7, C→E 10, D→E 3.

SettleABCDEWhat changed
init0
A (0)042B←4 via A, C←2 via A
C (2)032912B improved 4→3 via C, D←9, E←12
B (3)032812D improved 9→8 via B
D (8)032811E improved 12→11 via D
E (11)032811nothing left

Shortest path A→E = A→C→B→D→E, cost 11 (2+1+5+3). All values are real program output.

The teaching point is row 2: the direct arc A→B cost 4, but going through C cost 3. Always pick the smallest unsettled distance next, and always re-check whether a shorter route just appeared.

If you only have twenty minutes

  1. Circular queue tests. Empty front == rear; full (rear + 1) % capacity == front; advance with % capacity; capacity − 1 usable.
  2. Postfix operand order. First pop = right operand. Second pop = left.
  3. Height vs depth. Depth counts down from the root (root = 0); height counts up from the leaves (leaf = 0, empty = −1).
  4. BST insert rule. < goes left, otherwise right; new nodes are always leaves; in-order must come out ascending.
  5. Mark visited on enqueue in BFT, never on dequeue.
  6. Dijkstra relaxation. if (dist[u] + w < dist[v]) dist[v] = dist[u] + w; and settle the smallest unsettled vertex each round.
  7. Reset rear = nullptr when a linked queue empties.
  8. head = prev; at the end of an in-place list reverse.
  9. Degree checks. Undirected Σdeg = 2E; directed Σin = Σout = E.
  10. Complexities worth memorising cold. Array access O(1); list search O(n); stack/queue ops O(1); balanced BST O(log n) but degenerate O(n); binary search O(log n) and it needs a sorted array; bubble/selection/insertion O(n²), merge O(n log n).
  11. Precedence. ^ > * / > + -; ( is never output; equal precedence pops.
  12. Write the struct first. In a code question the node declaration and the null checks are marks on their own — put them down even if the algorithm body stalls.

Good luck. Sleep beats one more re-read.

Which C++ standard does this page assume?

Standard C++17. C++ began as C with Classes in 1979 and first shipped commercially in 1985; what a compiler flag actually selects today is one of the ISO revisions below, and the committee has published a new one every 3 years since 2011. Build these listings with -std=c++17 and they compile as written.

C++ ISO revisions, their publication identifiers, and what each one changes for the code on this page
RevisionPublished asWhat it changes for the code on this page
C++98ISO/IEC 14882:1998The first ISO C++, and the dialect most data-structures courses still teach from: templates, the STL containers, and raw pointers doing the work.
C++03ISO/IEC 14882:2003A defect-fix revision. Nothing on these pages depends on it, and nothing on these pages is broken by it.
C++11ISO/IEC 14882:2011Where nullptr, auto, range-based for, move semantics and the unordered containers arrive. Every listing here writes nullptr rather than NULL because of it.
C++14ISO/IEC 14882:2014A small revision: generic lambdas and std::make_unique. Used only where it makes ownership clearer.
C++17ISO/IEC 14882:2017What every listing on StuHub targets and was compiled against. If you build these files, build them with -std=c++17.
C++20ISO/IEC 14882:2020Concepts, ranges and std::midpoint. Flagged in the prose where it offers a shorter correct form, never assumed by the code.
C++23ISO/IEC 14882:2024Not used here. Named so you can tell whether a snippet you found elsewhere will compile on a lab machine that predates it.

Common questions

What does this page cover?

Night-before reference for a data structures exam: declarations, complexity tables, the rule that decides each answer, and worked traces in C++.

How long does this page take to work through?

About 13 minutes of reading at 200 words per minute, plus 0 questions with worked solutions at the foot of the page. Reading it end to end is the slow way; the intended use is to find the section you are stuck on, then do the questions for that section with the solutions covered.

Which C++ standard do these examples target?

Standard C++17 — ISO/IEC 14882:2017. Every listing was compiled with -std=c++17 and -Wall -Wextra before publication, and the linked-structure examples were also run under AddressSanitizer. Where C++20 offers a shorter correct form, such as std::midpoint, the prose says so instead of quietly using it.

Is StuHub free, and do I need an account?

It is free and there is nothing to sign in to. No login, account or payment is required to read any of the 20 topics — StuHub is published by DUOCODE TECHNOLOGY alongside APRide, and the ride board's accounts have nothing to do with it.

Can I paste this code into my assignment?

Treat it as a reference, not as an answer key. StuHub is educational material only, it is not coursework and it is not affiliated with or endorsed by any institution, so your own submission rules decide what you may reuse. Every listing was compiled and run before publication, and you should still compile and test anything you take.

Why is everything written in C++ rather than pseudocode?

Because most of the mistakes worth catching are C++ mistakes, not algorithm mistakes: a lost pointer, a destructor that never runs, an index that underflows because it was unsigned. Pseudocode hides exactly the layer where a data-structures assignment is actually failed.

Where should I check what the standard library really guarantees?

cppreference for the day-to-day answer, and the WG21 working drafts when the exact wording matters — both are linked below. Compiler documentation settles the rest: a warning you cannot explain is usually the compiler being right.

Does StuHub replace my lecture notes?

No. It is written to sit beside them: your course decides what is examinable, in what notation, and with which library restrictions. Where this page and your module handbook disagree about scope, the handbook wins.

Is StuHub connected to Asia Pacific University?

No. It is an independent reference published by DUOCODE TECHNOLOGY, not affiliated with or endorsed by Asia Pacific University or any other institution. It was written for APU students because that is who asked for it, and it is open to anyone.

Where can I check this against the language itself?

Nothing on this page outranks the standard or the library reference. When this page and one of these disagree, they are right and we want to know.

Published 2026-08-09 · updated 2026-08-27 · DUOCODE TECHNOLOGY