栈和队列
1.栈
1.1栈的认识
栈是一种线性表,栈中每个元素只有一个前驱和一个后继。栈只能在表尾插入和删除数据,遵循 LIFO(Last In First Out,后进先出)。进行数据插入和删除的一端叫做栈顶(top),另一端叫做栈底(bottom)。

注意
- 如果栈用数组实现,栈底指向栈中第一个数据的位置,栈顶指向栈中最后一个数据的下一个位置,如上图。
- 栈中没有数据叫做空栈。
1.2栈的基本操作
- 栈可以用数组和链表来实现,但数组的结构实现更优一些。栈是后进先出,如果用链表实现,我们得再链表尾部插入数据,删除数据,用数组来直接访问栈顶位置,插入数据或删除数据即可。所以数据在插入和删除数据更优。
- 选择数组来实现栈,又会出现静态栈(空间大小是固定的)和动态栈顶(空间是可扩容的)。静态栈还是会出现老问题:空间不够或则空间浪费。所以最终选择动态栈。
数组实现
- 定义
typedef int STElemType;//方便更换数据类型
typedef struct Stack
{
STElemType* a;
int top;//栈顶元素的下一个元素的下标
int capacity;//容量
}ST;
- 初始化
void STInit(ST* ps)
{
assert(ps);
ps->a = (STElemType*)malloc(sizeof(STElemType) * 5);//假设初始容量是5
if (ps->a == NULL)
{
perror("malloc fail");
return;
}
ps->capacity = 5;
ps->top = 0;//top是栈顶元素的下一个位置的下标
}
- 销毁
//销毁
void STDestroy(ST* ps)
{
assert(ps);
free(ps->a);
ps->a = NULL;
ps->capacity = 0;
ps->top = 0;
}
- 压栈
void STPush(ST* ps, STElemType x)
{
assert(ps);
//考虑栈是否满了
if (ps->top == ps->capacity)//top指向栈顶下一个元素的下标,当栈满后,top等于容量
{
//扩容(容量为原来的两倍)
STElemType* tmp = (STElemType*)realloc(sizeof(STElemType) * ps->capacity * 2);
if (tmp == NULL)
{
perror("realloc fail");
return;
}
ps->a = tmp;
ps->capacity *= 2;
}
ps->a[ps->top] = x;
ps->top++;
}
注意
用tmp接受新空间的地址而不用ps->a接受,是因为防止空间申请失败,返回NULL,如果用ps->a接受新空间地址且申请失败,就失去旧空间的地址,会造成内存泄漏。
- 判断栈空
bool STEmpty(ST* ps)
{
return ps->top == 0;//top等于0,返回真,说明栈空
}
- 出栈
void STPop(ST* ps)
{
assert(ps);
//考虑栈是否空
if (STEmpty(ps))
{
printf("栈空\n");
return;
}
ps->top--;//这就是用数组实现栈的好处,只需将top往前挪一位,不用删除数据
}
- 栈的大小
int STSize(ST* ps)
{
assert(ps);
return ps->top;
}
- 栈顶元素
STElemType STTop(ST* ps)
{
assert(ps);
if (STEmpty(ps))
{
printf("栈空\n");
return 0;
}
return ps->a[ps->top-1];
}
- 例子

2.队列
2.1队列的认识
队列也是一种线性表,队列中每个元素只有一个前驱和一个后继。不同于栈,队列只在表尾插入,在表头删除,遵循FIFO(First In First Out,先进先出)。进行数据插入的一端叫做队尾(rear),进行数据删除的一端叫做队头(front)。

