目录
一.引言
想象一下,你走进一家餐厅,服务员将你的菜品一个一个叠放在托盘上。当你取用时,最上面的菜总是最先被你拿走。又或者,你站在超市的结账队伍里,第一个排队的人总是第一个完成结账。
这两个简单的生活场景,恰好对应了计算机科学中最基础、最核心的两大数据结构:栈 (Stack) 和 队列 (Queue)。它们结构简单,但却是实现递归、深度/广度优先搜索、程序内存管理、任务调度等复杂功能的基石。
二.栈 (Stack):后进先出的世界
栈(Stack)是一种特殊的线性表,它所有的插入和删除操作都只能在一端进行,这一端被称为栈顶(Top)。它的核心原则是 后进先出 (Last In, First Out),简称 LIFO。栈的操作就像是一个羽毛球筒:你往里放球时,新球总是在最上面;当你取球时,也只能先取走最上面的球。

-
栈顶 (Top):允许插入和删除的一端。
-
栈底 (Bottom):固定的一端,不允许进行操作。
-
压栈/入栈 (Push):向栈顶添加元素。
-
弹栈/出栈 (Pop):从栈顶移除元素。
栈的实现方式主要有两种:顺序存储(数组或动态数组) 和 链式存储(链表)。
2.1 栈的顺序存储
2.1.1 顺序栈的结构定义
栈的顺序存储,顾名思义,是利用一段连续的内存空间(如数组或动态数组)来存储栈中的元素。这种实现方式也被称为顺序栈。

-
实现特点: 它通过数组下标的移动来模拟元素的入栈(插入)和出栈(删除)操作。通常,我们用一个变量(如 top)来指示栈顶元素的位置或下一个可插入数据的位置。

需要注意二者的区别:当实现顺序栈时,如果Top 指向栈顶元素,则初始化时应设置为 −1。如果Top指向下一个可插入位置,则初始化时应设置为0。这样二者才可以区分开,同时确保栈空和栈中只有一个元素的状态都能正确识别!!!(重要)
-
优点: 访问效率高,因为元素地址连续,存取速度快,时间复杂度为 O(1)。
-
缺点: 必须预先分配内存。如果栈的空间不足(栈满),则需要进行动态扩容,这会带来一定的性能开销。
typedef int STDataType; // 假设栈存储的数据类型为 int
typedef struct Stack
{
STDataType* a; // 指向存储元素的动态数组
int top; // 指向下一个要插入的位置
int capacity; // 栈的容量
} ST;
注意:本文博客采用Top指向下一个要插入的位置,这样Top的值刚好也等于目前栈中存储的元素数量!!!(重要)
2.1.2 顺序栈的基本操作
这里直接提供C语言程序,实现了栈的初始化、销毁、入栈、出栈、获取栈顶元素、判空和获取大小等核心功能。整个实现过程简单,思路清晰,并没有额外需要补充的点,不做过多解释。
// 初始化 (STInit)
void STInit(ST* pst)
{
assert(pst);
pst->a = NULL;
pst->top = 0;
pst->capacity = 0;
}
// 销毁 (STDestroy)
void STDestroy(ST* pst)
{
assert(pst);
free(pst->a);
pst->a = NULL;
pst->top = 0;
pst->capacity = 0;
}
// 入栈 (STPush):关键在于判断容量,并进行动态扩容
void STPush(ST* pst, STDataType x)
{
assert(pst);
if (pst->top == pst->capacity)
{
// 扩容:如果容量为0则初始化为4,否则加倍
int newcapacity = pst->capacity == 0 ? 4 : (pst->capacity * 2);
STDataType* temp = (STDataType*)realloc(pst->a, newcapacity * sizeof(STDataType)); // 注意:这里 sizeof(ST) 应该是 sizeof(STDataType)
if (temp == NULL)
{
perror("realloc fail!\n");
return;
}
pst->a = temp;
pst->capacity = newcapacity;
}
// 插入元素并更新栈顶位置
pst->a[pst->top] = x;
pst->top++;
}
// 出栈 (STPop):移除栈顶元素,只需将 top 减一
void STPop(ST* pst)
{
assert(pst);
assert(pst->top > 0); // 断言:确保栈非空才能出栈
pst->top--;
}
// 取栈顶元素 (STTop)
STDataType STTop(ST* pst)
{
assert(pst);
assert(pst->top > 0); // 断言:确保栈非空
return pst->a[pst->top - 1]; // 栈顶元素位于 top-1 的位置
}
// 判断栈是否为空 (STEmpty)
bool STEmpty(ST* pst)
{
assert(pst);
return pst->top == 0;
}
// 获取栈中元素个数 (STSize)
int STSize(ST* pst)
{
assert(pst);
return pst->top;
}
2.2 栈的链式存储
2.2.1 链式栈的结构定义
与顺序栈使用连续内存空间不同,栈的链式存储是利用单链表来实现栈结构。这种实现方式被称为链式栈。链式栈将栈中的每一个元素存储在一个独立的节点(Node)中,每个节点除了存储数据外,还包含一个指向下一个节点的指针。

