Data Structures Overview: Array, Stack, Queue, Heap, Tree & Graph
This article is a compact overview of the most important data structures – with performance analysis, use cases, and Big-O notation.
In a Nutshell
The core data structures Array, Stack, Queue, Heap, Tree, and Graph form the foundation of efficient algorithms. They differ in access time, memory requirements, and applications.
Compact Technical Description
Data structures organize data for efficient access and processing. Each structure has specific properties and optimal areas of application.
Performance Overview:
Array
- Access: O(1) - Direct via index
- Insert/Delete: O(n) - Elements must be shifted
- Memory: O(n) - Contiguous memory
- Use: Fixed size, frequent random access
Stack (LIFO)
- Push/Pop: O(1) - Only topmost element
- Memory: O(n) - Dynamic or fixed
- Use: Method calls, backtracking, parser
Queue (FIFO)
- Enqueue/Dequeue: O(1) - Front/Back
- Memory: O(n) - Circular queue possible
- Use: Wait queues, BFS, buffer
Heap (Priority Queue)
- Insert: O(log n) - Maintain heap property
- Extract Min/Max: O(log n) - Remove root
- Peek: O(1) - View minimum/maximum
- Use: Priorities, scheduling, heap sort
Tree (BST)
- Search: O(log n) - Balanced
- Insert/Delete: O(log n) - Balanced
- Memory: O(n) - Node-based
- Use: Ordered data, databases
Graph
- Traversal: O(V+E) - V=nodes, E=edges
- Memory: O(V+E) - Adjacency list
- Use: Networks, route planning
Exam-Relevant Key Points
- Array: O(1) access, fixed size, contiguous memory
- Stack: LIFO principle, push/pop O(1), call stack
- Queue: FIFO principle, enqueue/dequeue O(1), buffer
- Heap: Priority queue, O(log n) insert/extract, O(1) peek
- Tree: Hierarchical, O(log n) with balancing, BST
- Graph: Node-edge structure, BFS/DFS O(V+E)
- Big-O Notation: Time and space complexity
- IHK-relevant: Foundation for algorithms and efficiency
Core Components
- Array: Indexed collection with direct access
- Stack: LIFO data structure for backtracking
- Queue: FIFO data structure for wait queues
- Heap: Priority-based tree structure
- Tree: Hierarchical parent-child relationships
- Graph: Network of nodes and edges
- Performance: Big-O analysis of operations
- Application: Optimal structure selection
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 fill time: " + arrayZeit / 1_000_000 + " ms");
System.out.println("ArrayList fill time: " + 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 for bracket checking (LIFO)
String ausdruck = "{[()()]}";
if (pruefeKlammer(ausdruck)) {
System.out.println("Expression '" + ausdruck + "' is valid");
}
// Queue for printer queue (FIFO)
Queue<String> druckerQueue = new LinkedList<>();
druckerQueue.add("Dokument1.pdf");
druckerQueue.add("Dokument2.pdf");
druckerQueue.add("Dokument3.pdf");
System.out.println("Printer queue:");
while (!druckerQueue.isEmpty()) {
String dokument = druckerQueue.remove();
System.out.println("Print: " + dokument);
}
// Performance comparison
performanceVergleich();
}
// Bracket checking with 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 comparison (" + OPERATIONEN + " operations):");
System.out.println("Stack time: " + stackZeit / 1_000_000 + " ms");
System.out.println("Queue time: " + queueZeit / 1_000_000 + " ms");
}
}
3. Priority Queue (Heap) Application
import java.util.*;
public class PriorityQueueDemo {
public static void main(String[] args) {
// Min-heap for ascending sort
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 with 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 with priorities
taskSchedulingDemo();
}
private static void taskSchedulingDemo() {
// Task with priority
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 for tasks
PriorityQueue<Task> taskQueue = new PriorityQueue<>(
(t1, t2) -> Integer.compare(t2.priority, t1.priority) // Max-heap
);
taskQueue.add(new Task("Send email", 2));
taskQueue.add(new Task("Database backup", 5));
taskQueue.add(new Task("Analyze logs", 1));
taskQueue.add(new Task("Security update", 10));
System.out.println("\nTask scheduling (high priority first):");
while (!taskQueue.isEmpty()) {
System.out.println("Execute: " + taskQueue.remove());
}
}
}
4. Search Tree vs Array Search
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) on average
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("Search 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
| Operation | Array | Stack | Queue | Heap | BST | Graph |
|---|---|---|---|---|---|---|
| Access | O(1) | O(n) | O(n) | O(1) | O(log n) | O(V+E) |
| Search | O(n) | O(n) | O(n) | O(n) | O(log n) | O(V+E) |
| Insert | O(n) | O(1) | O(1) | O(log n) | O(log n) | O(1) |
| Delete | O(n) | O(1) | O(1) | O(log n) | O(log n) | O(V+E) |
Space Complexity
| Data Structure | Memory | Note |
|---|---|---|
| Array | O(n) | Contiguous |
| Stack | O(n) | Dynamic |
| Queue | O(n) | Circular possible |
| Heap | O(n) | Tree structure |
| BST | O(n) | Node-based |
| Graph | O(V+E) | V=nodes, E=edges |
Use Case Scenarios
Which data structure when?
Use Array when:
- Size is known and constant
- Frequent random access needed
- Memory space critical (contiguous)
Use Stack when:
- LIFO behavior needed
- Backtracking states
- Method calls of recursive functions
Use Queue when:
- FIFO behavior needed
- Implementing waiting lines
- Breadth-First Search
Use Heap when:
- Priorities are important
- Frequent access to minimum/maximum
- Scheduling algorithms
Use Tree when:
- Storing ordered data
- Efficient search required
- Hierarchical relationships
Use Graph when:
- Modeling network relationships
- Route planning required
- Many-to-many relationships
Performance Tips
Array Optimization
// Prepare loop
int[] array = new int[1000];
int laenge = array.length; // Not in every 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 Exam Questions
-
Which data structure for printer queue? Queue (FIFO) - first request is processed first.
-
Why is BST search faster than array search? BST: O(log n) by halving, Array: O(n) by linear search.
-
What is the difference between Stack and Queue? Stack: LIFO (Last-In, First-Out), Queue: FIFO (First-In, First-Out).
-
When do you use Heap instead of Array? When frequent access to minimum/maximum is needed (Priority Queue).
Most Important Sources
- https://de.wikipedia.org/wiki/Datenstruktur
- https://www.geeksforgeeks.org/data-structures/
- https://docs.oracle.com/javase/tutorial/collections/