2.2队列的基本操作
队列也可以用数组和链表实现,但是用链表的结构实现更优。用数组删除数据,其实是删除数组的第一个元素,出队列后如果不挪动数据,那么只是把队头位置往后移,那么这个位置就会浪费,如果挪动数据,那么就需要将后面的数据往前移,这样的话效率太低了,但后面的循环队列就可以用数组实现且完美解决这个问题。现在我选择用链表实现队列。
链表实现
- 定义
typedef int QElemType;//队列数据类型
typedef struct QueueNode//队列的结点
{
QElemType data;
struct QueueNode* next;
}QNode;
typedef struct Queue//队列
{
QueueNode* front;//用于头删
QueueNode* rear;//用于尾插
int size;//记录队列元素个数
}Queue;
- 初始化
void QueueInit(Queue* pq)
{
assert(pq);
pq->front = pq->rear = NULL;
pq->size = 0;
}
- 销毁
void QueueDestroy(Queue* pq)
{
assert(pq);
QNode* cur = pq->front;
while (cur)
{
QNode* tmp = cur->next ;
free(cur);
cur = tmp;
}
pq->front = pq->rear = NULL;
pq->size = 0;
}
- 入队
QNode* BuyNode(QElemType x)
{
QNode* newnode = (QNode*)malloc(sizeof(QNode));
if (newnode == NULL)
{
perror("malloc fail");
return;
}
newnode->data = x;
newnode->next = NULL;
return newnode;
}
void QueuePush(Queue* pq,QElemType x)
{
assert(pq);
//申请一个新的节点
QNode* newnode = BuyNode(x);
//还要考虑第一次入队时,front和rear都指向NULL的情况
if (pq->size == 0)
{
pq->front = pq->rear = newnode;
pq->size++;
}
else
{
pq->rear->next = newnode;
pq->rear = pq->rear->next;
pq->size++;
}
}
- 出队
void QueuePop(Queue* pq)
{
assert(pq);
//先判断队列是否为空
assert(!QueueEmpty(pq));
//这里还要考虑特殊情况:如果队列只有一个元素,释放掉队头后,front会指向NULL,而rear还是指向已释放的空间
if (pq->front == pq->rear)
{
free(pq->front);
pq->front = pq->rear = NULL;
}
else
{
QNode* next = pq->front->next;
free(pq->front);
pq->front = next;
}
pq->size--;
}
- 队列大小
//队列大小
int QueueSize(Queue* pq)
{
assert(pq);
return pq->size;
}
- 取队头元素
QElemType QueueFront(Queue* pq)
{
assert(pq);
//若队列为空,报错,防止越界访问
assert(!QueueEmpty(pq));
return pq->front->data;
}
- 取队尾元素
QElemType QueueBack(Queue* pq)
{
assert(pq);
assert(!QueueEmpty(pq));
return pq->rear->data;
}
2.3循环队列(LeetCode622)
循环队列是用数组实现,可以充分利用储存空间。OJ链接

代码
//定义
typedef struct
{
int* a;
int front;
int rear;
int k;//要存储的数据个数
}MyCircularQueue;
//初始化
MyCircularQueue* myCircularQueueCreate(int k)
{
MyCircularQueue* q = (MyCircularQueue*)malloc(sizeof(MyCircularQueue));
q->a = (int*)malloc(sizeof(int) * (k + 1));//得多申请一个,用来分辨队满和队空
if (q->a == NULL)
{
perror("malloc");
return;
}
q->front = q->rear = 0;
q->k = k;
return q;
}
//判断是否已满
bool myCircularQueueIsFull(MyCircularQueue* obj)
{
return (obj->rear + 1) % (obj->k + 1) == obj->front;
}
//入队
bool myCircularQueueEnQueue(MyCircularQueue* obj, int value)
{
//先判断队列是否已满
if (myCircularQueueIsFull(obj))
{
return false;
}
obj->a[obj->rear] = value;
obj->rear = (obj->rear + 1) % (obj->k + 1);
return true;
}
//判断队空
bool myCircularQueueIsEmpty(MyCircularQueue* obj) {
return obj->front == obj->rear;
}
//出队
bool myCircularQueueDeQueue(MyCircularQueue* obj) {
//先判断队空
if (myCircularQueueIsEmpty(obj))
{
return false;
}
obj->front = (obj->front + 1) % (obj->k + 1);//直接往前挪一步就可以了
return true;
}
//取队头元素
int myCircularQueueFront(MyCircularQueue* obj)
{
//先判断对列是否为空
if (myCircularQueueIsEmpty(obj))
{
return -1;
}
return obj->a[obj->front];
}
//取得队尾元素
int myCircularQueueRear(MyCircularQueue* obj)
{
//先判断队列是否为空
if (myCircularQueueIsEmpty(obj))
{
return -1;
}
return obj->a[(obj->rear - 1 + obj->k + 1)%(obj->k +1)];//要考虑rear=0的情况
}
//释放
void myCircularQueueFree(MyCircularQueue* obj)
{
free(obj->a);
obj->front = obj->rear = obj->k = 0;
free(obj);
}
3.练习
学习了这么多栈和队列的基本操作,是时候让它们上场。
3.1判断回文
回文是指正读、反读均相同的字符序列,如"abba"和"abdba"均是回文,但"good"不是回文。试设计算法判定给定的字符序列是否为回文。(提示:将一半字符入栈)
代码实现
int IsPalin(ST*ps,char* s)
{
int len = strlen(s);
int half = len / 2;
//压栈
int i = 0;
for (i = 0; i < half; i++)
{
STPush(ps, *s);
s++;
}
//判断字符串长度是否是奇数
if (len % 2)
{
s++;//是奇数就跳过中间的字符
}
//判断回文
while (*s)
{
STDataType tmp = STTop(&ps);
STPop(&ps);
if (tmp != *s)
{
return 0;
}
s++;
}
return 1;
}
3.2有效的括号(LeetCode 20)
给定一个只包括 ‘(’,‘)’,‘{’,‘}’,‘[’,‘]’ 的字符串 s ,判断字符串是否有效。OJ链接
有效字符串需满足:
- 左括号必须用相同类型的右括号闭合。
- 左括号必须以正确的顺序闭合。
- 每个右括号都有一个对应的相同类型的左括号

