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

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);
    }
}
相关推荐
闪电悠米3 小时前
力扣hot100-41.缺失的第一个正数-原地哈希详解
数据结构·算法·哈希算法
WL学习笔记4 小时前
堆(数据结构)
数据结构·
wabs6667 小时前
关于图论【卡码网100.最大岛屿的面积的思考】
数据结构·算法·图论
yuannl109 小时前
图的最短路径Dijkstra
数据结构
zander25810 小时前
138. 随机链表的复制
数据结构·算法·链表
过期动态10 小时前
【LeetCode 热题 100】找到字符串中所有字母异位词
java·数据结构·算法·leetcode·职场和发展·rabbitmq
来一碗刘肉面10 小时前
栈在递归中的应用
数据结构·算法
皓月斯语11 小时前
程序设计语言的特点
开发语言·数据结构·c++
邪修king11 小时前
C++ 进阶终章:异常机制与智能指针全解 —— 从错误处理到 RAII 资源管理,打通现代 C++ 的核心命脉
android·数据结构·c++
Adios79419 小时前
设置交集大小至少为2
数据结构·算法·leetcode