数据结构与算法:链栈的表示和实现

链栈的表示和实现

链栈是一种基于链表实现的栈(Stack),它遵循后进先出(LIFO)原则。与数组栈相比,链栈的优势在于动态内存分配,避免了固定大小限制,适合处理不确定规模的数据。下面,我将从概念、数据结构表示、基本操作实现等方面逐步讲解,使用C语言代码示例,语言自然易懂。

1. 链栈的基本概念

栈是一种线性数据结构,只允许在栈顶(Top)进行插入和删除操作。链栈使用单链表来存储元素:

  • 栈顶:链表的头节点(Head)作为栈顶,操作高效。
  • 节点结构:每个节点包含数据域和指针域,指针指向下一个节点。
  • 优势
    • 动态扩展:无需预先分配大小,内存按需分配。
    • 时间复杂度:入栈(Push)和出栈(Pop)操作均为$O(1)$,因为只涉及栈顶节点。
    • 空间复杂度:$O(n)$,其中$n$是元素个数,每个节点占用额外指针空间。
2. 链栈的数据结构表示

在C语言中,我们定义节点结构体(Node)和栈结构体(Stack)。链栈通常只需一个指针指向栈顶节点,不需要额外存储容量信息。

// 定义节点结构体
typedef struct Node {
    int data;           // 存储数据,假设为整数类型
    struct Node* next;  // 指向下一个节点的指针
} Node;

// 定义栈结构体
typedef struct Stack {
    Node* top;          // 栈顶指针,指向链表头部
} Stack;

  • 解释Node表示链表节点,包含数据data和指向下一个节点的指针nextStack只包含一个指针top,指向栈顶节点。当栈为空时,topNULL
3. 基本操作实现

链栈的核心操作包括初始化、入栈、出栈、查看栈顶、判断栈空和销毁栈。我将逐一实现这些操作,并提供C语言代码。所有操作时间复杂度均为$O(1)$(除了销毁栈为$O(n)$),因为只涉及栈顶指针操作。

(1) 初始化栈

创建一个空栈,将栈顶指针设为NULL

// 初始化栈
Stack* initStack() {
    Stack* stack = (Stack*)malloc(sizeof(Stack));  // 分配栈内存
    if (stack == NULL) {
        printf("内存分配失败!\n");
        exit(1);  // 异常处理
    }
    stack->top = NULL;  // 栈顶指针初始化为空
    return stack;
}

  • 说明:使用malloc动态分配栈结构体内存,并将top设为NULL表示空栈。
(2) 入栈操作(Push)

在栈顶插入新元素。创建新节点,将其插入链表头部,并更新栈顶指针。

// 入栈操作
void push(Stack* stack, int data) {
    Node* newNode = (Node*)malloc(sizeof(Node));  // 创建新节点
    if (newNode == NULL) {
        printf("内存分配失败!\n");
        exit(1);
    }
    newNode->data = data;        // 设置节点数据
    newNode->next = stack->top;  // 新节点指向原栈顶
    stack->top = newNode;        // 更新栈顶指针为新节点
}

  • 示例:调用push(stack, 10)后,新节点成为栈顶,原栈顶成为其下一个节点。
(3) 出栈操作(Pop)

删除栈顶元素并返回其值。需要检查栈是否为空。

// 出栈操作
int pop(Stack* stack) {
    if (isEmpty(stack)) {  // 检查栈空
        printf("栈为空,无法出栈!\n");
        exit(1);
    }
    Node* temp = stack->top;      // 临时保存栈顶节点
    int data = temp->data;        // 获取栈顶数据
    stack->top = temp->next;      // 更新栈顶为下一个节点
    free(temp);                   // 释放原栈顶内存
    return data;                  // 返回删除的数据
}

  • 说明:先保存栈顶数据,然后移动栈顶指针并释放节点内存。如果栈空,报错退出。
(4) 查看栈顶元素(Peek)

返回栈顶元素值,但不删除节点。

// 查看栈顶元素
int peek(Stack* stack) {
    if (isEmpty(stack)) {
        printf("栈为空,无栈顶元素!\n");
        exit(1);
    }
    return stack->top->data;  // 直接返回栈顶数据
}

(5) 判断栈是否为空

检查栈顶指针是否为NULL

// 判断栈空
int isEmpty(Stack* stack) {
    return (stack->top == NULL);  // 栈顶为NULL时返回1(真),否则0(假)
}

(6) 销毁栈

释放所有节点内存,防止内存泄漏。需要遍历链表释放每个节点。

// 销毁栈
void destroyStack(Stack* stack) {
    Node* current = stack->top;
    while (current != NULL) {
        Node* temp = current;      // 临时保存当前节点
        current = current->next;   // 移动到下一个节点
        free(temp);                // 释放当前节点
    }
    free(stack);  // 释放栈结构体内存
}

  • 时间复杂度:$O(n)$,因为需要遍历所有节点释放内存。
4. 完整示例代码

以下是一个简单的主函数示例,演示链栈的使用:

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

// 节点和栈定义(同上)

int main() {
    Stack* stack = initStack();  // 初始化栈

    push(stack, 10);  // 入栈元素10
    push(stack, 20);  // 入栈元素20
    push(stack, 30);  // 入栈元素30

    printf("栈顶元素: %d\n", peek(stack));  // 输出30

    printf("出栈元素: %d\n", pop(stack));   // 输出30
    printf("出栈元素: %d\n", pop(stack));   // 输出20

    if (isEmpty(stack)) {
        printf("栈为空\n");
    } else {
        printf("栈非空,当前栈顶: %d\n", peek(stack));  // 输出10
    }

    destroyStack(stack);  // 销毁栈
    return 0;
}

  • 运行结果:输出栈顶元素、出栈元素和栈状态。例如,先入栈10,20,30,然后出栈两次,最后栈顶为10。
5. 总结

链栈是一种高效、灵活的栈实现方式:

  • 优点:动态内存管理,避免溢出;操作简单高效。
  • 缺点:每个节点额外指针占用空间;内存分配/释放可能引入开销。
  • 适用场景:数据规模未知或频繁变化的场景,如函数调用栈、表达式求值。 通过以上内容,您应该能掌握链栈的核心原理和C语言实现。在实际应用中,确保正确处理内存管理以避免泄漏。如果您有疑问,欢迎继续讨论!
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值