栈和队列
目录
一、栈的概念及结构
1.1.栈的概念
一种特殊的线性表
1.2.栈的特性
只允许固定的一端进行插入与删除元素的操作
**栈顶:**进行数据插入与删除的一端
**栈底:**栈顶的另一端
栈中的数据遵循后进先出LIFO(Last In First Out)的原则
**压栈:**栈的插入操作,在栈顶
**出栈:**栈的删除操作,在栈顶
类似于向羽毛球桶取放羽毛球

1.3.栈的三种实现结构
**顺序表:**最后一个元素设为栈顶

**双向链表:**最后一个节点设为栈顶

**单链表:**第一个节点设为栈顶

二、动态顺序表实现栈
2.1.栈的文件结构
- 头文件(Stack.h):栈的结构创建,栈的方法声明
- 源文件(Stack.c):栈的方法实现
- 测试文件(test.c):测试数据结构的方法
2.2.头文件编写
2.2.1.头文件包含
cpp
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <assert.h>
2.2.2.数组元素重命名
cpp
typedef int STDataType;
2.2.3.栈结构的定义
cpp
typedef struct Stack
{
STDataType* a;//顺序表指针
int top;//栈顶元素下标
int capacity;//空间大小
}ST;
2.2.4.栈的初始化
cpp
void STInit(ST* pst);
2.2.5.栈的销毁
cpp
void STDestroy(ST* pst);
2.2.6.压栈
cpp
void STPush(ST* pst, STDataType x);
2.2.7.出栈
cpp
void STPop(ST* pst);
2.2.8.获取栈顶数据
cpp
STDataType STTop();
2.2.9.判断栈是否为空
cpp
bool STEmpty(ST* pst);
2.2.10.计算当前数据个数
cpp
int STSize(ST* pst);
2.3.源文件编写
2.3.1.头文件包含
cpp
#include "Stack.h"
2.3.2.栈的初始化
cpp
void STInit(ST* pst)
{
assert(pst);
pst->a = NULL;
pst->top = pst->capacity = 0;
}
注:
如果top初值为0,则top永远指向栈顶数据的下一个位置(该值与当前栈中数据个数相同)
如果top初值为-1,则top永远指向栈顶数据的位置