思路
遇到左括号就入栈,遇到右括号就将左括号出栈,判断左括号是否和右括号匹配。
代码实现
bool isValid(char* s)
{
ST st;
STInit(&st);
while (*s)//当字符为/0时,停止
{
if (*s == '(' || *s == '[' || *s == '{')
{
STPush(&st, *s);//栈中元素类型是char,记得修改STElemType
}
else
{
//先判断是否为空,如果栈为空,说明一上来就右括号或者左括号已经匹配完了,那就不匹配
if (STEmpty(&st))
{
STDestroy(&st);//记得将栈释放,防止内存泄漏
return false;
}
QElemType top = STTop(&st);
STPop(&st);
//判断是否匹配
if ((*s == ')' && top != '(') || (*s == ']' && top != '[') || (*s == '}' && top != '{'))
{
STDestroy(&st);
return false;
}
}
s++;//记得将字符指向下一个
}
//还要考虑如果栈中还有多余的左括号,那么就说明不匹配
if (!STEmpty(&st))
{
STDestroy(&st);
return false;
}
STDestroy(&st);
return true;
}
3.3用队列实现栈(LeetCode 225)
请你仅使用两个队列实现一个后入先出(LIFO)的栈,并支持普通栈的全部四种操作(push、top、pop 和 empty)。
实现 MyStack 类:
- void push(int x) 将元素 x 压入栈顶。
- int pop() 移除并返回栈顶元素。
- int top() 返回栈顶元素。
- bool empty() 如果栈是空的,返回 true ;否则,返回 false 。
思路
- 用两个队列来实现栈,保持一个队列为空,一个队列存放数据
- 因为队列遵循“先进先出”,栈遵循“后进先出”。入栈时,将数据压入有数据的队列(如果两个队列都没有数据,就任选一个)。出栈时,将前面的所有数据压入另一个队列,只保留一个元素在当前队列中,此时出队就相当于出栈,满足后进先出。