-
实现特点: 栈顶操作(入栈和出栈)对应于链表的头部操作(头插和头删)。我们用一个头指针 (top) 来指向栈顶元素,也就是链表的第一个节点。
-
优点:无需预先分配固定大小的内存,可以动态增长和缩小,不会有“栈满溢出”的风险。 入栈和出栈操作都只需要改变指针的指向,时间复杂度为 O(1)。
-
缺点: 相比顺序栈,每个节点都需要额外的空间来存储指针,内存利用率稍低。
typedef int STDataType; // 假设栈存储的数据类型为 int
// 链表节点结构
typedef struct StackNode
{
STDataType data;
struct StackNode* next;
} Node;
// 栈结构体
typedef struct Stack
{
Node* top; // 栈顶指针 (指向链表的第一个节点)
int size; // 栈中元素的个数
} ST;
2.2.2 链式栈的基本操作
这里同样直接提供C程序,实现初始化、销毁、入栈、出栈、获取栈顶元素、判空和获取大小等核心功能。整个过程容易理解,不做过多解释。
// 初始化 (STInit)
void STInit(ST* pst)
{
assert(pst);
pst->top = NULL; // 初始化时栈顶为空
pst->size = 0;
}
// 销毁 (STDestroy):遍历链表,逐个释放节点
void STDestroy(ST* pst)
{
assert(pst);
while (pst->top)
{
Node* next = pst->top->next;
free(pst->top);
pst->top = next;
}
pst->size = 0;
}
// 入栈 (STPush):使用头插法 O(1)
void STPush(ST* pst, STDataType x)
{
assert(pst);
// 1. 创建新节点
Node* newnode = (Node*)malloc(sizeof(Node));
if (newnode == NULL)
{
perror("malloc fail!\n");
exit(1);
}
// 2. 存入数据
newnode->data = x;
// 3. 新节点指向原栈顶
newnode->next = pst->top;
// 4. 更新栈顶指针
pst->top = newnode;
pst->size++;
}
// 出栈 (STPop):移除并释放栈顶节点 O(1)
void STPop(ST* pst)
{
assert(pst && pst->top); // 断言:确保栈非空
// 记录第二个节点,作为新的栈顶
Node* next = pst->top->next;
// 释放原栈顶节点
free(pst->top);
// 更新栈顶指针
pst->top = next;
pst->size--;
}
// 取栈顶元素 (STTop)
STDataType STTop(ST* pst)
{
assert(pst && pst->top); // 断言:确保栈非空
return pst->top->data;
}
// 判断栈是否为空 (STEmpty)
bool STEmpty(ST* pst)
{
assert(pst);
return pst->top == NULL;
}
// 获取栈中元素个数 (STSize)
int STSize(ST* pst)
{
assert(pst);
return pst->size;
}
三.队列 (Queue):先进先出的秩序
队列(Queue)是另一种重要的线性数据结构,它严格遵循 先进先出 (First In, First Out),简称 FIFO 的原则。想象一下排队购买电影票:第一个排队的人总是第一个获得服务并离开队伍。队列的操作就像是管道,数据从一端流入,从另一端流出。