2.3.3.栈的销毁
cpp
void STDestroy(ST* pst)
{
assert(pst);
free(pst->a);
pst->a = NULL;
pst->top = pst->capacity = 0;
}
2.3.4.压栈
cpp
void STPush(ST* pst, STDataType x)
{
assert(pst);
if (pst->top == pst->capacity)
{
int newcapacity = pst->capacity == 0 ? 4 : pst->capacity * 2;
STDataType* tmp = (STDataType*)realloc(pst->a, newcapacity * sizeof(STDataType));
if (tmp == NULL)
{
perror("realloc fail!");
return;
}
pst->a = tmp;
pst->capacity = newcapacity;
}
pst->a[pst->top] = x;
pst->top++;
}
2.3.5.出栈
cpp
void STPop(ST* pst)
{
assert(pst);
assert(pst->top > 0);
pst->top--;
}
2.3.6.获取栈顶数据
cpp
STDataType STTop(ST* pst)
{
assert(pst);
assert(pst->top > 0);
return pst->a[pst->top - 1];
}
2.3.7.判断栈是否为空
cpp
bool STEmpty(ST* pst)
{
assert(pst);
return pst->top == 0;
}
2.3.8.计算当前数据个数
cpp
int STSize(ST* pst)
{
assert(pst);
return pst->top;
}
2.4.测试文件编写
2.4.1.测试文件01
cpp
void Test01()
{
/*创建栈的结构*/
ST s;
/*栈的初始化*/
STInit(&s);
/*压栈*/
STPush(&s, 1);
STPush(&s, 2);
STPush(&s, 3);
/*出栈*/
STPop(&s);
STPop(&s);
/*栈顶元素*/
printf("%d\n", STTop(&s));
/*栈的打印*/
while(!STEmpty(&s))
{
printf("%d\n",STTop(&s));
STPop(&s);//只有把当前栈顶元素拿到才能访问下一个
}
/*栈的销毁*/
STDestroy(&s);
}
int main()
{
Test01();
return 0;
}
三、与栈有关的试题
试题1:有效的括号
题目内容:
给定一个只包括
'(',')','{','}','[',']'的字符串s,判断字符串是否有效有效字符串需满足:
左括号必须用相同类型的右括号闭合
左括号必须以正确的顺序闭合
每个右括号都有一个对应的相同类型的左括号
示例1:
输入:s = "()"
输出:true
示例2:
输入:s = "([)]"
输出:false
思路解析:
如果是左括号就入栈,遇到右括号时,将栈顶元素与右括号匹配
如果匹配成功,判断栈中是否有多余左括号,返回判断的布尔值
如果匹配失败,就直接销毁栈
代码部分:
cpp
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>
typedef char STDataType;
typedef struct Stack
{
STDataType* a;
int top;
int capacity;
}ST;
void STInit(ST* pst)
{
assert(pst);
pst->a = NULL;
pst->top = pst->capacity = 0;
}
void STDestroy(ST* pst)
{
assert(pst);
free(pst->a);
pst->a = NULL;
pst->top = pst->capacity = 0;
}
void STPush(ST* pst, STDataType x)
{
assert(pst);
if (pst->top == pst->capacity)
{
int newcapacity = pst->capacity == 0 ? 4 : pst->capacity * 2;
STDataType* tmp = (STDataType*)realloc(pst->a, newcapacity * sizeof(STDataType));
if (tmp == NULL)
{
perror("realloc fail!");
return;
}
pst->a = tmp;
pst->capacity = newcapacity;
}
pst->a[pst->top] = x;
pst->top++;
}
void STPop(ST* pst)
{
assert(pst);
assert(pst->top > 0);
pst->top--;
}
STDataType STTop(ST* pst)
{
assert(pst);
assert(pst->top > 0);
return pst->a[pst->top - 1];
}
bool STEmpty(ST* pst)
{
assert(pst);
return pst->top == 0;
}
bool isValid(char* s) {
ST st;
STInit(&st);
while(*s)
{
if(*s == '(' || *s == '[' || *s =='{')
{
STPush(&st,*s);
}
else
{
if(STEmpty(&st))
{
return false;
}
char top = STTop(&st);
STPop(&st);
if((top == '('&&*s != ')')
||(top == '{'&&*s != '}')
||(top == '['&&*s != ']'))
{
STDestroy(&st);
return false;
}
}
++s;
}
/*如果栈不为空,说明左括号比右括号多*/
bool ret = STEmpty(&st);
STDestroy(&st);
return ret;
}
四、队列的概念及结构
4.1.队列的概念
一种特殊的线性表
4.2.队列的特性
只允许在一端插入数据,在另一端删除数据
入队列: 进行插入操作的一端为队尾
出队列: 进行删除操作的一端为队头
队列中的数据遵循先进先出(First In First Out)的原则

类似于排队买奶茶,先到先得
4.3.经典应用
4.3.1.生产者消费者模型
生产者:医院挂号机(3,4,5,6,7......)
消费者:医生叫号(先叫3,再叫4)
公平性:队列的先进先出,保证了先到的号先被处理,不会插队
锁:保证一个时间只有一个线程操作队列(叫号叫到3,让3既到3号机又到4号机)
4.3.2.广度优先遍历(BFS)
将好友关系用图来表示:

给小徐推荐好友
以一个点为源点,一圈一圈扩散地寻找好友
边连接着直接好友小王、小明
直接好友的好友是小花、小张
直接好友的好友的好友是小杨
给小徐推荐直接好友的好友:
小徐入队列

小徐出队列,小徐的直接好友入队列

小明出队列,小明的直接好友入队列

小王出队列,小王的直接好友入队列

