考研408数据结构(栈与队列)
目录
顺序栈
基本概念
顺序栈是一种基于数组实现的栈结构,利用连续的内存空间存储数据,遵循“后进先出”(LIFO)原则。栈顶指针(通常为top)动态指向当前栈顶元素的位置,支持压栈(push)和弹栈(pop)操作。
核心特性
- 存储结构:通过数组实现,需预先分配固定大小的内存空间。
- 栈顶指针:初始值为
-1(空栈),插入元素时top递增,删除时递减。 - 时间复杂度:压栈和弹栈操作的时间复杂度均为O(1)。
代码实现
#include <stdio.h>
// 考研标准:栈的最大容量(可根据题目修改)
#define MaxSize 50
// 顺序栈结构体定义(考研必考写法)
typedef struct {
// 静态数组存放栈元素
int data[MaxSize];
// 栈顶指针:指向栈顶元素(考研最常用定义方式)
int top;
} SqStack;
// 1. 初始化栈(核心)
void InitStack(SqStack *S) {
// 栈顶指针置为-1,表示空栈
S->top = -1;
}
// 2. 判断栈是否为空
int StackEmpty(SqStack S) {
// 栈顶为-1 → 空栈,返回1;否则返回0
return S.top == -1;
}
// 3. 判断栈是否为满
int StackFull(SqStack S) {
// 栈顶指向最后一个元素 → 栈满,返回1
return S.top == MaxSize - 1;
}
// 4. 入栈操作(进栈)
int Push(SqStack *S, int e) {
// 栈满,入栈失败
if (StackFull(*S))
return 0;
// 栈顶指针先+1,再赋值
S->data[++S->top] = e;
// 入栈成功
return 1;
}
// 5. 出栈操作(删除栈顶元素,用e返回值)
int Pop(SqStack *S, int *e) {
// 栈空,出栈失败
if (StackEmpty(*S))
return 0;
// 先取栈顶元素,栈顶指针再-1
*e = S->data[S->top--];
// 出栈成功
return 1;
}
// 6. 取栈顶元素(不删除)
int GetTop(SqStack S, int *e) {
// 栈空,取元素失败
if (StackEmpty(S))
return 0;
*e = S.data[S.top];
// 取元素成功
return 1;
}
// 7. 销毁栈(静态栈无需free,仅重置指针即可)
void DestroyStack(SqStack *S) {
S->top = -1;
}
// 主函数:测试栈的所有操作(考研答题可写可不写,建议写上)
int main() {
// 定义一个栈
SqStack S;
// 初始化栈
InitStack(&S);
// 测试入栈
Push(&S, 10);
Push(&S, 20);
Push(&S, 30);
printf("入栈元素:10,20,30\n");
// 测试取栈顶
int topElem;
GetTop(S, &topElem);
printf("当前栈顶元素:%d\n", topElem);
// 测试出栈
int e;
Pop(&S, &e);
printf("出栈元素:%d\n", e);
GetTop(S, &topElem);
printf("出栈后栈顶元素:%d\n", topElem);
// 测试判空
if (StackEmpty(S))
printf("栈为空\n");
else
printf("栈不为空\n");
// 销毁栈
DestroyStack(&S);
return 0;
}
共享栈
基本概念
共享栈是一种利用同一块内存空间实现两个栈的数据结构。两个栈的栈底分别位于共享内存空间的两端,栈顶向中间生长。当两个栈顶相遇时,表示栈空间已满。
核心特性
空间高效利用
共享栈通过让两个栈共享同一块内存区域,避免了单独分配两块内存可能导致的浪费。尤其当两个栈的实际使用空间动态变化时,能更灵活地利用内存。
双向生长
两个栈的栈顶分别从数组的两端向中间延伸。栈A从下标0开始向高位生长,栈B从下标n-1开始向低位生长。
溢出条件
共享栈的溢出条件为两个栈顶相遇(即top1 + 1 >= top2)。此时无论向哪个栈压入元素都会导致栈满。
代码实现
#include <stdio.h>
#include <stdlib.h>
// 共享栈最大容量(可根据需求修改)
#define MaxSize 10
// 共享栈结构体定义(408标准写法)
typedef struct {
// 共享存储空间
int data[MaxSize];
// 栈1栈顶指针
int top1;
// 栈2栈顶指针
int top2;
} ShareStack;
//==================== 基本操作 ====================//
// 1. 初始化共享栈
void InitStack(ShareStack *S) {
// 栈1空栈状态
S->top1 = -1;
// 栈2空栈状态
S->top2 = MaxSize;
}
// 2. 判断共享栈是否为空
// flag=1 判栈1空;flag=2 判栈2空
int StackEmpty(ShareStack S, int flag) {
if (flag == 1) {
// 栈1空返回1
return S.top1 == -1;
} else if (flag == 2) {
// 栈2空返回1
return S.top2 == MaxSize;
}
// 非法标记
return -1;
}
// 3. 判断共享栈是否满
int StackFull(ShareStack S) {
// 标准判满条件
return S.top1 + 1 == S.top2;
}
// 4. 入栈操作
// flag=1 入栈1;flag=2 入栈2
int Push(ShareStack *S, int flag, int e) {
// 栈满,入栈失败
if (StackFull(*S)) {
printf("共享栈已满,入栈失败!\n");
return 0;
}
if (flag == 1) {
// 栈1:先移动指针,再入栈
S->data[++S->top1] = e;
} else if (flag == 2) {
// 栈2:先移动指针,再入栈
S->data[--S->top2] = e;
} else {
printf("栈编号错误!\n");
return 0;
}
return 1;
}
// 5. 出栈操作
// flag=1 出栈1;flag=2 出栈2,e保存出栈元素
int Pop(ShareStack *S, int flag, int *e) {
if (flag == 1) {
// 栈1空
if (S->top1 == -1) {
printf("栈1为空,出栈失败!\n");
return 0;
}
// 先出栈,再移动指针
*e = S->data[S->top1--];
} else if (flag == 2) {
// 栈2空
if (S->top2 == MaxSize) {
printf("栈2为空,出栈失败!\n");
return 0;
}
// 先出栈,再移动指针
*e = S->data[S->top2++];
} else {
printf("栈编号错误!\n");
return 0;
}
return 1;
}
// 6. 获取栈顶元素(不删除)
int GetTop(ShareStack S, int flag, int *e) {
if (flag == 1) {
if (S.top1 == -1) return 0;
*e = S.data[S.top1];
} else if (flag == 2) {
if (S.top2 == MaxSize) return 0;
*e = S.data[S.top2];
} else return 0;
return 1;
}
//==================== 测试主函数 ====================//
int main() {
ShareStack S;
// 初始化
InitStack(&S);
int e;
// 栈1入栈
Push(&S, 1, 10);
Push(&S, 1, 20);
Push(&S, 1, 30);
// 栈2入栈
Push(&S, 2, 100);
Push(&S, 2, 200);
// 栈1出栈
Pop(&S, 1, &e);
printf("栈1出栈元素:%d\n", e);
// 栈2出栈
Pop(&S, 2, &e);
printf("栈2出栈元素:%d\n", e);
// 获取栈顶
GetTop(S, 1, &e);
printf("栈1栈顶元素:%d\n", e);
GetTop(S, 2, &e);
printf("栈2栈顶元素:%d\n", e);
return 0;
}
链栈
基本概念
链栈是一种基于链表实现的栈结构,采用动态内存分配方式存储数据。与顺序栈不同,链栈无需预先定义固定容量,通过节点间的指针链接实现元素的入栈和出栈操作。链栈的栈顶通常为链表的头节点,操作均在链表头部完成,保证时间复杂度为 O(1)。
链栈的核心特性
动态扩展性
链栈的存储空间随需求动态分配,无需担心栈满问题(除非内存耗尽)。
高效操作
入栈和出栈仅需修改头节点指针,无需移动其他元素,操作效率稳定。
内存灵活性
每个节点独立分配内存,可分散存储,但需额外空间存储指针。
实现关键
- 栈顶指针指向链表头节点。
- 空栈时,栈顶指针为
NULL。 - 节点结构包含数据域和指向下一节点的指针域。
代码实现
带头结点的链栈
#include <stdio.h>
#include <stdlib.h>
// 链栈结点类型定义
typedef struct StackNode {
int data;
struct StackNode *next;
} StackNode, *StackPtr;
// 链栈(仅需栈顶指针,指向头结点)
typedef struct {
StackPtr top; // 栈顶指针,指向头结点
} LinkStack;
// 1. 初始化带头结点的链栈
int InitStack(LinkStack *S) {
// 创建头结点
S->top = (StackPtr)malloc(sizeof(StackNode));
if (S->top == NULL) return 0; // 内存分配失败
S->top->next = NULL; // 头结点后继为空
return 1;
}
// 2. 判断栈空
int StackEmpty(LinkStack S) {
// 头结点后无元素即为空
return S.top->next == NULL;
}
// 3. 入栈(头插法)
int Push(LinkStack *S, int e) {
StackPtr p = (StackPtr)malloc(sizeof(StackNode));
if (p == NULL) return 0;
p->data = e;
p->next = S->top->next; // 新结点指向原第一个元素
S->top->next = p; // 头结点指向新结点
return 1;
}
// 4. 出栈
int Pop(LinkStack *S, int *e) {
if (StackEmpty(*S)) return 0; // 栈空
StackPtr p = S->top->next; // p指向待删除结点
*e = p->data;
S->top->next = p->next; // 摘链
free(p);
return 1;
}
// 5. 取栈顶元素(不删除)
int GetTop(LinkStack S, int *e) {
if (StackEmpty(S)) return 0;
*e = S.top->next->data;
return 1;
}
// 6. 销毁栈
int DestroyStack(LinkStack *S) {
StackPtr p, q;
p = S->top;
while (p != NULL) {
q = p->next;
free(p);
p = q;
}
S->top = NULL;
return 1;
}
// 测试主函数
int main() {
LinkStack S;
int e;
InitStack(&S);
Push(&S, 10);
Push(&S, 20);
Push(&S, 30);
GetTop(S, &e);
printf("栈顶元素:%d\n", e);
Pop(&S, &e);
printf("出栈元素:%d\n", e);
GetTop(S, &e);
printf("新栈顶:%d\n", e);
printf("栈是否为空:%s\n", StackEmpty(S) ? "是" : "否");
DestroyStack(&S);
return 0;
}
不带头结点的链栈
#include <stdio.h>
#include <stdlib.h>
typedef struct StackNode {
int data;
struct StackNode *next;
} StackNode, *StackPtr;
typedef struct {
StackPtr top;
} LinkStack;
// 初始化
void InitStack(LinkStack *S) {
S->top = NULL;
}
// 判空
int StackEmpty(LinkStack S) {
return S.top == NULL;
}
// 入栈
int Push(LinkStack *S, int e) {
StackPtr p = (StackPtr)malloc(sizeof(StackNode));
if (!p) return 0;
p->data = e;
p->next = S->top;
S->top = p;
return 1;
}
// 出栈
int Pop(LinkStack *S, int *e) {
if (StackEmpty(*S)) return 0;
StackPtr p = S->top;
*e = p->data;
S->top = p->next;
free(p);
return 1;
}
// 取栈顶
int GetTop(LinkStack S, int *e) {
if (StackEmpty(S)) return 0;
*e = S.top->data;
return 1;
}
// 销毁栈
void DestroyStack(LinkStack *S) {
StackPtr p, q;
p = S->top;
while (p != NULL) {
q = p->next;
free(p);
p = q;
}
S->top = NULL; // 销毁后置空,避免野指针
}
// 测试
int main() {
LinkStack S;
int e;
InitStack(&S);
Push(&S, 10);
Push(&S, 20);
Push(&S, 30);
GetTop(S, &e);
printf("栈顶:%d\n", e);
Pop(&S, &e);
printf("出栈:%d\n", e);
DestroyStack(&S); // 用完销毁
return 0;
}
顺序循环队列
基本概念
顺序循环队列是一种基于数组实现的队列结构,通过模运算(取余)实现队列空间的循环利用。与普通顺序队列相比,其核心优势是避免“假溢出”(即队列未满但因数组尾部空间耗尽无法入队的问题)。
- 逻辑环形结构:物理上使用线性数组存储,但通过指针的循环移动模拟环形结构。
- 队头(Front)与队尾(Rear)指针:分别标记队列的第一个元素位置和下一个待插入位置。
- 空队列与满队列的判定:需通过指针位置关系区分,通常牺牲一个存储单元或使用标志位。
核心特性
空间复用
通过取模运算实现指针循环移动。当指针到达数组末尾时,通过 (rear + 1) % capacity 回到数组起始位置。
判空与判满条件
- 判空:
front == rear - 判满:
- 牺牲一个单元法:
(rear + 1) % capacity == front - 标志位法:额外维护一个布尔变量标记队列状态。
- 牺牲一个单元法:
时间复杂度
- 入队(Enqueue):O(1),仅修改
rear指针并赋值。 - 出队(Dequeue):O(1),仅修改
front指针。
代码实现
#include <stdio.h>
// 队列最大容量(考研固定写法)
#define MAXSIZE 100
// 循环队列结构体定义(408标准格式,一字不差)
typedef struct {
int data[MAXSIZE]; // 静态数组存放元素
int front; // 队头指针:指向队头元素
int rear; // 队尾指针:指向队尾元素的下一个位置
} SqQueue;
//==================== 基本操作(考研必考6大函数)====================
// 1. 初始化队列
void InitQueue(SqQueue *Q) {
Q->front = Q->rear = 0; // 初始指针均为0,队列为空
}
// 2. 判断队列是否为空
int QueueEmpty(SqQueue Q) {
// 空返回1,非空返回0
return Q.front == Q.rear;
}
// 3. 入队操作:队尾插入元素e,成功返回1,失败返回0
int EnQueue(SqQueue *Q, int e) {
// 队满判断(408标准条件)
if ((Q->rear + 1) % MAXSIZE == Q->front)
return 0;
Q->data[Q->rear] = e; // 元素存入队尾位置
Q->rear = (Q->rear + 1) % MAXSIZE; // 队尾指针循环后移
return 1;
}
// 4. 出队操作:删除队头元素,用e返回值,成功返回1,失败返回0
int DeQueue(SqQueue *Q, int *e) {
// 队空判断
if (Q->front == Q->rear)
return 0;
*e = Q->data[Q->front]; // 取出队头元素
Q->front = (Q->front + 1) % MAXSIZE; // 队头指针循环后移
return 1;
}
// 5. 读取队头元素(不删除)
int GetHead(SqQueue Q, int *e) {
if (Q.front == Q.rear)
return 0;
*e = Q.data[Q.front];
return 1;
}
// 6. 求队列长度(元素个数)
int QueueLength(SqQueue Q) {
return (Q.rear - Q.front + MAXSIZE) % MAXSIZE;
}
// 主函数:测试循环队列所有功能
int main() {
SqQueue Q;
InitQueue(&Q); // 初始化
// 入队测试
EnQueue(&Q, 10);
EnQueue(&Q, 20);
EnQueue(&Q, 30);
printf("队列长度:%d\n", QueueLength(Q)); // 输出 3
// 取队头
int head;
GetHead(Q, &head);
printf("队头元素:%d\n", head); // 输出 10
// 出队测试
int e;
DeQueue(&Q, &e);
printf("出队元素:%d\n", e); // 输出 10
printf("队列是否为空:%d\n", QueueEmpty(Q)); // 输出 0(非空)
return 0;
}
链式队列
基本概念
链式队列是一种基于链表实现的队列数据结构,采用动态内存分配方式存储元素。与顺序队列(数组实现)不同,链式队列无需预先分配固定大小的存储空间,通过节点间的指针链接实现元素的动态增删。
- 节点结构:每个节点包含数据域(存储元素)和指针域(指向下一节点)。
- 队首与队尾:通过两个指针(
front和rear)分别指向队列的首节点和尾节点,确保入队(enqueue)和出队(dequeue)操作的高效性。
核心特性
-
动态扩容
链式队列的容量理论上仅受系统内存限制,插入新元素时动态创建节点,无需考虑空间不足问题(假设内存充足)。 -
操作时间复杂度
- 入队操作:在队尾插入节点,时间复杂度为 O(1)。
- 出队操作:在队首删除节点,时间复杂度为 O(1)。
-
避免假溢出
顺序队列可能因数组空间固定导致“假溢出”(实际空间未满但无法插入),链式队列通过动态分配节点彻底解决此问题。 -
应用场景
适用于需要频繁动态增删数据的场景,如任务调度、消息缓冲等,尤其适合无法预估队列最大长度的场景。
代码实现
#include <stdio.h>
#include <stdlib.h>
// 链式队列结点结构
typedef struct QNode {
int data;
struct QNode *next;
} QNode;
// 链队列 头尾指针封装结构
typedef struct {
QNode *front; // 队头指针(指向头结点)
QNode *rear; // 队尾指针(指向最后一个数据结点)
} LinkQueue;
// 1. 初始化链队列(带头结点 标准初始化)
void InitQueue(LinkQueue *Q) {
// 建立头结点
QNode *head = (QNode *)malloc(sizeof(QNode));
head->next = NULL;
Q->front = head;
Q->rear = head;
}
// 2. 判断队列是否为空
int QueueEmpty(LinkQueue Q) {
// 带头结点判空条件:front == rear
return Q.front == Q.rear;
}
// 3. 入队操作 成功返回1,失败返回0
int EnQueue(LinkQueue *Q, int e) {
QNode *s = (QNode *)malloc(sizeof(QNode));
if (s == NULL)
return 0;
s->data = e;
s->next = NULL;
Q->rear->next = s; // 尾结点指向新结点
Q->rear = s; // rear后移
return 1;
}
// 4. 出队操作 成功返回1,用e带出队头元素
int DeQueue(LinkQueue *Q, int *e) {
if (QueueEmpty(*Q))
return 0;
QNode *p = Q->front->next; // p指向第一个数据结点
*e = p->data;
Q->front->next = p->next;
// 特殊:如果删除的是最后一个结点,rear要回指到头结点
if (Q->rear == p)
Q->rear = Q->front;
free(p);
return 1;
}
// 5. 获取队头元素(不删除)
int GetHead(LinkQueue Q, int *e) {
if (QueueEmpty(Q))
return 0;
*e = Q.front->next->data;
return 1;
}
// 6. 销毁队列(408拓展常考)
void DestroyQueue(LinkQueue *Q) {
QNode *p, *q;
p = Q->front;
while (p != NULL) {
q = p->next;
free(p);
p = q;
}
Q->front = Q->rear = NULL;
}
// 测试主函数
int main(void) {
LinkQueue Q;
InitQueue(&Q);
EnQueue(&Q, 1);
EnQueue(&Q, 2);
EnQueue(&Q, 3);
int val;
GetHead(Q, &val);
printf("队头元素:%d\n", val);
DeQueue(&Q, &val);
printf("出队元素:%d\n", val);
DestroyQueue(&Q);
return 0;
}更多推荐
所有评论(0)