-
队头 (Front / Head):允许删除的一端,即元素离开队列的地方。
-
队尾 (Rear / Tail):允许插入的一端,即元素进入队列的地方。
-
入队 (Push / Enqueue):在队尾添加元素。
-
出队 (Pop / Dequeue):从队头移除元素。
注意:在顺序存储(数组)结构中实现队列不仅较为复杂,还容易出现“假溢出”问题。更为关键的是,出队操作需要在数组头部删除元素,从而导致后续所有元素依次移动,其时间复杂度高达 O(N),效率较低。基于这一原因,队列通常更适合采用单链表来实现。
3.1 队列的链式存储
3.1.1 链队列的结构定义
链队列是利用链表来实现队列结构。与链式栈只需要一个指针指向栈顶不同,高效的链队列通常需要两个指针:①队头指针 (phead):指向队列的第一个元素,用于执行出队操作。②队尾指针 (ptail):指向队列的最后一个元素,用于执行入队操作。
-
实现特点: 为了确保 O(1) 的操作效率,链队列通常采用:
-
入队 (Enqueue): 在链表尾部插入节点(尾插法)。
-
出队 (Dequeue): 在链表头部删除节点(头删法)。
-

typedef int QDataType; // 假设队列存储的数据类型为 int
// 链表节点结构
typedef struct QueueNode
{
QDataType data;
struct QueueNode* next;
} QNode;
// 队列结构体
typedef struct Queue
{
QNode* phead; // 队头指针
QNode* ptail; // 队尾指针
int size; // 队列中元素个数
} Queue;
3.2.2 链队列的基本操作
这部分程序也比较容易理解,就是单链表的基础操作,不做过多解释了。
// 初始化 (QueueInit)
void QueueInit(Queue* q)
{
assert(q);
q->phead = NULL; // 队头指针置空
q->ptail = NULL; // 队尾指针置空
q->size = 0;
}
// 销毁 (QueueDestroy):遍历链表,释放所有节点
void QueueDestroy(Queue* q)
{
assert(q);
while (q->phead)
{
QNode* next = q->phead->next;
free(q->phead);
q->phead = next;
}
q->phead = q->ptail = NULL;
q->size = 0;
}
// 入队 (QueuePush):尾插法实现 O(1)
void QueuePush(Queue* q, QDataType x)
{
assert(q);
// 1. 创建新节点
QNode* newnode = (QNode*)malloc(sizeof(QNode));
if (newnode == NULL)
{
perror("malloc fail!\n");
exit(1);
}
newnode->data = x;
newnode->next = NULL;
// 2. 判断队列是否为空
if (q->ptail == NULL)
{
// 空队列时,新节点既是头又是尾
q->phead = q->ptail = newnode;
}
else
{
// 非空时,原队尾节点的 next 指向新节点,并更新队尾指针
q->ptail->next = newnode;
q->ptail = newnode;
}
q->size++;
}
// 出队 (QueuePop):头删法实现 O(1)
void QueuePop(Queue* q)
{
assert(q);
assert(!QueueEmpty(q)); // 断言:确保队列非空
// 处理只有一个元素的情况:出队后,头尾指针都置空
if (q->phead == q->ptail)
{
free(q->phead);
q->phead = q->ptail = NULL;
}
else
{
// 记录第二个节点,释放队头节点,并更新队头指针
QNode* next = q->phead->next;
free(q->phead);
q->phead = next;
}
q->size--;
}
// 判断队列是否为空 (QueueEmpty)
bool QueueEmpty(Queue* q)
{
assert(q);
return q->phead == NULL;
}
// 获取队头元素 (QueueFront)
QDataType QueueFront(Queue* q)
{
assert(q);
assert(!QueueEmpty(q));
return q->phead->data;
}
// 获取队尾元素 (QueueBack)
QDataType QueueBack(Queue* q)
{
assert(q);
assert(!QueueEmpty(q));
return q->ptail->data;
}
// 获取队列中元素个数 (QueueSize)
int QueueSize(Queue* q)
{
assert(q);
return q->size;
}
3.2 循环队列
3.2.1 循环队列的结构定义
虽然链队列非常灵活,但在某些需要高性能和内存连续的场景,我们仍然希望使用数组来实现队列。为了解决传统数组队列出队后会产生闲置空间,导致“假溢出”(即数组中仍有空位但无法继续入队)的问题,我们引入了循环队列 (Circular Queue)。注:front指向队首元素,rear指向下一个可插入数据的位置。

