Skip to content
IRC-CodingIRC-Coding
Data StructuresArray Stack QueueHeap Tree GraphPerformance AnalysisBig O NotationAlgorithmsFundamentalsDatabase

Data Structures Overview: Array, Stack, Queue, Heap, Tree & Graph

Essential data structures guide with performance analysis. Array O(1) access, Stack/Queue LIFO/FIFO, Heap priority queue, Tree O(log n), Graph traversal.

S

schutzgeist

8 min read
Data Structures Overview: Array, Stack, Queue, Heap, Tree & Graph

Data Structures Overview: Arrays, Stacks, Queues, Heaps, Trees & Graphs

This article provides a concise overview of core data structures — complete with performance analysis, use cases, and Big-O notation.

In a Nutshell

Arrays, stacks, queues, heaps, trees, and graphs form the foundation of efficient algorithms. Each differs in access time, memory usage, and practical applications.

Core Concepts

Data structures organize information for fast access and processing. Every structure has distinct properties and optimal use cases.

Performance summary:

Array

  • Access: O(1) — direct lookup by index
  • Insert/Delete: O(n) — elements must be shifted
  • Memory: O(n) — contiguous block
  • Use case: Fixed size, frequent random access

Stack (LIFO)

  • Push/Pop: O(1) — only affects top element
  • Memory: O(n) — dynamic or static
  • Use case: Function calls, backtracking, parsing

Queue (FIFO)

  • Enqueue/Dequeue: O(1) — front and rear operations
  • Memory: O(n) — circular implementation possible
  • Use case: Buffering, BFS, task queues

Heap (Priority Queue)

  • Insert: O(log n) — maintains heap property
  • Extract Min/Max: O(log n) — remove root
  • Peek: O(1) — view minimum or maximum
  • Use case: Priority scheduling, sorting, event handling

Tree (BST)

  • Search: O(log n) — when balanced
  • Insert/Delete: O(log n) — when balanced
  • Memory: O(n) — node-based
  • Use case: Ordered data, database indexing

Graph

  • Traversal: O(V+E) — V=vertices, E=edges
  • Memory: O(V+E) — adjacency list representation
  • Use case: Networks, pathfinding, routing

Key Takeaways

  • Array: O(1) access, fixed size, contiguous memory
  • Stack: LIFO principle, O(1) push/pop, call stack
  • Queue: FIFO principle, O(1) enqueue/dequeue, buffering
  • Heap: Priority-based, O(log n) insert/extract, O(1) peek
  • Tree: Hierarchical structure, O(log n) when balanced, BST
  • Graph: Nodes and edges, BFS/DFS O(V+E)
  • Big-O Notation: Time and space complexity analysis
  • Practical skill: Choosing the right structure for the problem

Core Components

  1. Array: Indexed collection with direct access
  2. Stack: LIFO structure for backtracking scenarios
  3. Queue: FIFO structure for sequential processing
  4. Heap: Priority-based tree structure
  5. Tree: Hierarchical parent-child relationships
  6. Graph: Network of connected nodes
  7. Performance: Big-O analysis of operations
  8. Application: Selecting the right structure

Practical Examples

1. Array vs ArrayList Performance

import java.util.*;

public class ArrayPerformance {
    public static void main(String[] args) {
        final int GROESSE = 100_000;
        
        // Array (feste Größe)
        long start = System.nanoTime();
        int[] array = new int[GROESSE];
        for (int i = 0; i < GROESSE; i++) {
            array[i] = i; // O(1) Schreiben
        }
        long arrayZeit = System.nanoTime() - start;
        
        // ArrayList (dynamisch)
        start = System.nanoTime();
        List<Integer> arrayList = new ArrayList<>();
        for (int i = 0; i < GROESSE; i++) {
            arrayList.add(i); // O(1) amortisiert
        }
        long arrayListZeit = System.nanoTime() - start;
        
        // Random Access Test
        Random random = new Random();
        
        start = System.nanoTime();
        for (int i = 0; i < 1000; i++) {
            int index = random.nextInt(GROESSE);
            int wert = array[index]; // O(1)
        }
        long arrayAccess = System.nanoTime() - start;
        
        start = System.nanoTime();
        for (int i = 0; i < 1000; i++) {
            int index = random.nextInt(GROESSE);
            int wert = arrayList.get(index); // O(1)
        }
        long arrayListAccess = System.nanoTime() - start;
        
        System.out.println("Array Füllzeit: " + arrayZeit / 1_000_000 + " ms");
        System.out.println("ArrayList Füllzeit: " + arrayListZeit / 1_000_000 + " ms");
        System.out.println("Array Access: " + arrayAccess / 1000 + " μs");
        System.out.println("ArrayList Access: " + arrayListAccess / 1000 + " μs");
    }
}