此时队列中的好友就是小徐直接好友的好友
五、单链表实现队列
5.1.栈的文件结构
- 头文件(Queue.h):队列的结构创建,队列的方法声明
- 源文件(Queue.c):队列的方法实现
- 测试文件(test.c):测试数据结构的方法
5.2.头文件编写
5.2.1.头文件包含
cpp
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <assert.h>
5.2.2.节点数据类型重命名
cpp
typedef int QDataType;
5.2.3.节点结构定义
cpp
typedef struct QueueNode
{
struct QueueNode* next;
QDataType val;
}QNode;
5.2.4.头尾结构定义
cpp
typedef struct Queue
{
QNode* phead;
QNode* ptail;
int size;
}Queue;
注:
将头指针,尾指针,以及节点个数封装成一个结构体
只需传送这个结构体的地址,就能完成对指针变量的修改
减少了传送的参数个数,避免了传送二级指针
**问:**同样是单链表,为什么实现队列时可以创建头尾结构?
因为这种头尾结构可以轻松实现尾插,但实现尾删非常麻烦
因为不方便寻找倒数第二个节点
5.2.5.队列的初始化
cpp
void QueueInit(Queue* pq);
5.2.6.队尾插入
cpp
void QueuePush(Queue* pq, QDataType x);
5.2.7.队头删除
cpp
void QueuePop(Queue* pq);
5.2.8.计算当前数据个数
cpp
int QueueSize(Queue* pq);
5.2.9.取队头数据
cpp
QDataType QueueFront(Queue* pq);
5.2.10.取队尾数据
cpp
QDataType QueueBack(Queue* pq);
5.2.11.判断队列是否为空
cpp
bool QueueEmpty(Queue* pq);
5.2.12.队列的销毁
cpp
void QueueDestroy(Queue* pq);
5.3.源文件编写
5.3.1.头文件包含
cpp
#inlcude "Queue.h"
5.3.2.队列的初始化
cpp
void QueueInit(Queue* pq)
{
assert(pq);
pq->phead = NULL;
pq->ptail = NULL;
pq->size = 0;
}
5.3.3.队尾插入
cpp
void QueuePush(Queue* pq, QDataType x)
{
QNode* newnode = (QNode*)malloc(sizeof(QNode));
if (newnode == NULL)
{
perror("malloc fail!");
return;
}
newnode->next = NULL;
newnode->val = x;
if (pq->ptail == NULL)
{
pq->phead = pq->ptail = newnode;
}
else
{
pq->ptail->next = newnode;
pq->ptail = newnode;
}
pq->size++;
}
5.3.4.队头删除
cpp
void QueuePop(Queue* pq)
{
assert(pq);
assert(pq->size != 0);
if(pq->phead->next == NULL)//一个节点
{
free(pq->phead);
pq->phead = pq->ptail = NULL;
}
else//多个节点
{
QNode* next = pq->phead->next;
free(pq->phead);
pq->phead = next;
}
pq->size--;
}
5.3.5.计算当前数据个数
cpp
int QueueSize(Queue* pq)
{
assert(pq);
return pq->size;
}
5.3.9.取队头数据
cpp
QDataType QueueFront(Queue* pq)
{
assert(pq);
assert(pq->phead);
return pq->phead->val;
}
5.3.10.取队尾数据
cpp
QDataType QueueBack(Queue* pq)
{
assert(pq);
assert(pq->ptail);
return pq->ptail->val;
}
5.3.11.判断队列是否为空
cpp
bool QueueEmpty(Queue* pq)
{
assert(pq);
return pq->size == 0;
}
5.3.12.队列的销毁
cpp
void QueueDestroy(Queue* pq)
{
assert(pq);
QNode* cur = pq->phead;
while (cur)
{
QNode* next = cur->next;
free(cur);
cur = next;
}
pq->phead = pq->ptail = NULL;
pq->size = 0;
}
5.4.测试文件编写
5.4.1.测试文件01
cpp
void Test01()
{
Queue q;
QueueInit(&q);
QueuePush(&q, 1);
QueuePush(&q, 2);
QueuePush(&q, 3);
QueuePush(&q, 4);
while(!QueueEmpty(&q))
{
printf("%d ",QueueFront(&q));
QueuePop(&q);
}
printf("\n");
}
int main()
{
Test01();
return 0
}
七、与栈和队列有关的试题
试题2:用队列实现栈
题目内容:
请你仅使用两个队列实现一个后入先出(LIFO)的栈,并支持普通栈的全部四种操作
(push、top、pop、和empty)
示例:
输入:
"MyStack","push","push","top","pop","empty"
\[\],\[1\],\[2\],\[\],\[\],\[\]
输出:
NULL,NULL,NULL,2,2,false
思路解析:

