栈和队列
1、栈和队列的定义和特点
- 栈和队列是两种常用的、重要的数据结构
- 栈和队列是限定插入和删除只能在表的“端点”的线性表
1. 特点
- 栈(stack):先入后出、后入先出(Last In First Out)(LIFO结构)
- 队列(queue):先入先出,后入后出(First In First Out)(FIFO结构)
2. 相关概念
- 栈:仅在表尾进行插入和删除的线性表
栈顶(Top):栈的表尾
栈底(Base):栈的表头
入(压)栈:插入元素到栈顶(push)
出(弹)栈:删除栈顶的元素(pop)
- 队列:仅在表尾插入,在表头删除的线性表
入队:在表尾插入元素
出队:在表头删除元素
2、栈的表示和操作的实现
1. 抽象数据类型定义
数据对象:
D = {ai | ai ∈ ElemSet, i = 1, 2,..., n, n >= 0}
数据关系:
R1 = {<ai-1, ai>|ai - 1, ai ∈ D, i = 2,..., n}
# 约定an端为栈顶,a1为栈底
基本操作:
# 初始化、压栈、弹栈、取栈顶元素等
2. 栈的表示
- 顺序栈:栈的顺序存储
利用数组依次存放自栈底到栈顶的数据元素,一般栈底存放低地址端
附设top指针指向栈顶,base指针指向栈底,stacksize表示栈可用的最大容量
注:为操作方便,通常top指向栈顶元素之上的下标地址
空栈:base == top
栈满:top - base == stacksize
上溢(overflow):top - base > stacksize
下溢(underflow):top - base < 0
注:一般认为上溢是一种错误,下溢是一种结束条件
#define MAX_SIZE 5
typedef struct Element
{
int num;
char string[10];
}Element;
typedef struct SqStack
{
Element* base; // 栈底指针
Element* top; // 栈顶指针
int stackSize; // 栈的最大容量
}SqStack;
-
链栈:栈的链式存储
利用单链表存放数据元素,只能在链表头部进行操作
栈的元素通过链表结点存储,通过栈顶指针指向栈顶,下一结点指向上一次存储的元素
注:该链表无需再设头结点
typedef struct StackNode
{
Element data;
struct StackNode* next;
}StackNode, * LinkStack;
3. 栈的操作
1. 顺序栈
- InitStack(SqStack* s)
- 申请一个容量为MAXSIZE的数组空间,base指向栈底
- top指向栈底,表示空栈
- stacksize设为MAXSIZE
Status InitSqStack(SqStack* s) { Element* p = (Element*)malloc(sizeof(Element) * MAX_SIZE); if (p) { s->base = p; s->top = s->base; s->stackSize = MAX_SIZE; return OK; } else { return ERROR; } }
-
IsEmpty(SqStack* s)
当top == base时,栈为空栈
Status IsEmpty(const SqStack* s) { return s->base == s->top ? TRUE : FALSE; }
-
GetLength(SqStack* s)
指针间的差值(top - base)为两指针中间的元素个数
int GetLength(const SqStack* s) { return s->top - s->base; }
-
CleanStack(SqStack* s)
将top重新置于base的位置,即令top = base
void CleanSqStack(SqStack* s) { s->top = s->base; }
-
DestroyStack(SqStack** s)
释放内存空间
void DestroySqStack(SqStack** sp) { if ((*sp)->base) { free((*sp)->base); *sp = NULL; } }
-
Push(SqStack* s, Element* e)
- 判断是否满栈,若满则报错,或再开辟空间
- 将新元素压栈,栈顶指针+1
#define APPEND_SIZE 5 Status SqPush(SqStack* s, Element* e) { // 判断 if (GetLength(s) == s->stackSize) { // 扩容 Element* p = realloc(s->base, sizeof(Element) * (s->stackSize + APPEND_SIZE)); if (p) { s->base = p; s->top = s->base + s->stackSize; p = NULL; s->stackSize += APPEND_SIZE; } else { return ERROR; } } // 压栈 memcpy((s->top)++, e, sizeof(Element)); return OK; }
-
Pop(SqStack* s, Element* e)
- 判断是否空栈,若空则报错
- 将元素弹栈,栈顶指针-1
Status SqPop(SqStack* s, Element* e) { // 判断 if (IsEmpty(s)) { return ERROR; } else { // 弹栈 memcpy(e, --(s->top), sizeof(Element)); return OK; } }
2. 链栈
-
InitStack(LinkStack* s)
构造一个空栈并将栈顶指针置为空
void InitLinkStack(LinkStack* s) { *s = NULL; }
-
IsLinkStackEmpty(LiinkStack s)
栈顶元素为空则为空表
Status IsLinkStackEmpty(const LinkStack s) { return s ? FALSE : TRUE; }
-
LinkStackPush(LinkStack* s, Element* data)
- 创建node结点,并将其next标记为s的指向
- 修改s的指向,使其指向栈顶结点
Status LinkStackPush(LinkStack* s, Element* data) { StackNode* p = (StackNode*)malloc(sizeof(StackNode)); if (p) { memcpy(&(p->data), data, sizeof(Element)); p->next = *s; *s = p; return OK; } else { return ERROR; } }
-
LinkStackPop(LinkStack* s, Element* data)
- 判断是否下溢
- 释放栈顶指针
- 修改s的指向
void LinkStackPop(LinkStack* s, Element* data) { if (s) { StackNode* p = *s; memcpy(data, &((*s)->data), sizeof(Element)); *s = (*s)->next; free(p); return OK; } // 下溢 else { return ERROR; } }
-
GetTop(LinkStack s, Element* top)
- 判断栈顶是否为空
- 取出栈顶数据域
void GetTop(const LinkStack s, Element* top) { if (s) { memcpy(top, &(s->data), sizeof(Element)); } else { return ERROR; } }
4. 栈与递归
-
递归:若一个对象部分地包含它自己,或用它自己给自己定义,则成这个对象是递归的结构
-
递归的使用场景
- 函数递归
// 阶乘
long Fact(long n)
{
assert(n >= 0);
if (n > 1)
{
return Fact(n - 1) * n;
}
else
{
return 1;
}
}
// 斐波那契数列
long Fib(long n)
{
assert(n >= 1);
if (n > 2)
{
return Fib(n - 2) + Fib(n - 1);
}
else
{
return 1;
}
}
- 具有递归特性的数据结构
二叉树、广义表......
- 可递归求解的问题
迷宫问题、汉诺塔问题......
-
递归思想:分而治之
-
递归必备条件:
- 能将一个问题转化为新问题,而新问题与原问题的解法相同或类同,即有一定规律
- 通过转化问题以实现简化
- 必须存在一个明确的递归出口
-
递归的过程:
调用(压栈) -> 运算 -> 返回(弹栈)
-
递归->非递归:
由于递归相对时间开销大,故可以将递归问题转化为非递归问题解决
- 尾递归、单项递归 ==> 循环
- 自用栈模拟系统运行时栈
3、队列的表示和操作的实现
队列(Queue)特点:在队尾(Rear)插入元素,在队头(Front)删除元素
1. 抽象数据类型定义
数据对象:
D = {ai | ai ∈ ElemSet, i = 1, 2,..., n, n >= 0}
数据关系:
R1 = {<ai-1, ai>|ai - 1, ai ∈ D, i = 2,..., n}
# 约定a1端为队头,an为栈队尾
基本操作:
# 初始化、入队、出队、遍历等
2. 队列的表示
- 顺序队列
typedef struct SqQueue
{
Data* base; // 初始化的动态内存空间
int front; // 队头下标
int rear; // 队尾下标
}SqQueue;
- 链队列
// 链表结点
typedef struct QNode
{
Data data;
struct QNode* next;
}QNode, * QueuePtr;
// 链队
typedef struct LinkQueue
{
QueuePtr front;
QueuePtr rear;
}LinkQueue;
3. 队列的操作
1. 顺序队列
a. 空队列:rear = front = 0
b. 入队:rear对应的元素赋值,rear++
c. 出队:front++
d. 溢出分析:
- 真溢出:front = 0 && rear == MAXQSIZE
- 假溢出:front != 0 && rear == MAXQSIZE
e. 假溢出解决方案:
- 出队时所有元素向前移动(时间效率差)
- 循环队列:当假溢出,令rear = 0,即重新利用之前的空间
f. 队空和队满
- 设置标识符
- 少用一个元素空间,即队空时队尾与队头重合,队满时队尾+1与队头重合
- InitSqQueue(SqQueue* q)
- 分配base空间,base指向数组首地址
- 头指针和尾指针置零,表示队空
Status InitSqQueue(SqQueue* q) { q->base = (Data*)malloc(sizeof(Data) * MAXQSIZE); if (q->base) { q->front = 0; q->rear = 0; return OK; } else { return ERROR; } }
-
GetSqQueueLength(SqQueue* q)
int GetSqQueueLength(const SqQueue* q) { return (q->rear - q->front + MAXQSIZE) % MAXQSIZE; }
注:由于采用循环队列,故不可之间通过指针相减计算长度
-
IsSqQueueFull(SqQueue* q)
若尾指针+1等于头指针则判断其为队满
Status IsSqQueueFull(const SqQueue* q) { return (q->rear + 1) % MAXQSIZE == q->front ? TRUE : FALSE; }
-
IsSqEmpty(SqQueue* q)
若尾指针等于头指针则判断其为队空
Status IsSqQueueEmpty(const SqQueue* q) { return q->rear == q->front ? TRUE : FALSE; }
-
EnSqQueue(SqQueue* q, Data* data)
- 判读是否队满
- 新元素加入队尾,队尾指针+1
Status EnSqQueue(SqQueue* q, Data* data) { if (IsSqQueueFull(q)) return ERROR; memcpy(q->base + q->rear, data, sizeof(Data)); q->rear = (q->rear + 1) % MAXQSIZE; return OK; }
-
DeSqQueue(SqQueue* q, Data* data)
- 判断是否队空
- 拷贝队头指针所指数据,队头指针+1
Status DeSqQueue(SqQueue* q, Data* data) { if (IsSqQueueEmpty(q)) return ERROR; memcpy(data, q->base + q->front, sizeof(Data)); q->front = (q->front + 1) % MAXQSIZE; return OK; }
-
GetFront(SqQueue* q, Data* data)
Status GetFront(const SqQueue* q, Data* data) { if (IsSqQueueEmpty(q)) return ERROR; memcpy(data, q->base + q->front, sizeof(Data)); return OK; }
2. 链队
a. 空队列:rear == front
b. 入队:rear->next = x
c. 出队:free(front)
-
InitLinkQueue(LinkQueue* q)
- 创建头结点
- 首尾指针均指向该头结点
Status InitLinkQueue(LinkQueue* q) { QueuePtr p = (QueuePtr)malloc(sizeof(QNode)); if (p) { p->next = NULL; q->front = p; q->rear = p; return OK; } else { return ERROR; } }
-
DestroyLinkQueue(LinkQueue* q)
从头依次释放所有结点
void DestroyLinkQueue(LinkQueue* q) { QNode* p = q->front; QNode* tmp; while (p) { tmp = p; p = p->next; free(tmp); } q->front = NULL; q->rear = NULL; }
-
EnLinkQueue(LinkQueue* q, Data* data)
- 创建结点
- 将结点添加至队尾,队尾指针指向该结点
Status EnLinkQueue(LinkQueue* q, Data* data) { QNode* node = (QNode*)malloc(sizeof(QNode)); if (node) { memcpy(&(node->data), data, sizeof(Data)); node->next = NULL; q->rear->next = node; q->rear = q->rear->next; return OK; } else { return ERROR; } }
-
DeLinkQueue(LinkQueue* q, Data* data)
- 判断是否为空
- 拷贝结点
- 将队头指针指向next结点,原结点释放
Status DeLinkQueue(LinkQueue* q, Data* data) { if (IsLinkQueueEmpty(q)) { return ERROR; } else { QNode* p = q->front->next; memcpy(data, &(p->data), sizeof(Data)); q->front->next = p->next; if (q->rear == p) { q->rear = q->front; } free(p); return OK; } }
注:当出队至队列清空(即删除元素为队尾结点),则需使队头和队尾指针相等,以视为空表
-
GetFront(LinkQueue* q, Data* data)
Status GetFront_link(LinkQueue* q, Data* data) { if (IsLinkQueueEmpty(q)) { return ERROR; } else { memcpy(data, &(q->front->next->data), sizeof(Data)); return OK; } }