2. Stack vs Queue Application

import java.util.*;

public class StackQueueDemo {
    public static void main(String[] args) {
        // Stack für Klammerprüfung (LIFO)
        String ausdruck = "{[()()]}";
        if (pruefeKlammer(ausdruck)) {
            System.out.println("Ausdruck '" + ausdruck + "' ist gültig");
        }
        
        // Queue für Druckerwarteschlange (FIFO)
        Queue<String> druckerQueue = new LinkedList<>();
        druckerQueue.add("Dokument1.pdf");
        druckerQueue.add("Dokument2.pdf");
        druckerQueue.add("Dokument3.pdf");
        
        System.out.println("Druckerwarteschlange:");
        while (!druckerQueue.isEmpty()) {
            String dokument = druckerQueue.remove();
            System.out.println("Drucke: " + dokument);
        }
        
        // Performance Vergleich
        performanceVergleich();
    }
    
    // Klammerprüfung mit Stack
    private static boolean pruefeKlammer(String ausdruck) {
        Stack<Character> stack = new Stack<>();
        
        for (char zeichen : ausdruck.toCharArray()) {
            switch (zeichen) {
                case '(': case '[': case '{':
                    stack.push(zeichen);
                    break;
                case ')':
                    if (stack.isEmpty() || stack.pop() != '(') return false;
                    break;
                case ']':
                    if (stack.isEmpty() || stack.pop() != '[') return false;
                    break;
                case '}':
                    if (stack.isEmpty() || stack.pop() != '{') return false;
                    break;
            }
        }
        return stack.isEmpty();
    }
    
    private static void performanceVergleich() {
        final int OPERATIONEN = 1_000_000;
        
        // Stack Performance
        long start = System.nanoTime();
        Stack<Integer> stack = new Stack<>();
        for (int i = 0; i < OPERATIONEN; i++) {
            stack.push(i);
        }
        for (int i = 0; i < OPERATIONEN; i++) {
            stack.pop();
        }
        long stackZeit = System.nanoTime() - start;
        
        // Queue Performance
        start = System.nanoTime();
        Queue<Integer> queue = new LinkedList<>();
        for (int i = 0; i < OPERATIONEN; i++) {
            queue.add(i);
        }
        for (int i = 0; i < OPERATIONEN; i++) {
            queue.remove();
        }
        long queueZeit = System.nanoTime() - start;
        
        System.out.println("\nPerformance Vergleich (" + OPERATIONEN + " Operationen):");
        System.out.println("Stack Zeit: " + stackZeit / 1_000_000 + " ms");
        System.out.println("Queue Zeit: " + queueZeit / 1_000_000 + " ms");
    }
}

3. Priority Queue (Heap) Application

import java.util.*;

public class PriorityQueueDemo {
    public static void main(String[] args) {
        // Min-Heap für aufsteigende Sortierung
        PriorityQueue<Integer> minHeap = new PriorityQueue<>();
        minHeap.add(30);
        minHeap.add(10);
        minHeap.add(20);
        minHeap.add(40);
        minHeap.add(5);
        
        System.out.println("Min-Heap (Priority Queue):");
        while (!minHeap.isEmpty()) {
            System.out.println("Extract Min: " + minHeap.remove());
        }
        
        // Max-Heap mit Comparator
        PriorityQueue<Integer> maxHeap = new PriorityQueue<>(Collections.reverseOrder());
        maxHeap.add(30);
        maxHeap.add(10);
        maxHeap.add(20);
        maxHeap.add(40);
        maxHeap.add(5);
        
        System.out.println("\nMax-Heap:");
        while (!maxHeap.isEmpty()) {
            System.out.println("Extract Max: " + maxHeap.remove());
        }
        
        // Task-Scheduling mit Prioritäten
        taskSchedulingDemo();
    }
    