循环队列将数组的首尾看作是相连的,形成一个环状结构。当队尾指针到达数组末尾时,它会“回绕”到数组开头继续存储,从而有效地利用了数组的所有空间。

现在,我们就迎来了一个关键问题:循环队列如何来实现队头和队尾指针的移动?——通过取模运算!取模运算 (mod N) 的结果总是落在 0 到 N−1 的范围内,当指针达到数组末尾下标 N−1 后再加 1 时,取模运算会将其自动“回绕”到下标为 0 的位置,从而实现循环。
-
初始时:Q->front = Q->rear = 0。
-
队首指针进1:Q->front = (Q->front + 1) % MAXSIZE。
-
队尾指针进1:Q->rear = (Q->rear + 1) % MAXSIZE。
-
队列长度:(Q->rear - Q->front + MAXSIZE) % MAXSIZE。

此时,我们又引入了一个新的问题,无论是队空还是队满,front == rear 总是满足。那么循环队列如何判断队空和队满呢?——通常有三种解决思路!
-
牺牲一个存储空间: 数组实际容量为 K+1,但最多只存储 K 个元素。此时,判满条件为 (rear + 1) % (K + 1) == front ,判空条件为 front == rear。
-
额外记录元素个数 (
size): 不浪费空间,通过 size 或其他标志位来辅助判断。此时,判满条件为 size == Maxsize ,判空条件为 size == 0。 -
额外的状态标记(tag):通过记录队列上一次执行的操作来辅助判断。tag = 0 表示上一次操作是删除(出队),若此时 front = rear,则队列被清空。tag = 1 表示上一次操作是插入(入队),若此时 front = rear,则队列刚刚被填满。
那么在实际中最常用的方法是第一种,这里我们也以第一种方法为例,画图来解析:

// 结构体定义 (k 为用户要求的最大容量)
typedef struct
{
int* arr; // 存储数据的数组
int front; // 队头指针 (指向队头元素)
int rear; // 队尾指针 (指向下一个可插入的位置)
int k; // 用户要求的容量
}MyCircularQueue;
3.2.2 循环队列的基本操作
// 函数声明 (为保持完整性)
bool myCircularQueueIsFull(MyCircularQueue* obj);
bool myCircularQueueIsEmpty(MyCircularQueue* obj);
// 创建 (myCircularQueueCreate):分配 k+1 空间
MyCircularQueue* myCircularQueueCreate(int k)
{
MyCircularQueue* obj = (MyCircularQueue*)malloc(sizeof(MyCircularQueue));
// 实际分配 k+1 的空间,用于牺牲一个位置来区分满和空
obj->arr = (int*)malloc((k + 1) * sizeof(int));
if(obj->arr == NULL)
{
perror("malloc fail!");
exit(1);
}
obj->front = 0; // 队头从0开始
obj->rear = 0; // 队尾从0开始
obj->k = k;
return obj;
}
// 入队 (myCircularQueueEnQueue):O(1)
bool myCircularQueueEnQueue(MyCircularQueue* obj, int value)
{
if(myCircularQueueIsFull(obj)) // 先判满
{
return false;
}
obj->arr[obj->rear] = value;
// 队尾指针循环后移
obj->rear = (obj->rear + 1) % (obj->k + 1);
return true;
}
// 出队 (myCircularQueueDeQueue):O(1)
bool myCircularQueueDeQueue(MyCircularQueue* obj)
{
if(myCircularQueueIsEmpty(obj)) // 先判空
{
return false;
}
// 队头指针循环后移
obj->front = (obj->front + 1) % (obj->k + 1);
return true;
}
// 获取队头元素 (myCircularQueueFront)
int myCircularQueueFront(MyCircularQueue* obj)
{
if(myCircularQueueIsEmpty(obj))
{
return -1; // 约定空队列返回-1或抛出错误
}
return obj->arr[obj->front];
}
// 获取队尾元素 (myCircularQueueRear)
int myCircularQueueRear(MyCircularQueue* obj)
{
if(myCircularQueueIsEmpty(obj))
{
return -1; // 约定空队列返回-1或抛出错误
}
// 队尾指向下一个可插入位置,所以队尾元素在它前面一个位置
// 通过 (rear - 1 + k + 1) % (k + 1) 处理 rear = 0 时的取模负数问题
return obj->arr[(obj->rear - 1 + obj->k + 1) % (obj->k + 1)];
}
// 判断队列是否为空:头尾指针重合即为空
bool myCircularQueueIsEmpty(MyCircularQueue* obj)
{
return obj->front == obj->rear;
}
// 判断队列是否为满:队尾指针的下一个位置是队头指针,说明队尾和队头只隔着被牺牲的一个空间
bool myCircularQueueIsFull(MyCircularQueue* obj)
{
return (obj->rear + 1) % (obj->k + 1) == obj->front;
}
// 释放资源
void myCircularQueueFree(MyCircularQueue* obj)
{
free(obj->arr);
free(obj);
}
四.栈与队列练习题
理论结合实践才能真正掌握数据结构。以下是三道经典练习题,它们能帮助你理解栈和队列在实际问题中的灵活应用。
4.1 两个队列实现栈
请你仅使用两个队列实现一个后入先出(LIFO)的栈,并支持普通栈的全部四种操作(push、top、pop 和 empty)。
-
入栈:入不为空的队列。
-
出栈:将不为空的队列前n-1个元素倒到为空的队列,出不为空队列的最后一个元素。

