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 顺序队列的初始化
将front和rear均设为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 链队列的初始化
创建头节点,front和rear均指向头节点(表示空队列):
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);
}
}