数据结构实验四:栈与队列的基本操作

本节实验课总体安排:

1、顺序栈的基本操作

2、链队列的基本操作

根据栈和队列的运算规则,选取不同的存储方式。

一、顺序栈的入栈、出栈、取栈顶元素、遍历栈


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

#define STACK_INIT_SIZE 100
#define STACK_ADD_SIZE 10

typedef int SElemType;

typedef struct sqstack {
    SElemType* top;
    SElemType* base;
    int max_size;
} Sqstack;

// 初始化栈
bool init_stack(Sqstack *sq)
{
    sq->base = (SElemType*)malloc(STACK_INIT_SIZE * sizeof(SElemType));
    if (!sq->base) {
        printf("初始化栈失败\n");
        return false;
    }
    sq->top = sq->base;
    sq->max_size = STACK_INIT_SIZE;
    return true;
}

// 判栈满
bool judge_full(Sqstack sq)
{
    return (sq.top - sq.base >= sq.max_size);
}

// 扩容
bool growth_stack(Sqstack *sq)
{
    int current_size = sq->top - sq->base;
    SElemType* new_base = (SElemType*)realloc(sq->base, (sq->max_size + STACK_ADD_SIZE) * sizeof(SElemType));
    if (!new_base) {
        printf("扩容失败\n");
        return false;
    }

    sq->base = new_base;
    sq->top = sq->base + current_size;
    sq->max_size += STACK_ADD_SIZE;

    return true;
}

// 判栈空
bool judge_empty(Sqstack sq)
{
    return (sq.base == sq.top);
}

// 遍历输出
bool print_stack(Sqstack sq)
{
    if (judge_empty(sq)) {
        printf("当前栈为空\n");
        return false;
    }

    printf("栈中的元素为:");
    int i = sq.top - sq.base, j;
    for (j = 1; j <= i; j++)
        printf("%d ", *(sq.top - j));
    printf("\n");
    return true;
}

// 入栈
bool push(Sqstack *sq, SElemType data)
{
    if (judge_full(*sq)) {
        printf("栈满,自动扩容\n");
        if (!growth_stack(sq))
            return false;
    }

    *(sq->top) = data;
    sq->top++;
    return true;
}

// 出栈
bool pop(Sqstack *sq, SElemType *data)
{
    if (judge_empty(*sq)) {
        printf("当前栈为空\n");
        return false;
    }

    *data = *(--sq->top);
    return true;
}

// 取栈顶元素
bool get_top(Sqstack sq, SElemType *data)
{
    if (judge_empty(sq)) {
        printf("当前栈为空\n");
        return false;
    }

    *data = *(sq.top - 1);
    return true;
}

int main()
{
    printf("顺序栈操作\n");
    Sqstack sq;
    init_stack(&sq);

    int i, n;
    SElemType data;
    printf("请输入入栈元素个数:");
    scanf("%d", &n);

    for (i = 0; i < n; i++) {
        printf("请输入第%d个元素:", i + 1);
        scanf("%d", &data);
        push(&sq, data);
    }

    print_stack(sq);

    printf("出栈的栈顶元素:");
    if (pop(&sq, &data))
        printf("%d\n", data);
    print_stack(sq);

    printf("输出栈顶元素:");
    if (get_top(sq, &data))
        printf("%d\n", data);

    free(sq.base); // 释放内存
    return 0;
}

二、代码讲解

