So implementieren Sie einen Stapel in der C-Programmierung

Ein Stapel, oft auch als Stack bezeichnet, ist eine fundamentale Datenstruktur in der Informatik. Er stellt eine lineare Struktur dar, die nach dem Prinzip des „Last In, First Out“ (LIFO) funktioniert. Dies bedeutet, dass das Element, das zuletzt hinzugefügt wurde, auch als erstes wieder entfernt wird. Man kann sich dies wie einen Stapel von Tellern vorstellen.

In diesem Artikel werden wir die Implementierung eines Stapels in der Programmiersprache C detailliert untersuchen. Wir werden verschiedene Ansätze zur Realisierung eines Stacks in C betrachten und die grundlegenden Operationen erläutern, die auf einem Stapel ausgeführt werden können.

1. Einführung in den Begriff des Stacks

Ein Stack ist eine lineare Datenstruktur, die auf dem LIFO-Prinzip beruht. Neue Elemente werden immer am „oberen“ Ende des Stacks hinzugefügt (Push-Operation), während Elemente ebenfalls vom „oberen“ Ende entnommen werden (Pop-Operation).

Grundlegende Operationen eines Stacks:

  • Push: Fügt ein neues Element ganz oben auf dem Stapel ein.
  • Pop: Entfernt das oberste Element vom Stack und gibt es zurück.
  • Peek: Liest das oberste Element des Stacks aus, ohne es zu entfernen.
  • IsEmpty: Überprüft, ob der Stack leer ist.
  • IsFull: Überprüft, ob der Stack voll ist (relevant bei Array-Implementierungen).

Einsatzgebiete von Stacks:

Stacks finden in unterschiedlichen Anwendungsbereichen der Programmierung Verwendung, zum Beispiel:

  • Funktionsaufrufe: Der sogenannte Call-Stack verwaltet Informationen über Funktionsaufrufe und Rücksprungadressen.
  • Auswertung mathematischer Ausdrücke: Stacks werden zur Auswertung von Ausdrücken in Postfix-Notation (umgekehrte polnische Notation) verwendet.
  • Rückgängigmachen von Operationen: In Texteditoren oder Webbrowsern werden Stacks verwendet, um Undo/Redo-Funktionen zu realisieren.
  • Speicherverwaltung: In manchen Systemen zur Speicherverwaltung werden Stacks zur Verwaltung des Heaps eingesetzt.

2. Implementierung eines Stacks in C mit Arrays

Eine einfache Möglichkeit zur Implementierung eines Stacks in C besteht in der Nutzung eines Arrays. Hierbei deklarieren wir ein Array, das die Elemente des Stacks aufnimmt, und einen Index, der stets auf das oberste Element verweist.

Beispielcode:


#include <stdio.h>
#include <stdlib.h>

#define MAX_SIZE 100

typedef struct Stack {
    int items[MAX_SIZE];
    int top;
} Stack;

void push(Stack *stack, int data) {
    if (stack->top == MAX_SIZE - 1) {
        printf("Stack Overflow\n");
        exit(1);
    } else {
        stack->top++;
        stack->items[stack->top] = data;
    }
}

int pop(Stack *stack) {
    if (stack->top == -1) {
        printf("Stack Underflow\n");
        exit(1);
    } else {
        int data = stack->items[stack->top];
        stack->top--;
        return data;
    }
}

int peek(Stack *stack) {
    if (stack->top == -1) {
        printf("Stack is empty\n");
        return -1;
    } else {
        return stack->items[stack->top];
    }
}

int isEmpty(Stack *stack) {
    return stack->top == -1;
}

int isFull(Stack *stack) {
    return stack->top == MAX_SIZE - 1;
}

int main() {
    Stack *stack = malloc(sizeof(Stack));
    stack->top = -1;

    push(stack, 10);
    push(stack, 20);
    push(stack, 30);

    printf("Oberstes Element: %d\n", peek(stack));

    printf("Entferntes Element: %d\n", pop(stack));

    printf("Oberstes Element: %d\n", peek(stack));

    free(stack);

    return 0;
}

In diesem Code:

  • Definieren wir eine Struktur namens Stack, die ein Array items zum Speichern der Elemente und einen Index top enthält, der auf das oberste Element zeigt.
  • Die Funktion push() fügt ein neues Element zum Stack hinzu.
  • Die Funktion pop() entfernt das oberste Element vom Stack.
  • Die Funktion peek() liest das oberste Element des Stacks aus, ohne es zu entfernen.
  • Die Funktionen isEmpty() und isFull() prüfen, ob der Stack leer bzw. voll ist.

3. Implementierung eines Stacks in C mit verketteten Listen

Eine weitere Möglichkeit, einen Stack in C zu implementieren, ist die Verwendung einer verketteten Liste. Eine verkettete Liste ist eine Sequenz von Knoten, wobei jeder Knoten einen Verweis auf den nachfolgenden Knoten enthält.

Beispielcode:


#include <stdio.h>
#include <stdlib.h>

typedef struct Node {
    int data;
    struct Node *next;
} Node;

typedef struct Stack {
    struct Node *top;
} Stack;

void push(Stack *stack, int data) {
    Node *newNode = malloc(sizeof(Node));
    newNode->data = data;
    newNode->next = stack->top;
    stack->top = newNode;
}

int pop(Stack *stack) {
    if (stack->top == NULL) {
        printf("Stack Underflow\n");
        exit(1);
    } else {
        Node *temp = stack->top;
        int data = temp->data;
        stack->top = stack->top->next;
        free(temp);
        return data;
    }
}

int peek(Stack *stack) {
    if (stack->top == NULL) {
        printf("Stack is empty\n");
        return -1;
    } else {
        return stack->top->data;
    }
}

