数据结构(二)线性表————栈与队列

1. 栈的基本概念

1.1 栈的定义

栈是限定仅在表尾进行插入 / 删除操作的线性表,遵循 ** 后进先出(LIFO)** 原则:

  • 表尾端称为栈顶(top),是操作的唯一位置;
  • 表头端称为栈底(bottom)
  • 不含元素的栈称为空栈

1.2 栈的基本操作

栈的核心操作包括:

  • 进栈(push):向栈顶插入元素;
  • 出栈(pop):从栈顶删除最后插入的元素。

2. 栈的顺序结构实现

顺序结构通过数组 存储栈元素,配合top指针标记栈顶位置。

2.1 顺序栈的结构定义

cs 复制代码
#define MAXSIZE = 100
typedef int ElemType;
typedef struct{
    ElemType data[MAXSIZE];  // 数组存储栈元素
    int top;                 // 栈顶指针(标记栈顶位置)
}Stack;

2.2 顺序栈的核心操作

2.2.1 初始化栈

将栈顶指针top设为-1(表示空栈):

cs 复制代码
void initStack(Stack *s)
{
    s->top = -1;
}
2.2.2 判断栈是否为空

通过top == -1判断空栈:

cs 复制代码
int isEmpty(Stack *s)
{
    if (s->top == -1)
    {
        printf("空的\n");
        return 1;  // 空栈返回1
    }
    else
    {
        return 0;  // 非空返回0
    }
}
2.2.3 进栈(压栈)

先判断栈是否已满,再将元素插入栈顶:

cs 复制代码
int push(Stack *s, ElemType e)
{
    if (s->top >= MAXSIZE - 1)  // 栈满(数组下标从0开始)
    {
        printf("满了\n");
        return 0;
    }
    s->top++;                   // 栈顶指针上移
    s->data[s->top] = e;        // 元素存入栈顶
    return 1;
}
2.2.4 出栈

先判断栈是否为空,再取出栈顶元素并下移top

cs 复制代码
ElemType pop(Stack *s, ElemType *e)
{
    if (s->top == -1)  // 空栈无法出栈
    {
        printf("空的\n");
        return 0;
    }
    *e = s->data[s->top];  // 取出栈顶元素
    s->top--;              // 栈顶指针下移
    return 1;
}
2.2.5 获取栈顶元素

仅读取栈顶元素,不修改top指针:

cs 复制代码
int getTop(Stack *s, ElemType *e)
{
    if (s->top == -1)  // 空栈无栈顶元素
    {
        printf("空的\n");
        return 0;
    }
    *e = s->data[s->top];  // 读取栈顶元素
    return 1;
}

2.3 顺序栈的动态内存分配实现

通过malloc动态分配栈的结构和数组空间:

cs 复制代码
#define MAXSIZE 100
typedef int ElemType;
typedef struct
{
    ElemType *data;  // 动态数组指针
    int top;
}Stack;

Stack* initStack()
{
    Stack *s = (Stack*)malloc(sizeof(Stack));  // 分配栈结构空间
    s->data = (ElemType*)malloc(sizeof(ElemType) * MAXSIZE);  // 分配数组空间
    s->top = -1;  // 初始化栈顶指针
    return s;
}

3. 栈的链式结构实现

链式结构通过单链表 存储栈元素,以头节点的后继节点作为栈顶

3.1 链栈的结构定义

cs 复制代码
typedef int ElemType;
typedef struct stack
{
    ElemType data;          // 节点存储的元素
    struct stack *next;     // 指向下一节点的指针
}Stack;

3.2 链栈的核心操作

3.2.1 初始化链栈

创建头节点,将其next设为NULL(表示空栈):

cs 复制代码
Stack* initStack()
{
    Stack *s = (Stack*)malloc(sizeof(Stack));
    s->data = 0;    // 头节点数据域可留空(或作其他标识)
    s->next = NULL;
    return s;
}
3.2.2 判断链栈是否为空

通过头节点的next == NULL判断空栈:

cs 复制代码
int isEmpty(Stack *s)
{
    if (s->next == NULL)
    {
        printf("空的\n");
        return 1;  // 空栈返回1
    }
    else
    {
        return 0;  // 非空返回0
    }
}
3.2.3 进栈(压栈)

新节点插入到头节点之后(栈顶位置)

cs 复制代码
int push(Stack *s, ElemType e)
{
    Stack *p = (Stack*)malloc(sizeof(Stack));
    p->data = e;          // 新节点存入元素
    p->next = s->next;    // 新节点指向原栈顶节点
    s->next = p;          // 头节点指向新节点(新节点成为栈顶)
    return 1;
}
3.2.4 出栈

删除头节点的后继节点(栈顶节点):

cs 复制代码
int pop(Stack *s, ElemType *e)
{
    if(s->next == NULL)  // 空栈无法出栈
    {
        printf("空的\n");
        return 0;
    }
    *e = s->next->data;  // 取出栈顶元素
    Stack *q = s->next;  // 记录栈顶节点
    s->next = q->next;   // 头节点指向原栈顶的下一节点
    free(q);             // 释放原栈顶节点内存
    return 1;
}
3.2.5 获取栈顶元素