代码实现
//定义
typedef struct {
Queue q1;
Queue q2;
} MyStack;
//初始化
MyStack* myStackCreate() {
MyStack* S = (MyStack*)malloc(sizeof(MyStack));
if (S == NULL)
{
perror("malloc fail");
return;
}
QInit(&S->q1);
QInit(&S->q2);
return S;
}
//插入
void myStackPush(MyStack* obj, int x) {
//将数据压入有数据的队列中
//所以要找出有数据的队列
if (!QEmpty(&obj->q1))
{
QPush(&obj->q1, x);
}
else
{
QPush(&obj->q2, x);
}
}
//判断栈是否为空
bool myStackEmpty(MyStack* obj)
{
return QEmpty(&obj->q1) && QEmpty(&obj->q2);//两个队列都为空,栈才为空
}
//出栈
int myStackPop(MyStack* obj)
{
//先判断两个队列是否都为空,为空就报错
assert(!myStackEmpty(obj));
//找出两个队列中的空队列和非空队列
Queue* EmptyQ = &obj->q1;//假设q1为空,q2非空
Queue* NoEmptyQ = &obj->q2;
//接下来确定空队列和非空队列
if (QEmpty(&obj->q2))
{
EmptyQ = &obj->q2;
NoEmptyQ = &obj->q1;
}
//将非空队列的前n-1个元素入队到空队列
while (QueueSize(NoEmptyQ) > 1)//非空队列只剩下一个元素就停止
{
QPush(EmptyQ, QueueFront(NoEmptyQ));
QueuePop(NoEmptyQ);
}
//接下来取非空队列的最后一个元素,就相当于取栈顶的元素,顺便出栈
int top = QueueFront(NoEmptyQ);
QueuePop(NoEmptyQ);
return top;
}
//取栈顶元素
int myStackTop(MyStack* obj)
{
//先判断两个队列是否都为空
assert(!MyStackEmpty(obj));
//取栈顶元素就相当于取非空队列的队尾元素
Queue* EmptyQ = &obj->q1;
Queue* NoEmptyQ = &obj->q2;
if (QEmpty(&obj->q2))
{
EmptyQ = &obj->q2;
NoEmptyQ = &obj->q1;
}
return QueueBack(NoEmptyQ);
}
//释放
void myStackFree(MyStack* obj) {
QueueDestroy(&obj->q1);
QueueDestroy(&obj->q2);
free(obj);
obj = NULL;
}
3.4用栈实现队列(LeetCode232)
请你仅使用两个栈实现先入先出队列。队列应当支持一般队列支持的所有操作(push、pop、peek、empty):
实现 MyQueue 类:
- void push(int x) 将元素 x 推到队列的末尾
- int pop() 从队列的开头移除并返回元素
- int peek() 返回队列开头的元素
- boolean empty() 如果队列为空,返回 true ;否则,返回 false
思路
- 一个栈(PushStack)用来压入数据,一个栈(PopStack)用来弹出数据。
- 入队时,选择PushStack压入数据。当需要出队时,如果PopStack为空栈,就将PushStack的数据压入PopStack,然后PopStack出栈就相当于出队;如果PopStack不为空栈,那么就直接出栈。

代码实现
//定义
typedef struct
{
ST pushst;
ST popst;
}MyQueue;
//初始化
MyQueue* myQueueCreate()
{
MyQueue* mq = (MyQueue*)malloc(sizeof(MyQueue));
if (mq == NULL)
{
perror("malloc fail");
return;
}
STInit(&mq->popst);
STInit(&mq->pushst);
return mq;
}
//压栈
void myQueuePush(MyQueue* obj, int x)
{
STPush(&obj->pushst, x);
}
//判断栈空
bool myQueueEmpty(MyQueue* obj)
{
return STEmpty(&obj->popst) && STEmpty(&obj->pushst);//两个栈为空,队列才为空
}
//出栈
int myQueuePop(MyQueue* obj)
{
//先判断两个栈是否为空
assert(!myQueueEmpty(obj));
//如果popst为空,那么将pushst的数据压入popst,再在popst出栈
//如果popst非空,那么直接在popst出栈
if (STEmpty(&obj->popst))
{
while (!STEmpty(&obj->pushst))
{
STPush(&obj->popst, STTop(&obj->pushst));
STPop(&obj->pushst);
}
}
int front = STTop(&obj->popst);
STPop(&obj->popst);
return front;
}
//返回队头元素
int myQueuePeek(MyQueue* obj)
{
//先判断队列是否为空
assert(!myQueueEmpty(obj));
//如果popst没有数据就将pushst的数据导入popst
if (STEmpty(&obj->popst))
{
while (!STEmpty(&obj->pushst))
{
STPush(&obj->popst, STTop(&obj->pushst));
STPop(&obj->pushst);
}
}
return STTop(&obj->popst);
}
//释放队列
void myQueueFree(MyQueue* obj) {
STDestroy(&obj->popst);
STDestroy(&obj->pushst);
free(obj);
obj = NULL;
}
4814

被折叠的 条评论
为什么被折叠?