数据结构说明(Sqstack

typedef struct sqstack {
    SElemType* top;    // 指向栈顶下一个可用位置(即“栈顶指针”的惯用表示)
    SElemType* base;   // 指向栈底第一个元素的地址
    int max_size;      // 当前分配的最大元素个数(容量)
} Sqstack;
  • base 指向已经分配的数组起始地址(数组下标 0)。

  • top 指向数组中下一次入栈的位置(当栈为空时 top == base)。

  • 当前栈中元素个数为 top - base(指针相减,单位是元素个数)。

  • 空间布局示例(入栈 10,20,30 后):

base -> [10][20][30][   ][   ] ... 
            ^         ^
         base      top(指向第4格)

函数:init_stack(Sqstack *sq)

作用:初始化顺序栈,分配初始内存并设置 basetopmax_size

实现步骤

  1. 用 malloc(STACK_INIT_SIZE * sizeof(SElemType)) 为 sq->base 分配 STACK_INIT_SIZE 个元素的内存。

  2. 判断 malloc 返回值是否为 NULL:若是 NULL,表示分配失败,打印错误并返回 false

  3. 若分配成功,令 sq->top = sq->base(表示空栈),并设置 sq->max_size = STACK_INIT_SIZE

  4. 返回 true 表示初始化成功。

时间复杂度:O(1)。
注意点

  • malloc 失败时应妥善处理(当前实现打印错误并返回 false,调用者应检查返回值)。

  • 推荐使用 size_t 表示容量以避免与 int 的类型问题(当前用 int,在元素非常多时可能溢出)。


函数:judge_full(Sqstack sq)

作用:判断栈是否已满(即当前元素个数是否达到或超过容量)。

实现步骤

  1. 计算 sq.top - sq.base(返回当前元素个数)。

  2. 与 sq.max_size 比较:若 >=,返回 true,否则 false

时间复杂度:O(1)。
注意点

  • 该函数以值传递 Sqstack(拷贝结构体);对小结构体没问题,但若结构体变大可改为指针传参以避免拷贝开销。

  • top - base 的结果类型是 ptrdiff_t(算术上比 int 更合适),但用在比较时通常可行。


函数:growth_stack(Sqstack *sq)

作用:为栈扩容(把容量增加 STACK_ADD_SIZE),并保持已有元素不变。

实现步骤

  1. 先计算当前元素个数 current_size = sq->top - sq->base(这是必要的,因为 realloc 可能移动内存,必须用 current_size 在完成后重置 top)。

  2. 调用 realloc(sq->base, (sq->max_size + STACK_ADD_SIZE) * sizeof(SElemType)) 请求扩大分配空间。

    • realloc 若返回非 NULL,会返回新的内存地址(可能与原地址相同或不同),并保留原数据(按字节复制)。

    • 若 realloc 返回 NULL,原来 sq->base 的内存仍然有效(注意不要把 realloc 的返回直接赋回原指针,除非先检测返回值)。

  3. 判断 realloc 返回值 new_base

    • 若为 NULL,打印“扩容失败”,返回 false(原内存仍然可用)。

    • 若非 NULL,把 sq->base = new_base,根据 current_size 更新 sq->top = sq->base + current_size,并把 sq->max_size += STACK_ADD_SIZE

  4. 返回 true

时间复杂度:O(n)(realloc 复制当前数据到新内存,n = 元素个数)。
注意点 / 风险

  • realloc 可能失败,正确的做法是先把返回结果保存在临时指针 new_base,只有在非 NULL 时才覆盖 sq->base(你的实现是这样的,安全)。

  • 扩容策略为固定增量 STACK_ADD_SIZE,在大量 push 的情况下会频繁 realloc。一个更常用的策略是按倍数扩容(例如 max_size *= 2),能把均摊时间复杂度保持为常数。

  • current_size 必须先计算并在 realloc 后重新计算 top,否则如果 realloc 把内存移动到新地址,原 top指针会成为悬空指针。


函数:judge_empty(Sqstack sq)

作用:判断栈是否为空。

实现步骤

  1. 比较 sq.base == sq.top:若相等则为空栈,返回 true;否则返回 false

时间复杂度:O(1)。
注意点

  • 同样以值拷贝方式传参,若想避免拷贝可以传指针。


函数:print_stack(Sqstack sq)

作用:按从栈顶到栈底顺序打印栈中当前所有元素(典型的“从栈顶开始打印”以展示 LIFO 顺序)。

实现步骤

  1. 调用 judge_empty(sq) 判断是否为空,若空则打印 "当前栈为空" 并返回 false

  2. 计算元素个数 i = sq.top - sq.base

  3. 用 for (j = 1; j <= i; j++) 依次输出 *(sq.top - j),这样第一次输出的是 *(sq.top - 1)(栈顶元素),依次向下到 *(sq.base)(栈底元素)。

  4. 输出换行并返回 true

时间复杂度:O(n),n = 当前元素个数(必须遍历所有元素以打印)。
注意点

  • 输出顺序是从栈顶到栈底(符合观察栈内容的直觉)。

  • 函数以值传参(拷贝结构体),但没有修改栈,所以不会影响外部 sq


函数:push(Sqstack *sq, SElemType data)

作用:将一个元素压入栈顶(入栈)。

实现步骤

  1. 调用 judge_full(*sq)(注意传值)检查是否已满。如果已满:

    • 打印 "栈满,自动扩容"

    • 调用 growth_stack(sq) 做扩容;若扩容失败则返回 false(无法入栈)。

  2. *(sq->top) = data; 将元素写入 top 指向的位置(此位置为下一个可用槽)。

  3. sq->top++; 将 top 指针向后移动一个元素位置,表示新的栈顶下一个可用位置。

  4. 返回 true 表示入栈成功。

时间复杂度:均摊 O(1)。(通常一次写入 O(1),只有在扩容时为 O(n))
注意点

  • 如果 growth_stack 失败,push 返回 false。调用处应检查返回值以避免数据丢失(你的 main 中未检查返回值,建议补充)。

  • *(sq->top) = data; 在 top 已指向有效内存时安全;如果 top 已越界(说明 judge_full 有逻辑错误),会导致写越界。


函数:pop(Sqstack *sq, SElemType *data)

作用:弹出栈顶元素(出栈),并把弹出的值写入 *data

实现步骤

  1. 调用 judge_empty(*sq) 判断是否为空栈;若为空打印 "当前栈为空" 并返回 false

  2. 使用前置递减 --sq->top,然后 *data = *(sq->top);。前置递减把 top 指回最后已占用元素的位置,随后读出该元素值。

  3. 返回 true 表示出栈成功,返回的 *data 就是被弹出的元素。

时间复杂度:O(1)。
注意点

  • 出栈后没有清除原内存(不是必要),但如果元素类型较大或含有资源(例如指针需要释放),应在必要时做清理。

  • pop 没有做缩容(通常不需要,但可优化以节省内存)。


函数:get_top(Sqstack sq, SElemType *data)

作用:读取当前栈顶(但不弹出),把栈顶元素存入 *data

实现步骤

  1. 检查是否空栈(judge_empty(sq));若空则打印提示并返回 false

  2. *data = *(sq.top - 1);:因为 sq.top 指向下一个空位置,所以栈顶元素位于 sq.top - 1

  3. 返回 true

时间复杂度:O(1)。
注意点

  • 传参用值拷贝 Sqstack sq,函数内部不修改栈,所以这样是安全的。

  • 必须保证 sq.top != sq.base(即非空)才能安全地做 sq.top - 1


函数:main()

作用:演示并驱动栈的使用流程:初始化、批量入栈、打印、出栈、读栈顶、释放内存(free)。

实现步骤(按代码流程)

  1. 打印启动信息 printf("顺序栈操作\n");

  2. 声明 Sqstack sq; 并调用 init_stack(&sq) 初始化栈空间(但注意当前 main 没有检查返回值,建议检查以处理分配失败)。

  3. 读取要入栈元素的个数 n(用 scanf("%d", &n))。注意:应检查 scanf 的返回值以保证合法输入。

  4. 循环 for (i = 0; i < n; i++)

    • 读取每个元素 scanf("%d", &data)(同样应检查返回值)。

    • 调用 push(&sq, data) 入栈(应检查 push 的返回值以处理扩容失败)。

  5. 调用 print_stack(sq) 打印当前栈内容。

  6. 调用 pop(&sq, &data) 弹出一个元素并打印;若 pop 成功则打印弹出的值。

  7. 再次 print_stack(sq) 显示出栈后的栈内容。

  8. 调用 get_top(sq, &data) 获取当前栈顶并打印(若栈非空)。

  9. free(sq.base) 释放之前 malloc 分配的内存,避免内存泄露。

  10. return 0;

时间复杂度(整体):取决于用户输入量 n,总体 O(n)(入栈 n 次 + 打印一次 O(n))。

注意点

  • 主函数中对 init_stackpushpopscanf 等返回值没有做充分检查,实际代码应健壮化处理错误分支(内存不足、输入错误、扩容失败等)。

  • free(sq.base) 在程序结束时释放内存,这是必要的良好实践(尽管程序退出时 OS 会回收,但在大型程序中应显式释放)。


小示例(手工演示一次入栈过程)

假设 STACK_INIT_SIZE = 5,初始 base = 0x1000(伪地址),top = base

  • 入栈 10:写 *(top)=10,然后 top++。现在 top - base = 1

  • 入栈 20:写 *(top)=20top++,现在 top - base = 2
    若继续入栈直到 top - base == max_sizepush 会触发 growth_stack 扩容(realloc 分配新空间并把前 current_size 项拷贝过来,更新 base 和 top)。

三、链队列的初始化、入队、出队、遍历队列、回文数

  • // 链队列 (C语言版)
    
    #include <stdio.h>
    #include <stdlib.h>
    #include <stdbool.h>
    
    typedef char ElemType;
    
    // 队列结点
    typedef struct qnode {
        ElemType data;
        struct qnode *next;
    } QueueNode, *QNode;
    
    // 链队列结构
    typedef struct linkqueue {
        QNode front;  // 队头指针
        QNode rear;   // 队尾指针
    } LinkQueue;
    
    // 函数声明
    bool init_queue(LinkQueue *q);               // 初始化
    bool en_queue(LinkQueue *q, ElemType data);  // 入队列
    bool de_queue(LinkQueue *q, ElemType *data); // 出队列
    bool clear_queue(LinkQueue *q);              // 清空队列
    bool judge_empty(LinkQueue q);               // 判空
    bool get_length(LinkQueue q, int *length);   // 队列长度
    bool get_head(LinkQueue q, ElemType *data);  // 得到队头元素
    bool traverse_queue(LinkQueue q);            // 遍历队列
    
    void show();
    void switch_channel(int channel, LinkQueue *q);
    
    int main()
    {
        LinkQueue q;
        int channel;
    
        do {
            show();
            scanf("%d", &channel);
            switch_channel(channel, &q);
            printf("\n");
        } while (1);
    
        return 0;
    }
    
    // 菜单
    void show()
    {
        printf("链队列操作\n");
        printf("1--初始化、入队元素\n");
        printf("2--出队列\n");
        printf("3--输出队头元素\n");
        printf("4--队列长度\n");
        printf("5--清空队列\n");
        printf("6--遍历队列\n");
        printf("0--退出\n");
    }
    
    // 菜单分发
    void switch_channel(int channel, LinkQueue *q)
    {
        ElemType data;
        int n, i;
    
        switch (channel) {
        case 1:
            init_queue(q);
            printf("请输入要入队列元素的数目:");
            scanf("%d", &n);
            for (i = 0; i < n; i++) {
                printf("请输入第%d个元素:", i + 1);
                scanf(" %c", &data);  // 注意前面有空格,避免回车干扰
                en_queue(q, data);
            }
            traverse_queue(*q);
            break;
        case 2:
            if (de_queue(q, &data))
                printf("出队列元素为:%c\n", data);
            traverse_queue(*q);
            break;
        case 3:
            if (get_head(*q, &data))
                printf("队头元素为:%c\n", data);
            traverse_queue(*q);
            break;
        case 4:
            get_length(*q, &n);
            printf("当前队列长度为:%d\n", n);
            break;
        case 5:
            clear_queue(q);
            traverse_queue(*q);
            break;
        case 6:
            traverse_queue(*q);
            break;
        case 0:
            exit(0);
        default:
            printf("无效选择\n");
            break;
        }
    }
    
    // 初始化
    bool init_queue(LinkQueue *q)
    {
        q->front = q->rear = (QNode)malloc(sizeof(QueueNode));
        if (!q->front) return false;
        q->front->next = NULL;
        printf("初始化完成\n");
        return true;
    }
    
    // 入队列
    bool en_queue(LinkQueue *q, ElemType data)
    {
        QNode p = (QNode)malloc(sizeof(QueueNode));
        if (!p) return false;
        p->data = data;
        p->next = NULL;
        q->rear->next = p;
        q->rear = p;
        return true;
    }
    
    // 出队列
    bool de_queue(LinkQueue *q, ElemType *data)
    {
        if (judge_empty(*q)) {
            printf("队列为空\n");
            return false;
        }
    
        QNode p = q->front->next;
        *data = p->data;
        q->front->next = p->next;
        if (q->rear == p)  // 若删除的是最后一个结点
            q->rear = q->front;
        free(p);
        return true;
    }
    
    // 清空队列
    bool clear_queue(LinkQueue *q)
    {
        QNode p;
        while (q->front->next) {
            p = q->front->next;
            q->front->next = p->next;
            free(p);
        }
        q->rear = q->front;
        printf("清空完成\n");
        return true;
    }
    
    // 判空
    bool judge_empty(LinkQueue q)
    {
        return (q.front == q.rear);
    }
    
    // 队列长度
    bool get_length(LinkQueue q, int *length)
    {
        *length = 0;
        QNode p = q.front->next;
        while (p) {
            (*length)++;
            p = p->next;
        }
        return true;
    }
    
    // 得到队头元素
    bool get_head(LinkQueue q, ElemType *data)
    {
        if (judge_empty(q)) {
            printf("队列为空\n");
            return false;
        }
        *data = q.front->next->data;
        return true;
    }
    
    // 遍历队列
    bool traverse_queue(LinkQueue q)
    {
        if (judge_empty(q)) {
            printf("遍历队列--队列为空\n");
            return false;
        }
        printf("遍历队列:");
        QNode p = q.front->next;
        while (p) {
            printf("%c ", p->data);
            p = p->next;
        }
        printf("\n");
        return true;
    }
    

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值