typedef int QDataType;
typedef struct QListNode
{
QDataType data;
struct QListNode* next;
}QNode;
//声明队列
typedef struct Queue
{
QNode* phead;
QNode* ptail;
int size;
}Queue;
void QueueInit(Queue* q); //队列初始化
void QueuePush(Queue* q, QDataType x); //队尾入队列
void QueuePop(Queue* q); //队头出队列
QDataType QueueFront(Queue* q); //获取队列头部元素
QDataType QueueBack(Queue* q); //获取队列尾部元素
bool QueueEmpty(Queue* q); //判空
int QueueSize(Queue* q); //获取队列中有效元素个数
void QueueDestroy(Queue* q); //销毁队列
void QueueInit(Queue* q)
{
assert(q);
q->phead = NULL;
q->ptail = NULL;
q->size = 0;
}
void QueuePush(Queue* q, QDataType x)
{
assert(q);
QNode* newcode = (QNode*)malloc(sizeof(QNode));
if (newcode == NULL)
{
perror("malloc fail!\n");
exit(1);
}
newcode->data = x;
newcode->next = NULL;
if (q->ptail == NULL)
{
q->phead = q->ptail = newcode;
}
else
{
q->ptail->next = newcode;
q->ptail = newcode;
}
q->size++;
}
bool QueueEmpty(Queue* q)
{
assert(q);
return q->phead == NULL;
}
void QueuePop(Queue* q)
{
assert(q);
assert(!QueueEmpty(q));
if (q->phead == q->ptail)
{
free(q->phead);
q->phead = q->ptail = NULL;
}
else
{
QNode* next = q->phead->next;
free(q->phead);
q->phead = next;
}
q->size--;
}
QDataType QueueFront(Queue* q)
{
assert(q);
assert(!QueueEmpty(q));
return q->phead->data;
}
QDataType QueueBack(Queue* q)
{
assert(q);
assert(!QueueEmpty(q));
return q->ptail->data;
}
int QueueSize(Queue* q)
{
assert(q);
return q->size;
}
void QueueDestroy(Queue* q)
{
assert(q);
while (q->phead)
{
QNode* next = q->phead->next;
free(q->phead);
q->phead = next;
}
q->phead = q->ptail = NULL;
q->size = 0;
}
typedef struct
{
Queue q1;
Queue q2;
} MyStack;
MyStack* myStackCreate()
{
MyStack* obj = (MyStack*)malloc(sizeof(MyStack));
QueueInit(&(obj->q1));
QueueInit(&(obj->q2));
return obj;
}
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* NoneEmpty = &(obj->q2);
if(!QueueEmpty(&(obj->q1)))
{
NoneEmpty = &(obj->q1);
Empty = &(obj->q2);
}
//出数据不为空的倒一下
while(QueueSize(NoneEmpty) > 1)
{
QueuePush(Empty, QueueFront(NoneEmpty));
QueuePop(NoneEmpty);
}
//出不为空的最后一个数据
int data = QueueBack(NoneEmpty);
QueuePop(NoneEmpty);
return data;
}
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);
}
4.2 两个栈实现队列
请你仅使用两个栈实现先入先出队列。队列应当支持一般队列支持的所有操作(push、pop、peek、empty):
-
入队:直接将元素压入输入栈。
-
出队:如果输出栈有数据,说明这些数据已经按正确的队列顺序排列,直接弹出数据即可。如果没有数据,需要将输入栈的数据依次弹出并压入输出栈,再从输出栈弹出一个元素,完成出队操作。

