文章目录
- [1. 栈](#1. 栈)
-
- [1.1 栈的结构和概念](#1.1 栈的结构和概念)
- [1.2 栈的实现](#1.2 栈的实现)
- [1.3 有关栈的OJ题:[有效的括号](https://leetcode.cn/problems/valid-parentheses/)](#1.3 有关栈的OJ题:有效的括号)
- [2. 队列](#2. 队列)
-
- 2.1队列的概念及结构
- [2.2 队列的实现](#2.2 队列的实现)
- [3. 栈和队列经典面试题](#3. 栈和队列经典面试题)
-
- [3.1 [用队列实现栈](https://leetcode.cn/problems/implement-stack-using-queues/)](#3.1 用队列实现栈)
- [3.2 [用栈实现队列](https://leetcode.cn/problems/implement-queue-using-stacks/)](#3.2 用栈实现队列)
- [3.3 [设计循环队列](https://leetcode.cn/problems/design-circular-queue/)](#3.3 设计循环队列)

1. 栈
1.1 栈的结构和概念
概念:
栈:一种特殊的线性表,其只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除操作的一端称为栈顶,另一端称为栈底。
栈中的数据元素遵守后进先出LIFO(Last In First Out)的原则。
压栈:栈的插入操作叫做进栈/压栈/入栈,入数据在栈顶。
出栈:栈的删除操作叫做出栈。出数据也在栈顶。
结构:
1. 使用数组实现:

2. 链表


使用数组实现栈和使用链表实现栈的区别。

总的来说,由于栈只在一端操作,数组在绝大多数场景下性能和简洁性都占优,实际工程中数组实现的栈更常见。
1.2 栈的实现
栈的实现一般可以使用数组或者链表实现,相对而言数组的结构实现更优一些。因为数组在尾上插入数据的代价比较小。
Stack.h 文件
c
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <assert.h>
#if 0
// 下面是定长的静态栈的结构,实际中一般不实用,所以我们主要实现下面的支持动态增长的栈
typedef int STDataType;
#define N 10
typedef struct Stack
{
STDataType _a[N];
int _top; // 栈顶
}Stack;
#endif
// 支持动态增长的栈
typedef int STDataType;
typedef struct Stack
{
STDataType* a;
int top; //栈顶
int capacity; //容量
}ST;
//初始化栈
void STInit(ST* pst);
// 入栈
void STPush(ST* pst, STDataType x);
//出栈
void STPop(ST* pst);
//获取栈顶元素
STDataType STTop(ST* pst);
//检测栈是否为空,如果为空返回非零结果,如果不为空返回0
bool STEmpty(ST* pst);
// 获取栈中有效元素个数
int STSize(ST* pst);
//销毁栈
void STDestroy(ST* pst);
Stack.c 文件
c
#define _CRT_SECURE_NO_WARNINGS
#include "Stack.h"
//实现初始化栈
void STInit(ST* pst)
{
assert(pst);
pst->a = NULL;
//top指向栈顶元素的下一个可用位置。
pst->top = 0;
/*
top指向栈顶元素
pst->top = -1;
*/
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);//栈不为空才pop
return pst->a[pst->top - 1];
}
//检测栈是否为空,如果为空返回非零结果,如果不为空返回0
bool STEmpty(ST* pst)
{
assert(pst);
return pst->top == 0;
}
// 获取栈中有效元素个数
int STSize(ST* pst)
{
assert(pst);
return pst->top;
}
//实现销毁栈
void STDestroy(ST* pst)
{
assert(pst);
free(pst->a);
pst->a = NULL;
pst->top = pst->capacity = 0;
}
test.c 文件
c
#define _CRT_SECURE_NO_WARNINGS
#include "Stack.h"
//int main()
//{
// ST s;
//
// STInit(&s);
// STPush(&s, 1);
// STPush(&s, 2);
// STPush(&s, 3);
// STPush(&s, 5);
//
// printf("%d\n",STTop(&s));
//
// STTop(&s);
// printf("%d\n", STTop(&s));
//
//
// STTop(&s);
// printf("%d\n", STTop(&s));
//
//
// STDestroy(&s);
//
// return 0;
//}
int main()
{
ST s;
STInit(&s);
STPush(&s, 1);
STPush(&s, 2);
STPush(&s, 3);
STPush(&s, 4);
while (!STEmpty(&s))
{
printf("%d ",STTop(&s));
STPop(&s);//弹出栈顶数据
}
STDestroy(&s);
return 0;
}
这里需要特殊说明的是栈的初始化代码不同写法中 top 的意义:
1. top指向栈顶元素的下一个可用位置: pst->top = 0;

2. top指向栈顶元素: pst->top = -1;

两种方式都可以使用但是使用时应注意要匹配对应的方法,不能混用。
1.3 有关栈的OJ题:有效的括号
题目描述:
给定一个只包括 ' ( ' ,' ) ' ,' { ' ,' } ' ,' [ ' ,' ] ' 的字符串 s ,判断字符串是否有效。
有效字符串需满足:
1. 左括号必须用相同类型的右括号闭合。
2. 左括号必须以正确的顺序闭合。
3. 每个右括号都有一个对应的相同类型的左括号。
示例:

题解:

c
// 支持动态增长的栈
typedef char STDataType;
typedef struct Stack
{
STDataType* a;
int top; //栈顶
int capacity; //容量
}ST;
//初始化栈
void STInit(ST* pst);
// 入栈
void STPush(ST* pst, STDataType x);
//出栈
void STPop(ST* pst);
//获取栈顶元素
STDataType STTop(ST* pst);
//检测栈是否为空,如果为空返回非零结果,如果不为空返回0
bool STEmpty(ST* pst);
// 获取栈中有效元素个数
int STSize(ST* pst);
//销毁栈
void STDestroy(ST* pst);
//实现初始化栈
void STInit(ST* pst)
{
assert(pst);
pst->a = NULL;
//top指向栈顶元素的下一个可用位置。
pst->top = 0;
/*
top指向栈顶数据
pst->top = -1;
*/
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];
}
//检测栈是否为空,如果为空返回非零结果,如果不为空返回0
bool STEmpty(ST* pst)
{
assert(pst);
return pst->top == 0;
}
// 获取栈中有效元素个数
int STSize(ST* pst)
{
assert(pst);
return pst->top;
}
//实现销毁栈
void STDestroy(ST* pst)
{
assert(pst);
free(pst->a);
pst->a = NULL;
pst->top = pst->capacity = 0;
}
//主要思路
bool isValid(char* s)
{
ST st;
STInit(&st);
while(*s != '\0')
{
if(*s == '(' || *s == '{' || *s == '[')
{
//左括号入栈
STPush(&st, *s);
}
else
{
if(STEmpty(&st))
{
STDestroy(&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;
}
2. 队列
2.1队列的概念及结构
队列:只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表,队列具有先进先出FIFO(First In First Out) 的特点。
入队列:进行插入操作的一端称为队尾
出队列:进行删除操作的一端称为队头

2.2 队列的实现
队列也可以使用数组和链表的结构实现,使用链表的结构实现更优一些,因为如果使用数组的结构,出队列在数组头上出数据,效率会比较低。
1. Queue.h 文件
c
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.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);
//队尾插入数据
void QueuePush(Queue* pq, QDataType x);
//队头删除数据
void QueuePop(Queue* pq);
//求队列的数据个数
int QueueSize(Queue* pq);
//取队头的数据
QDataType QueueFront(Queue* pq);
//取队尾的数据
QDataType QueueBack(Queue* pq);
//判空
bool QueueEmpty(Queue* pq);
//销毁队列
void QueueDesTroy(Queue* pq);
2. Queue.c 文件
c
#define _CRT_SECURE_NO_WARNINGS
#include "Queue.h"
//队列初始化
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; //newnode成为新的尾
}
pq->size++;//记录数据个数
}
//队头删除数据
void QueuePop(Queue* pq)
{
assert(pq);
assert(pq->size != 0); //保证链表不为空
//防止队列中只有一个数据的情况下phead为空但是ptail为野指针
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--;
}
//求队列的数据个数
int QueueSize(Queue* pq)
{
assert(pq);
return pq->size;
}
//取队头的数据
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;
}
//判空
bool QueueEmpty(Queue* pq)
{
assert(pq);
return pq->size == 0;
}
//销毁队列
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;
}
3. test.c 文件
c
#define _CRT_SECURE_NO_WARNINGS
#include "Queue.h"
int main()
{
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");
return 0;
}
3. 栈和队列经典面试题
3.1 用队列实现栈
题目描述:

示例:

题解:

c
//单链表实现
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);
//队尾插入数据
void QueuePush(Queue* pq, QDataType x);
//队头删除数据
void QueuePop(Queue* pq);
//求队列的数据个数
int QueueSize(Queue* pq);
//取队头的数据
QDataType QueueFront(Queue* pq);
//取队尾的数据
QDataType QueueBack(Queue* pq);
//判空
bool QueueEmpty(Queue* pq);
//销毁队列
void QueueDesTroy(Queue* pq);
//队列初始化
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; //newnode成为新的尾
}
pq->size++;//记录数据个数
}
//队头删除数据
void QueuePop(Queue* pq)
{
assert(pq);
assert(pq->size != 0); //保证链表不为空
//防止队列中只有一个数据的情况下phead为空但是ptail为野指针
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--;
}
//求队列的数据个数
int QueueSize(Queue* pq)
{
assert(pq);
return pq->size;
}
//取队头的数据
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;
}
//判空
bool QueueEmpty(Queue* pq)
{
assert(pq);
return pq->size == 0;
}
//销毁队列
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;
}
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))) //队1不为空插入队1
{
QueuePush(&(obj->q1),x);
}
else //队1为空插入队2
{
QueuePush(&(obj->q2),x);
}
}
int myStackPop(MyStack* obj)
{
//假设法
Queue* Empty = &(obj->q1);//假设q1为空
Queue* nonEmpty = &(obj->q2); //假设q2不为空
if(!QueueEmpty(&(obj->q1)))//如果q1不为空(假设错误)
{
nonEmpty = &(obj->q1);
Empty = &(obj->q2);
}
//把不为空的队列的前size-1个数据倒走,再删除最后一个就是栈顶元素
while(QueueSize(nonEmpty) > 1)
{
QueuePush(Empty,QueueFront(nonEmpty));
QueuePop(nonEmpty);
}
int top = QueueFront(nonEmpty);
QueuePop(nonEmpty);
return top;
}
int myStackTop(MyStack* obj) //取栈顶元素
{
//队1不为空返回队1的队尾
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);
}
3.2 用栈实现队列
题目描述:

示例:

题解:

c
// 支持动态增长的栈
typedef int STDataType;
typedef struct Stack
{
STDataType* a;
int top; //栈顶
int capacity; //容量
}ST;
//初始化栈
void STInit(ST* pst);
// 入栈
void STPush(ST* pst, STDataType x);
//出栈
void STPop(ST* pst);
//获取栈顶元素
STDataType STTop(ST* pst);
//检测栈是否为空,如果为空返回非零结果,如果不为空返回0
bool STEmpty(ST* pst);
// 获取栈中有效元素个数
int STSize(ST* pst);
//销毁栈
void STDestroy(ST* pst);
//实现初始化栈
void STInit(ST* pst)
{
assert(pst);
pst->a = NULL;
//top指向栈顶元素的下一个可用位置。
pst->top = 0;
/*
top指向栈顶元素
pst->top = -1;
*/
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);//栈不为空才pop
pst->top--;
}
//实现获取栈顶元素
STDataType STTop(ST* pst)
{
assert(pst);
assert(pst->top > 0);
return pst->a[pst->top - 1];
}
//检测栈是否为空,如果为空返回非零结果,如果不为空返回0
bool STEmpty(ST* pst)
{
assert(pst);
return pst->top == 0;
}
// 获取栈中有效元素个数
int STSize(ST* pst)
{
assert(pst);
return pst->top;
}
//实现销毁栈
void STDestroy(ST* pst)
{
assert(pst);
free(pst->a);
pst->a = NULL;
pst->top = pst->capacity = 0;
}
typedef struct
{
ST pushst;// 专门用于入队
ST popst; // 专门用于出队
} MyQueue;
MyQueue* myQueueCreate()
{
MyQueue* obj = (MyQueue*)malloc(sizeof(MyQueue));
STInit(&(obj->pushst));
STInit(&(obj->popst));
return obj;
}
int myQueuePeek(MyQueue* obj)
{
//取队头的数据
if(STEmpty(&(obj->popst)))
{
//如果 popst 为空,就把 pushst 中的全部元素逐个弹出并压入 popst
while(!STEmpty(&(obj->pushst)))
{
int top = STTop(&(obj->pushst));
STPush(&(obj->popst),top);
STPop(&(obj->pushst));
}
}
// 此时 popst 栈顶就是队头元素
return STTop(&(obj->popst));//获取popst中的栈顶数据也就是队头数据
}
void myQueuePush(MyQueue* obj, int x)
{
//入数据
STPush(&(obj->pushst),x);
}
int myQueuePop(MyQueue* obj)
{
//出数据
// 先获取队头元素(内部保证 popst 不为空)
int front = myQueuePeek(obj);
STPop(&(obj->popst));
return front;
/*
myQueuePop 要做两件事:
拿到队头元素(即最早入队的值)
把这个元素从队列中删除
而 myQueuePeek 正好完成了第一件事,
并保证 popst 栈顶就是队头。
因此 myQueuePop 可以直接调用 myQueuePeek 拿到队头值,
然后只需要再从popst 弹栈一次即可。
*/
}
bool myQueueEmpty(MyQueue* obj)
{
//判空
// 两个栈都为空时队列才为空
return STEmpty(&(obj->popst)) && STEmpty(&(obj->pushst));
}
void myQueueFree(MyQueue* obj)
{
//销毁
STDestroy(&(obj->popst));
STDestroy(&(obj->pushst));
free(obj);
}
3.3 设计循环队列
什么是循环队列?
循环队列是一种线性数据结构,其操作表现基于 FIFO(先进先出)原则并且队尾被连接在队首之后以形成一个循环。其空间大小是固定的,它也被称为"环形缓冲器"。
循环队列的一个好处是我们可以利用这个队列之前用过的空间。在一个普通队列里,一旦一个队列满了,我们就不能插入下一个元素,即使在队列前面仍有空间。但是使用循环队列,我们能使用这些空间去存储新的值。
可以理解为:有限空间,保证先进先出,重复使用。
题目描述:

示例:

思路:
可以使用链表和数组来实现

1. 数组实现:

这种实现方法有一个隐藏的问题:假溢出

c
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;
//满了就返回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 - 1 + obj->k + 1) % (obj->k + 1)];
//return obj->tail == 0 ? obj->a[obj->k] : obj->a[obj->tail-1];
}
}
void myCircularQueueFree(MyCircularQueue* obj)
{
//释放空间
free(obj->a);
free(obj);
}
2. 链表实现

总结:两种方法各有优劣,如果是高频、性能敏感的场景(如内核网络缓冲区、音视频环形缓冲)几乎清一色用数组。而如果当你明确需要动态容量,且可以接受额外的内存分配开销和代码复杂度时,再考虑链表。
栈和队列是计算机世界里最规矩的公民:一个讲究先来后到,一个偏爱后来居上。我们已经摸清了它们的脾气,也学会了用数组和链表去驾驭它们。但如果数据不想排成一列,而是要分岔、要分层、要像族谱一样展开呢?
这时候,就该请出下一位主角------二叉树。
下一篇,让我们走进树的领地,去看一看这种"非线性的美"