cpp
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
typedef int QDataType;
typedef struct QueueNode
{
struct QueueNode* next;
QDataType val;
}QNode;
typedef struct Queue
{
QNode* phead;
QNode* ptail;
int size;
}Queue;
void QueueInit(Queue* pq)
{
assert(pq);
pq->phead = NULL;
pq->ptail = NULL;
pq->size = 0;
}
void QueuePush(Queue* pq, QDataType x)
{
QNode* newnode = (QNode*)malloc(sizeof(QNode));
if (newnode == NULL)
{
perror("malloc fail!");
return;
}
newnode->next = NULL;
newnode->val = x;
if (pq->ptail == NULL)
{
pq->phead = pq->ptail = newnode;
}
else
{
pq->ptail->next = newnode;
pq->ptail = newnode;
}
pq->size++;
}
void QueuePop(Queue* pq)
{
assert(pq);
assert(pq->size != 0);
if(pq->phead->next == NULL)
{
free(pq->phead);
pq->phead = pq->ptail = NULL;
}
else
{
QNode* next = pq->phead->next;
free(pq->phead);
pq->phead = next;
}
pq->size--;
}
bool QueueEmpty(Queue* pq)
{
assert(pq);
return pq->size == 0;
}
QDataType QueueFront(Queue* pq)
{
assert(pq);
assert(pq->phead);
return pq->phead->val;
}
QDataType QueueBack(Queue* pq)
{
assert(pq);
assert(pq->ptail);
return pq->ptail->val;
}
void QueueDestroy(Queue* pq)
{
assert(pq);
QNode* cur = pq->phead;
while (cur)
{
QNode* next = cur->next;
free(cur);
cur = next;
}
pq->phead = pq->ptail = NULL;
pq->size = 0;
}
int QueueSize(Queue* pq)
{
assert(pq);
return pq->size;
}
typedef struct {
Queue q1;
Queue q2;
} MyStack;
MyStack* myStackCreate() {
MyStack* pst = (MyStack*)malloc(sizeof(MyStack));
QueueInit(&(pst->q1));
QueueInit(&(pst->q2));
return pst;
}
void myStackPush(MyStack* obj, int x) {
if(!QueueEmpty(&(obj->q1)))
{
QueuePush(&(obj->q1),x);
}
else
{
QueuePush(&(obj->q2),x);
}
}
int myStackPop(MyStack* obj) {
//假设法
Queue* empty = &(obj->q1);
Queue* nonEmpty = &(obj->q2);
if(!QueueEmpty(&(obj->q1)))
{
nonEmpty = &(obj->q1);
empty = &(obj->q2);
}
while(QueueSize(nonEmpty) > 1)
{
QueuePush(empty,QueueFront(nonEmpty));
QueuePop(nonEmpty);
}
int top = QueueFront(nonEmpty);
QueuePop(nonEmpty);
return top;
}
int myStackTop(MyStack* obj) {
if(!QueueEmpty(&(obj->q1)))
{
return QueueBack(&(obj->q1));
}
else
{
return QueueBack(&(obj->q2));
}
}
bool myStackEmpty(MyStack* obj) {
return QueueEmpty(&(obj->q1)) && QueueEmpty(&(obj->q2));
}
void myStackFree(MyStack* obj) {
QueueDestroy(&(obj->q1));
QueueDestroy(&(obj->q2));
free(obj);
}
/**
* Your MyStack struct will be instantiated and called as such:
* MyStack* obj = myStackCreate();
* myStackPush(obj, x);
* int param_2 = myStackPop(obj);
* int param_3 = myStackTop(obj);
* bool param_4 = myStackEmpty(obj);
* myStackFree(obj);
*/
试题3:设计循环队列
题目内容:
设计你的循环队列
循环队列:
一种线性数据结构,其操作表现基于 FIFO(先进先出)原则并且队尾被连接在队首之后以形成一个循环,它也被称为环形缓冲器
循环队列好处:
在一个普通队列里,一旦一个队列满了,就不能插入下一个元素,即使在队列前面仍有空间,但是使用循环队列,可以利用这个队列之前用过的空间,使用这些空间去存储新的值
你的实现应该支持如下操作:
MyCircularQueue(k):构造器 设置队列长度为 k
Front:从队首获取元素 如果队列为空,返回 -1
Rear:获取队尾元素 如果队列为空,返回 -1
enQueue(value):向循环队列插入一个元素 如果成功插入则返回真
deQueue():从循环队列中删除一个元素 如果成功删除则返回真
isEmpty():检查循环队列是否为空
isFull():检查循环队列是否已满
示例:
MyCircularQueue circularQueue = new
MyCircularQueue(3);//设置长度为 3
circularQueue.enQueue(1);//返回 true
circularQueue.enQueue(2);//返回 true
circularQueue.enQueue(3);//返回 true
circularQueue.enQueue(4);//返回 false
队列已满
circularQueue.Rear();//返回 3
circularQueue.isFull();//返回 true
circularQueue.deQueue();//返回 true
circularQueue.enQueue(4);//返回 true
circularQueue.Rear();//返回4
代码部分:
cpp
typedef struct
{
int* a;
int head;
int tail;
int k;
} MyCircularQueue;
MyCircularQueue* myCircularQueueCreate(int k)
{
MyCircularQueue* obj = (MyCircularQueue*)malloc(sizeof(MyCircularQueue));
obj->a = (int*)malloc(sizeof(int)*(k+1));
obj->head = 0;
obj->tail = 0;
obj->k = k;
return obj;
}
bool myCircularQueueIsEmpty(MyCircularQueue* obj)
{
return obj->head == obj->tail;
}
bool myCircularQueueIsFull(MyCircularQueue* obj)
{
return (obj->tail+1)%(obj->k+1) == obj->head;
}
bool myCircularQueueEnQueue(MyCircularQueue* obj, int value)
{
if(myCircularQueueIsFull(obj))
{
return false;
}
obj->a[obj->tail] = value;
obj->tail++;
obj->tail %= (obj->k+1);
return true;
}
bool myCircularQueueDeQueue(MyCircularQueue* obj)
{
if(myCircularQueueIsEmpty(obj))
{
return false;
}
++obj->head;
obj->head %= obj->k+1;
return true;
}
int myCircularQueueFront(MyCircularQueue* obj)
{
if(myCircularQueueIsEmpty(obj))
{
return -1;
}
else
{
return obj->a[obj->head];
}
}
int myCircularQueueRear(MyCircularQueue* obj)
{
if(myCircularQueueIsEmpty(obj))
{
return -1;
}
else
{
return obj->a[(obj->tail + obj->k)%(obj->k + 1)];
}
}
void myCircularQueueFree(MyCircularQueue* obj)
{
free(obj->a);
free(obj);
}
/**
* Your MyCircularQueue struct will be instantiated and called as such:
* MyCircularQueue* obj = myCircularQueueCreate(k);
* bool param_1 = myCircularQueueEnQueue(obj, value);
* bool param_2 = myCircularQueueDeQueue(obj);
* int param_3 = myCircularQueueFront(obj);
* int param_4 = myCircularQueueRear(obj);
* bool param_5 = myCircularQueueIsEmpty(obj);
* bool param_6 = myCircularQueueIsFull(obj);
* myCircularQueueFree(obj);
*/
试题4:用栈实现队列
题目内容:
请你仅使用两个栈实现先入先出队列
队列应当支持一般队列支持的所有操作(push,pop,peek,empty)
示例:
输入:["MyQueue","push","push","peek","pop","empty"]
\[\],\[1\],\[2\],\[\],\[\],\[\]
输出:[NULL,NULL,NULL,1,1,false]
代码部分:
cpp
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>
typedef char STDataType;
typedef struct Stack
{
STDataType* a;
int top;
int capacity;
}ST;
void STInit(ST* pst)
{
assert(pst);
pst->a = NULL;
pst->top = pst->capacity = 0;
}
void STDestroy(ST* pst)
{
assert(pst);
free(pst->a);
pst->a = NULL;
pst->top = pst->capacity = 0;
}
void STPush(ST* pst, STDataType x)
{
assert(pst);
if (pst->top == pst->capacity)
{
int newcapacity = pst->capacity == 0 ? 4 : pst->capacity * 2;
STDataType* tmp = (STDataType*)realloc(pst->a, newcapacity * sizeof(STDataType));
if (tmp == NULL)
{
perror("realloc fail!");
return;
}
pst->a = tmp;
pst->capacity = newcapacity;
}
pst->a[pst->top] = x;
pst->top++;
}
void STPop(ST* pst)
{
assert(pst);
assert(pst->top > 0);
pst->top--;
}
STDataType STTop(ST* pst)
{
assert(pst);
assert(pst->top > 0);
return pst->a[pst->top - 1];
}
bool STEmpty(ST* pst)
{
assert(pst);
return pst->top == 0;
}
typedef struct
{
ST pushst;
ST popst;
} MyQueue;
MyQueue* myQueueCreate()
{
MyQueue* obj = (MyQueue*)malloc(sizeof(MyQueue));
STInit(&(obj->pushst));
STInit(&(obj->popst));
return obj;
}
void myQueuePush(MyQueue* obj, int x)
{
STPush(&(obj->pushst),x);
}
int myQueuePeek(MyQueue* obj)
{
if(STEmpty(&(obj->popst)))
{
while(!STEmpty(&(obj->pushst)))
{
int top = STTop(&(obj->pushst));
STPush(&(obj->popst),top);
STPop(&(obj->pushst));
}
}
return STTop(&(obj->popst));
}
int myQueuePop(MyQueue* obj)
{
int front = myQueuePeek(obj);
STPop(&(obj->popst));
return front;
}
bool myQueueEmpty(MyQueue* obj)
{
return STEmpty(&(obj->popst)) && STEmpty(&(obj->pushst));
}
void myQueueFree(MyQueue* obj)
{
STDestroy(&(obj->popst));
STDestroy(&(obj->pushst));
free(obj);
}
/**
* Your MyQueue struct will be instantiated and called as such:
* MyQueue* obj = myQueueCreate();
* myQueuePush(obj, x);
* int param_2 = myQueuePop(obj);
* int param_3 = myQueuePeek(obj);
* bool param_4 = myQueueEmpty(obj);
* myQueueFree(obj);
*/