//声明栈
typedef int STDataType;
typedef struct Stack
{
STDataType* a;
int top;
int capacity;
}ST;
void STInit(ST* pst); //初始化
void STDestroy(ST* pst); //销毁
void STPush(ST* pst, STDataType x); //入栈
void STPop(ST* pst); //出栈
STDataType STTop(ST* pst); //取栈顶元素
bool STEmpty(ST* pst); //判空
int STSize(ST* pst); //获取数据个数
//top指向栈中数据的下一个位置
void STInit(ST* pst)
{
assert(pst);
pst->a = NULL;
pst->top = 0;
pst->capacity = 0;
}
void STDestroy(ST* pst)
{
assert(pst);
free(pst->a);
pst->a = NULL;
pst->top = 0;
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* temp = (STDataType*)realloc(pst->a, newcapacity * sizeof(ST));
if (temp == NULL)
{
perror("realloc fail!\n");
return;
}
pst->a = temp;
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;
}
int STSize(ST* pst)
{
assert(pst);
return pst->top;
}
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 myQueuePop(MyQueue* obj)
{
if(STEmpty(&(obj->PopST)))
{
while(!STEmpty(&(obj->PushST)))
{
STPush(&(obj->PopST), STTop(&(obj->PushST)));
STPop(&(obj->PushST));
}
}
int data = STTop(&(obj->PopST));
STPop(&(obj->PopST));
return data;
}
int myQueuePeek(MyQueue* obj)
{
if(STEmpty(&(obj->PopST)))
{
return obj->PushST.a[0];
}
return STTop(&(obj->PopST));
}
bool myQueueEmpty(MyQueue* obj)
{
return STEmpty(&(obj->PushST)) && STEmpty(&(obj->PopST));
}
void myQueueFree(MyQueue* obj)
{
STDestroy(&(obj->PushST));
STDestroy(&(obj->PopST));
free(obj);
}
4.3 括号匹配问题
题目描述:给定一个只包括 '(',')','{','}','[',']' 的字符串 s ,判断字符串是否有效。①左括号必须用相同类型的右括号闭合。②左括号必须以正确的顺序闭合。③每个右括号都有一个对应的相同类型的左括号。

题目解析:用栈充当一个“等待匹配”的容器,完美保证匹配顺序的正确性。
-
遍历字符串: 从左到右依次扫描字符串中的每个字符。
-
遇到左括号: 如果字符是左括号(如
(,{,[),将其压入栈中。这表示我们期望在将来某个时刻遇到它的匹配右括号。 -
遇到右括号: 如果字符是右括号(如
),},]),此时必须立即检查栈顶元素:-
栈为空: 如果栈为空,说明在当前右括号之前没有匹配的左括号,匹配失败,字符串无效。
-
栈非空: 弹出栈顶元素,检查它是否与当前右括号类型匹配。如果类型不匹配(例如,遇到
),但栈顶是[),匹配失败,字符串无效。
-
-
遍历结束: 当整个字符串遍历完毕后,检查栈的状态:
-
栈为空: 说明所有左括号都被成功匹配并弹出了,字符串有效。
-
栈非空: 说明栈中还残留有未匹配的左括号,字符串无效。
-
请各位读者多多点赞,多多支持!
387

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