int isEmpty(Stack *stack) {
    return stack->top == NULL;
}

int main() {
    Stack *stack = malloc(sizeof(Stack));
    stack->top = NULL;

    push(stack, 10);
    push(stack, 20);
    push(stack, 30);

     printf("Oberstes Element: %d\n", peek(stack));

    printf("Entferntes Element: %d\n", pop(stack));

    printf("Oberstes Element: %d\n", peek(stack));

    free(stack);

    return 0;
}

In diesem Beispiel:

  • Definieren wir eine Struktur Node, die die Daten und einen Zeiger auf den nächsten Knoten enthält.
  • Die Struktur Stack enthält einen Zeiger namens top, der auf den obersten Knoten der Liste verweist.
  • Die Funktion push() erstellt einen neuen Knoten, speichert die Daten und fügt den neuen Knoten am Anfang der Liste ein.
  • Die Funktion pop() entfernt den obersten Knoten aus der Liste und gibt dessen Daten zurück.
  • Die Funktion peek() gibt die Daten des obersten Knotens zurück, ohne ihn zu entfernen.
  • Die Funktion isEmpty() überprüft, ob der Stack leer ist.

4. Vor- und Nachteile der verschiedenen Implementierungen

Array-basierte Implementierung:

Vorteile:

  • Einfache Implementierung.
  • Direkter und schneller Zugriff auf Elemente.

Nachteile:

  • Statische Größe: Die Arraygröße muss bei der Kompilierung festgelegt werden und kann zur Laufzeit nicht verändert werden.
  • Mögliches Stack Overflow: Wenn das Array voll ist, können keine weiteren Elemente hinzugefügt werden.

Verkettete Liste-basierte Implementierung:

Vorteile:

  • Dynamische Größe: Die Liste kann beliebig viele Elemente aufnehmen.
  • Keine Gefahr eines Stack Overflows.

Nachteile:

  • Komplexere Implementierung.
  • Weniger effizienter Elementzugriff, da die Liste durchlaufen werden muss.

5. Schlussfolgerung

In diesem Artikel haben wir die Implementierung von Stacks in C anhand zweier Methoden beleuchtet: mit Arrays und mit verketteten Listen. Die geeignete Methode hängt von den jeweiligen Projektanforderungen ab. Eine Array-basierte Implementierung eignet sich gut für Stacks mit fester Größe und Bedarf an schnellem Zugriff. Eine Implementierung mit verketteten Listen bietet eine dynamische Größe und Flexibilität für wechselnde Datenmengen.

6. FAQs

1. Was bedeutet „Stack Overflow“?

Ein „Stack Overflow“ tritt auf, wenn ein Programm versucht, ein Element in einen bereits vollen Stack einzufügen. Dies kann zum Absturz des Programms führen.

2. Was bedeutet „Stack Underflow“?

Ein „Stack Underflow“ tritt auf, wenn ein Programm versucht, ein Element von einem bereits leeren Stack zu entfernen. Dies kann ebenfalls zum Absturz des Programms führen.

3. Wie kann man einen Stack mit dynamischer Größe realisieren?

Ein Stack mit dynamischer Größe lässt sich am besten mit einer verketteten Liste realisieren, da diese beliebig viele Knoten aufnehmen kann und sich die Stackgröße dynamisch anpasst.

4. Worin besteht der Unterschied zwischen einem Stack und einer Queue?

Ein Stack ist eine LIFO-Datenstruktur (Last In, First Out), während eine Queue nach dem FIFO-Prinzip (First In, First Out) arbeitet. Beim Stack wird das zuletzt hinzugefügte Element als erstes entfernt, bei der Queue das zuerst hinzugefügte Element.

5. In welchen Bereichen der Programmierung werden Stacks eingesetzt?

Stacks finden Anwendung in vielen Bereichen, zum Beispiel bei der Abarbeitung von Funktionsaufrufen, der Auswertung mathematischer Ausdrücke, bei Undo/Redo-Funktionen und der Speicherverwaltung.

6. Wie kann ein Stack in C dynamisch allokiert werden?

Ein Stack kann in C durch die Verwendung der Funktion malloc() dynamisch allokiert werden, um genügend Speicher für die Stack-Struktur bereitzustellen.

7. Wie hoch ist die Zeitkomplexität der Operationen push() und pop()?

Die Zeitkomplexität für push() und pop() beträgt O(1), da diese Operationen unabhängig von der Stackgröße in konstanter Zeit durchgeführt werden.

8. Ist es möglich, in C einen Stack mit einem Array ohne festes Größenlimit zu implementieren?

Ja, dies kann durch dynamische Speicherallokation des Arrays erreicht werden, wobei die Arraygröße bei Bedarf erhöht werden kann.

9. Gibt es noch andere Möglichkeiten zur Implementierung eines Stacks in C?

Ja, neben Arrays und verketteten Listen können auch andere Strukturen wie Bäume oder Hash-Tabellen zur Implementierung eines Stacks verwendet werden, abhängig von den Projektanforderungen.

10. Welche Implementierung ist am besten?

Die „beste“ Implementierung hängt von den spezifischen Projektanforderungen ab. Wenn eine feste Größe ausreicht und schneller Zugriff erforderlich ist, ist das Array ideal. Für dynamische Größen und Flexibilität ist die Implementierung mit verketteten Listen besser geeignet.

Stichworte: C Programmierung, Datenstrukturen, Stack, Array, Verkettete Liste, LIFO, Push, Pop, Peek, IsEmpty, IsFull, Stack Overflow, Stack Underflow, Call Stack, Arithmetische Ausdrücke, Undo/Redo, Speicherverwaltung.