Skip to content
IRC-Coding IRC-Coding
Datenstrukturen Array Stack Queue Heap Baum Graph Performance Analyse Big O Notation

Datenstrukturen Übersicht: Array, Stack, Queue, Heap, Baum & Graph

Übersicht wichtiger Datenstrukturen mit Performance-Analyse. Array O(1) Zugriff, Stack/Queue LIFO/FIFO, Heap Priority Queue, Baum O(log n), Graph Traversierung.

S

schutzgeist

2 min read

Datenstrukturen Übersicht: Array, Stack, Queue, Heap, Baum & Graph

Dieser Beitrag ist eine kompakte Übersicht der wichtigsten Datenstrukturen – mit Performance-Analyse, Einsatzgebieten und Big-O Notation.

In a Nutshell

Die Kern-Datenstrukturen Array, Stack, Queue, Heap, Baum und Graph bilden die Grundlage effizienter Algorithmen. Sie unterscheiden sich in Zugriffszeit, Speicherbedarf und Anwendungsgebieten.

Kompakte Fachbeschreibung

Datenstrukturen organisieren Daten für effizienten Zugriff und Verarbeitung. Jede Struktur hat spezifische Eigenschaften und optimale Einsatzgebiete.

Performance-Übersicht:

Array

  • Zugriff: O(1) - Direkt per Index
  • Einfügen/Löschen: O(n) - Elemente müssen verschoben werden
  • Speicher: O(n) - Zusammenhängender Speicher
  • Einsatz: Feste Größe, häufiger Random Access

Stack (LIFO)

  • Push/Pop: O(1) - Nur oberstes Element
  • Speicher: O(n) - Dynamisch oder fix
  • Einsatz: Methodenaufrufe, Backtracking, Parser

Queue (FIFO)

  • Enqueue/Dequeue: O(1) - Vorne/Hinten
  • Speicher: O(n) - Circular Queue möglich
  • Einsatz: Warteschlangen, BFS, Buffer

Heap (Priority Queue)

  • Insert: O(log n) - Heap-Eigenschaft erhalten
  • Extract Min/Max: O(log n) - Wurzel entfernen
  • Peek: O(1) - Minimum/Maximum ansehen
  • Einsatz: Prioritäten, Scheduling, Heap Sort

Baum (BST)

  • Suchen: O(log n) - Balanciert
  • Einfügen/Löschen: O(log n) - Balanciert
  • Speicher: O(n) - Knotenbasiert
  • Einsatz: Geordnete Daten, Datenbanken

Graph

  • Traversierung: O(V+E) - V=Knoten, E=Kanten
  • Speicher: O(V+E) - Adjazenzliste
  • Einsatz: Netzwerke, Routenplanung

Prüfungsrelevante Stichpunkte

  • Array: O(1) Zugriff, feste Größe, zusammenhängender Speicher
  • Stack: LIFO-Prinzip, push/pop O(1), Call Stack
  • Queue: FIFO-Prinzip, enqueue/dequeue O(1), Buffer
  • Heap: Priority Queue, O(log n) insert/extract, O(1) peek
  • Baum: Hierarchisch, O(log n) bei Balancierung, BST
  • Graph: Knoten-Kanten-Struktur, BFS/DFS O(V+E)
  • Big-O Notation: Zeit- und Speicherkomplexität
  • IHK-relevant: Fundament für Algorithmen und Effizienz

Kernkomponenten

  1. Array: Indizierte Sammlung mit direktem Zugriff
  2. Stack: LIFO-Datenstruktur für Rückverfolgung
  3. Queue: FIFO-Datenstruktur für Warteschlangen
  4. Heap: Prioritätenbasierte Baumstruktur
  5. Baum: Hierarchische Eltern-Kind-Beziehungen
  6. Graph: Netzwerk aus Knoten und Kanten
  7. Performance: Big-O Analyse von Operationen
  8. Anwendung: Optimale Strukturauswahl

Praxisbeispiele

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 Anwendung

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) Anwendung

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());
        }
    }
}