    private static void taskSchedulingDemo() {
        // Task mit Priorität
        class Task {
            String name;
            int priority;
            
            Task(String name, int priority) {
                this.name = name;
                this.priority = priority;
            }
            
            @Override
            public String toString() {
                return name + " (Prio: " + priority + ")";
            }
        }
        
        // Priority Queue für Tasks
        PriorityQueue<Task> taskQueue = new PriorityQueue<>(
            (t1, t2) -> Integer.compare(t2.priority, t1.priority) // Max-Heap
        );
        
        taskQueue.add(new Task("Email senden", 2));
        taskQueue.add(new Task("Datenbank backup", 5));
        taskQueue.add(new Task("Log analysieren", 1));
        taskQueue.add(new Task("Security update", 10));
        
        System.out.println("\nTask-Scheduling (hohe Priorität zuerst):");
        while (!taskQueue.isEmpty()) {
            System.out.println("Ausführen: " + taskQueue.remove());
        }
    }
}
import java.util.*;

class TreeNode {
    int wert;
    TreeNode links, rechts;
    
    TreeNode(int wert) {
        this.wert = wert;
        this.links = this.rechts = null;
    }
}

class BinarySearchTree {
    TreeNode wurzel;
    
    void insert(int wert) {
        wurzel = insertRecursive(wurzel, wert);
    }
    
    private TreeNode insertRecursive(TreeNode knoten, int wert) {
        if (knoten == null) {
            return new TreeNode(wert);
        }
        
        if (wert < knoten.wert) {
            knoten.links = insertRecursive(knoten.links, wert);
        } else if (wert > knoten.wert) {
            knoten.rechts = insertRecursive(knoten.rechts, wert);
        }
        
        return knoten;
    }
    
    boolean search(int wert) {
        return searchRecursive(wurzel, wert);
    }
    
    private boolean searchRecursive(TreeNode knoten, int wert) {
        if (knoten == null) return false;
        if (knoten.wert == wert) return true;
        
        return wert < knoten.wert 
            ? searchRecursive(knoten.links, wert)
            : searchRecursive(knoten.rechts, wert);
    }
}

public class SuchbaumVsArray {
    public static void main(String[] args) {
        final int GROESSE = 100_000;
        Random random = new Random();
        
        // Build BST
        BinarySearchTree bst = new BinarySearchTree();
        for (int i = 0; i < GROESSE; i++) {
            bst.insert(random.nextInt(GROESSE * 10));
        }
        
        // Create array
        List<Integer> array = new ArrayList<>();
        for (int i = 0; i < GROESSE; i++) {
            array.add(random.nextInt(GROESSE * 10));
        }
        
        int suchZahl = random.nextInt(GROESSE * 10);
        
        // BST search O(log n) average case
        long start = System.nanoTime();
        boolean bstGefunden = bst.search(suchZahl);
        long bstZeit = System.nanoTime() - start;
        
        // Array search O(n) worst case
        start = System.nanoTime();
        boolean arrayGefunden = array.contains(suchZahl);
        long arrayZeit = System.nanoTime() - start;
        
        System.out.println("Searching for " + suchZahl + ":");
        System.out.println("BST time: " + bstZeit / 1000 + " μs, found: " + bstGefunden);
        System.out.println("Array time: " + arrayZeit / 1000 + " μs, found: " + arrayGefunden);
        
        // Sorted array (Binary Search)
        Collections.sort(array);
        start = System.nanoTime();
        int index = Collections.binarySearch(array, suchZahl);
        long binaryZeit = System.nanoTime() - start;
        
        System.out.println("Binary search time: " + binaryZeit / 1000 + " μs, index: " + index);
    }
}

5. Graph Traversal Comparison

import java.util.*;

class Graph {
    private Map<Integer, List<Integer>> adjazenzliste = new HashMap<>();
    
    void kanteHinzufuegen(int u, int v) {
        adjazenzliste.computeIfAbsent(u, k -> new ArrayList<>()).add(v);
        adjazenzliste.computeIfAbsent(v, k -> new ArrayList<>()).add(u);
    }
    
    // Breadth-First Search (Queue)
    void bfs(int start) {
        Set<Integer> besucht = new HashSet<>();
        Queue<Integer> queue = new LinkedList<>();
        
        queue.add(start);
        besucht.add(start);
        
        System.out.print("BFS: ");
        while (!queue.isEmpty()) {
            int knoten = queue.remove();
            System.out.print(knoten + " ");
            
            for (int nachbar : adjazenzliste.getOrDefault(knoten, Collections.emptyList())) {
                if (!besucht.contains(nachbar)) {
                    besucht.add(nachbar);
                    queue.add(nachbar);
                }
            }
        }
        System.out.println();
    }
    