仅读取头节点后继节点的元素,不修改链表:

cs 复制代码
int getTop(Stack *s, ElemType *e)
{
    if (s->next == NULL)  // 空栈无栈顶元素
    {
        printf("空的\n");
        return 0;
    }
    *e = s->next->data;  // 读取栈顶元素
    return 1;
}

4. 队列的基本概念与顺序结构实现

4.1 队列的定义

队列是限定在一端插入、另一端删除的线性表,遵循 ** 先进先出(FIFO)** 原则:

  • 允许插入的一端称为队尾(rear)
  • 允许删除的一端称为队头(front)
  • 元素按a₁→a₂→...→aₙ的顺序入队,出队顺序与入队顺序一致。

4.2 队列的顺序结构实现

顺序结构通过数组 存储队列元素,配合front(队头指针)和rear(队尾指针)标记操作位置。

4.2.1 顺序队列的结构定义
cs 复制代码
#define MAXSIZE 100
typedef int ElemType;
typedef struct
{
    ElemType data[MAXSIZE];  // 数组存储队列元素
    int front;               // 队头指针
    int rear;                // 队尾指针
}Queue;
4.2.2 顺序队列的初始化

frontrear均设为0(表示空队列):

cs 复制代码
void initQueue(Queue *Q)
{
    Q->front = 0;
    Q->rear = 0;
}
4.2.3 判断顺序队列是否为空

通过front == rear判断空队列:

cs 复制代码
int isEmpty(Queue *Q)
{
    if (Q->front == Q->rear)
    {
        printf("空的\n");
        return 1;  // 空队列返回1
    }
    else
    {
        return 0;  // 非空返回0
    }
}
4.2.4 顺序队列的出队操作

取出队头元素,队头指针front后移:

cs 复制代码
ElemType dequeue(Queue *Q)
{
    if (Q->front == Q->rear)  // 空队列无法出队
    {
        printf("空的\n");
        return 0;
    }
    ElemType e = Q->data[Q->front];  // 取出队头元素
    Q->front++;                      // 队头指针后移
    return e;
}
4.2.5 顺序队列的入队操作

先判断队列是否 "假满"(通过queueFull调整空间),再将元素插入队尾:

cs 复制代码
int equene(Queue *Q, ElemType e)
{
    if (Q->rear >= MAXSIZE)  // 队尾指针超出数组范围
    {
        if(!queueFull(Q))    // 调用queueFull尝试调整空间
        {
            return 0;
        }
    }
    Q->data[Q->rear] = e;  // 元素插入队尾
    Q->rear++;             // 队尾指针后移
    return 1;
}
4.2.6 顺序队列的 "假满" 调整(queueFull

当队尾指针超出范围但队头有空闲空间时,将元素前移以腾出空间:

cs 复制代码
int queueFull(Queue *Q)
{
    if (Q->front > 0)  // 队头有空闲空间
    {
        int step = Q->front;
        // 将元素向前移动step位
        for (int i = Q->front; i <= Q->rear; ++i)
        {
            Q->data[i - step] = Q->data[i];
        }
        Q->front = 0;          // 队头指针重置为0
        Q->rear = Q->rear - step;  // 队尾指针同步前移
        return 1;
    }
    else  // 队头无空闲空间,队列真满
    {
        printf("真的满了\n");
        return 0;
    }
}
4.2.7 获取顺序队列的队头元素

仅读取队头元素,不修改指针:

cs 复制代码
int getHead(Queue *Q, ElemType *e)
{
    if (Q->front == Q->rear)  // 空队列无队头元素
    {
        printf("空的\n");
        return 0;
    }
    *e = Q->data[Q->front];  // 读取队头元素
    return 1;
}
4.2.8 顺序队列的动态内存分配初始化

通过malloc动态分配队列结构和数组空间:

cs 复制代码
typedef struct
{
    ElemType *data;  // 动态数组指针
    int front;
    int rear;
}Queue;

Queue* initQueue()
{
    Queue *q = (Queue*)malloc(sizeof(Queue));
    q->data = (ElemType*)malloc(sizeof(ElemType) * MAXSIZE);
    q->front = 0;
    q->rear = 0;
    return q;
}

4.3 队列的链式结构实现

链式结构通过单链表 存储队列元素,front指向队头节点,rear指向队尾节点。

4.3.1 链队列的结构定义
cs 复制代码
// 队列节点结构
typedef struct QueueNode
{
    ElemType data;
    struct QueueNode *next;
}QueueNode;

// 队列结构(包含队头、队尾指针)
typedef struct
{
    QueueNode *front;
    QueueNode *rear;
}Queue;
4.3.2 链队列的初始化

创建头节点,frontrear均指向头节点(表示空队列):

cs 复制代码
Queue* initQueue()
{
    Queue *q = (Queue*)malloc(sizeof(Queue));
    QueueNode *node = (QueueNode*)malloc(sizeof(QueueNode));
    node->data = 0;    // 头节点数据域可留空
    node->next = NULL;
    q->front = node;
    q->rear = node;
    return q;
}
4.3.3 判断链队列是否为空

通过front == rear(均指向头节点)判断空队列:

cs 复制代码
int isEmpty(Queue *q)
{
    if (q->front == q->rear)
    {
        return 1;  // 空队列返回1
    }
    else
    {
        return 0;  // 非空返回0
    }
}
4.3.4 链队列的入队操作

新节点插入到队尾,更新rear指针指向新队尾:

cs 复制代码
void equene(Queue *q, ElemType e)
{
    QueueNode *node = (QueueNode*)malloc(sizeof(QueueNode));
    node->data = e;
    node->next = NULL;
    q->rear->next = node;  // 原队尾节点指向新节点
    q->rear = node;        // 队尾指针更新为新节点
}
4.3.5 链队列的出队操作

删除队头节点,若队列为空则同步更新rear指针:

cs 复制代码
int dequeue(Queue *q, ElemType *e)
{
    QueueNode *node = q->front->next;  // 队头节点(头节点的后继)
    *e = node->data;                   // 取出队头元素
    q->front->next = node->next;       // 头节点指向原队头的下一节点
    
    if (q->rear == node)  // 若队头是最后一个节点,队尾指针重置为头节点
    {
        q->rear = q->front;
    }
    
    free(node);  // 释放原队头节点内存
    return 1;
}
4.3.6 获取链队列的队头元素

仅读取队头节点的元素,不修改队列结构:

cs 复制代码
ElemType getFront(Queue *q)
{
    if (isEmpty(q))  // 判断队列是否为空
    {
        printf("空的\n");
        return 0;
    }
    return q->front->next->data;  // 读取队头元素
}
4.2.7 顺序队列的循环队列实现 - 入队

通过取模运算实现 "循环",避免空间闲置:

cs 复制代码
int equene(Queue *Q, ElemType e)
{
    // 队满条件:(rear+1)%MAXSIZE == front
    if ((Q->rear + 1) % MAXSIZE == Q->front)
    {
        printf("满了\n");
        return 0;
    }
    Q->data[Q->rear] = e;                  // 元素存入队尾
    Q->rear = (Q->rear + 1) % MAXSIZE;     // 队尾指针循环后移
    return 1;
}
4.2.8 顺序队列的循环队列实现 - 出队

队头指针同样通过取模运算循环后移:

cs 复制代码
int dequeue(Queue *Q, ElemType *e)
{
    if (Q->front == Q->rear)  // 队空条件
    {
        printf("空的\n");
        return 0;
    }
    *e = Q->data[Q->front];                // 取出队头元素
    Q->front = (Q->front + 1) % MAXSIZE;   // 队头指针循环后移
    return 1;
}

5. 栈与队列的对比

5.1 栈与队列的核心差异

维度 队列
逻辑结构 和线性表一致,元素间是一对一关系 和线性表一致,元素间是一对一关系
存储结构 顺序存储:空间预分配,可能闲置 / 溢出;链式存储:动态分配,无闲置 / 溢出,可扩容 顺序存储(常设计为循环队列):空间预分配,可能闲置 / 溢出;链式存储:动态分配,无闲置 / 溢出,可扩容
运算规则 插入、删除仅在栈顶,遵循后进先出(LIFO) 插入在队尾、删除在队头,遵循先进先出(FIFO)

6. 斐波那契数列的递归实现

6.1 递归方式求斐波那契数列第 n 项

斐波那契数列定义:第 1、2 项为 1,从第 3 项起,每一项是前两项之和:

cs 复制代码
int fibonacci(int n)
{
    if (n == 1 || n == 2)  // 基线条件:第1、2项为1
    {
        return 1;
    }
    else  // 递归条件:f(n) = f(n-1) + f(n-2)
    {
        return fibonacci(n - 1) + fibonacci(n - 2);
    }
}
相关推荐
AI科技星2 小时前
光速的几何本质与运动极限:基于张祥前统一场论对光子及有质量粒子运动的统一诠释
数据结构·人工智能·经验分享·算法·计算机视觉
谈笑也风生2 小时前
经典算法题型之复数乘法(一)
数据结构·算法
一分之二~3 小时前
回溯算法--全排列
c语言·数据结构·c++·算法·leetcode
ohnoooo93 小时前
251211算法 搜索
数据结构·算法
热爱专研AI的学妹3 小时前
Coze-AI 智能体平台:工作流如何成为智能体的 “自动化引擎”?解锁零代码落地新范式
运维·数据结构·人工智能·自动化
聆风吟º3 小时前
【数据结构手札】顺序表实战指南(三):扩容 | 尾插 | 尾删
数据结构·顺序表·扩容·尾插·尾删
IT方大同3 小时前
数组的初始化与使用
c语言·数据结构·算法
im_AMBER3 小时前
Leetcode 84 水果成篮 | 删除子数组的最大得分
数据结构·c++·笔记·学习·算法·leetcode·哈希算法
AAA阿giao3 小时前
从树到楼梯:数据结构与算法的奇妙旅程
前端·javascript·数据结构·学习·算法·力扣·