4. Suchbaum vs Array Suche

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();
        
        // BST aufbauen
        BinarySearchTree bst = new BinarySearchTree();
        for (int i = 0; i < GROESSE; i++) {
            bst.insert(random.nextInt(GROESSE * 10));
        }
        
        // Array erstellen
        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 Suche O(log n) im Durchschnitt
        long start = System.nanoTime();
        boolean bstGefunden = bst.search(suchZahl);
        long bstZeit = System.nanoTime() - start;
        
        // Array Suche O(n) im Worst-Case
        start = System.nanoTime();
        boolean arrayGefunden = array.contains(suchZahl);
        long arrayZeit = System.nanoTime() - start;
        
        System.out.println("Suche nach " + suchZahl + ":");
        System.out.println("BST Zeit: " + bstZeit / 1000 + " μs, gefunden: " + bstGefunden);
        System.out.println("Array Zeit: " + arrayZeit / 1000 + " μs, gefunden: " + arrayGefunden);
        
        // Sortierter 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 Zeit: " + binaryZeit / 1000 + " μs, Index: " + index);
    }
}

5. Graph Traversierung Vergleich

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/Rekursion)
    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();
        
        // Graph aufbauen
        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 Traversierung:");
        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 Vergleichstabelle

Zeitkomplexität

OperationArrayStackQueueHeapBSTGraph
ZugriffO(1)O(n)O(n)O(1)O(log n)O(V+E)
SuchenO(n)O(n)O(n)O(n)O(log n)O(V+E)
EinfügenO(n)O(1)O(1)O(log n)O(log n)O(1)
LöschenO(n)O(1)O(1)O(log n)O(log n)O(V+E)

Speicherkomplexität

DatenstrukturSpeicherBemerkung
ArrayO(n)Zusammenhängend
StackO(n)Dynamisch
QueueO(n)Circular möglich
HeapO(n)Baumstruktur
BSTO(n)Knotenbasiert
GraphO(V+E)V=Knoten, E=Kanten

Anwendungsszenarien

Wann welche Datenstruktur?

Array verwenden wenn:

  • Größe bekannt und konstant
  • Häufiger Random Access benötigt
  • Speicherplatz kritisch (zusammenhängend)

Stack verwenden wenn:

  • LIFO-Verhalten benötigt
  • Rückverfolgung von Zuständen
  • Methodenaufrufe rekursiver Funktionen

Queue verwenden wenn:

  • FIFO-Verhalten benötigt
  • Warteschlangen realisieren
  • Breadth-First Search

Heap verwenden wenn:

  • Prioritäten wichtig
  • Häufig auf Minimum/Maximum zugegriffen
  • Scheduling-Algorithmen

Baum verwenden wenn:

  • Geordnete Daten gespeichert werden
  • Effiziente Suche erforderlich
  • Hierarchische Beziehungen

Graph verwenden wenn:

  • Netzwerkbeziehungen modelliert
  • Routenplanung erforderlich
  • Viele-zu-viele Beziehungen

Performance-Tipps

Array-Optimierung

// Schleife vorbereiten
int[] array = new int[1000];
int laenge = array.length; // Nicht in jeder Iteration

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

Stack-Optimierung

// Array statt Stack für feste Größe
int[] stack = new int[1000];
int top = -1;

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

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

Queue-Optimierung

// Circular Queue für feste Größe
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];
}

Häufige Prüfungsfragen

  1. Welche Datenstruktur für Druckerwarteschlange? Queue (FIFO) - erste Anfrage wird zuerst bearbeitet.

  2. Warum ist BST-Suche schneller als Array-Suche? BST: O(log n) durch Halbierung, Array: O(n) durch lineare Suche.

  3. Was ist der Unterschied zwischen Stack und Queue? Stack: LIFO (Last-In, First-Out), Queue: FIFO (First-In, First-Out).

  4. Wann verwendet man Heap statt Array? Wenn häufig auf Minimum/Maximum zugegriffen werden muss (Priority Queue).

Wichtigste Quellen

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

Ähnliche Beiträge