    // Depth-First Search (Stack/Recursion)
    void dfs(int start) {
        Set<Integer> besucht = new HashSet<>();
        dfsRecursive(start, besucht);
    }
    
    private void dfsRecursive(int knoten, Set<Integer> besucht) {
        besucht.add(knoten);
        System.out.print(knoten + " ");
        
        for (int nachbar : adjazenzliste.getOrDefault(knoten, Collections.emptyList())) {
            if (!besucht.contains(nachbar)) {
                dfsRecursive(nachbar, besucht);
            }
        }
    }
}

public class GraphTraversal {
    public static void main(String[] args) {
        Graph graph = new Graph();
        
        // Build graph
        graph.kanteHinzufuegen(0, 1);
        graph.kanteHinzufuegen(0, 2);
        graph.kanteHinzufuegen(1, 3);
        graph.kanteHinzufuegen(2, 4);
        graph.kanteHinzufuegen(3, 4);
        graph.ketteHinzufuegen(4, 5);
        
        System.out.println("Graph traversal:");
        graph.bfs(0); // Level-order: 0 1 2 3 4 5
        graph.dfs(0); // Depth-first: 0 1 3 4 2 5
    }
}

Big-O Notation Comparison Table

Time Complexity

OperationArrayStackQueueHeapBSTGraph
AccessO(1)O(n)O(n)O(1)O(log n)O(V+E)
SearchO(n)O(n)O(n)O(n)O(log n)O(V+E)
InsertO(n)O(1)O(1)O(log n)O(log n)O(1)
DeleteO(n)O(1)O(1)O(log n)O(log n)O(V+E)

Space Complexity

Data StructureSpaceRemark
ArrayO(n)Contiguous
StackO(n)Dynamic
QueueO(n)Circular possible
HeapO(n)Tree structure
BSTO(n)Node-based
GraphO(V+E)V=vertices, E=edges

Use Case Scenarios

Choosing the right data structure

Use arrays when:

  • Size is known and constant
  • Random access is needed frequently
  • Memory space is critical (contiguous allocation)

Use stacks when:

  • LIFO behavior is required
  • Backtracking through states
  • Managing recursive function calls

Use queues when:

  • FIFO behavior is required
  • Implementing waiting lines
  • Breadth-First Search traversal

Use heaps when:

  • Priority ordering matters
  • Frequent access to minimum or maximum values
  • Implementing scheduling algorithms

Use trees when:

  • Storing ordered data
  • Efficient searching is needed
  • Modeling hierarchical relationships

Use graphs when:

  • Modeling network relationships
  • Path planning is involved
  • Many-to-many relationships exist

Performance Tips

Array optimization

// Prepare loop
int[] array = new int[1000];
int laenge = array.length; // Don't fetch on each iteration

for (int i = 0; i < laenge; i++) {
    array[i] = i;
}

Stack optimization

// Array instead of Stack for fixed size
int[] stack = new int[1000];
int top = -1;

void push(int wert) {
    stack[++top] = wert;
}

int pop() {
    return stack[top--];
}

Queue optimization

// Circular queue for fixed size
int[] queue = new int[1000];
int front = 0, rear = 0;

void enqueue(int wert) {
    queue[rear = (rear + 1) % queue.length] = wert;
}

int dequeue() {
    return queue[front = (front + 1) % queue.length];
}

Common Interview Questions

  1. Which data structure for a printer queue? Queue (FIFO) – the first request is processed first.

  2. Why is BST search faster than array search? BST: O(log n) through halving; array: O(n) through linear search.

  3. What’s the difference between a stack and a queue? Stack: LIFO (Last-In, First-Out); queue: FIFO (First-In, First-Out).

  4. When should you use a heap instead of an array? When frequent access to minimum or maximum values is required (Priority Queue).

Key Resources

  1. https://de.wikipedia.org/wiki/Datenstruktur
  2. https://www.geeksforgeeks.org/data-structures/
  3. https://docs.oracle.com/javase/tutorial/collections/
Back to Blog
Share:

Related Posts