目录
[20. 有效的括号 - 力扣(LeetCode)](#20. 有效的括号 - 力扣(LeetCode))
前言
本篇文章将讲解栈的概念和结构,栈的实现,栈的题等知识的相关内容,本章代码知识,与顺序表相关,所以如果还没看过顺序表文章,可以看看:
一、栈概念与结构
栈概念
栈是一种线性数据结构 ,核心特性为后进先出(LIFO,Last In First Out),所有操作仅允许在栈顶进行。其高效性(基本操作时间复杂度O(1))使其成为算法设计与系统开发的基础工具。
特性:
- 栈顶(Top):唯一允许插入和删除元素的一端,动态指向最新元素。
- 栈底(Bottom):固定端,最早入栈的元素存放于此。
- 基本操作 :
- 入栈(Push):在栈顶添加元素。
- 出栈(Pop):移除并返回栈顶元素。
- 查看栈顶(Peek/Top):返回栈顶元素但不删除。
- 判空(IsEmpty):检查栈是否无元素。
- 判满(IsFull):仅数组实现栈时需判断容量是否用尽。
它具有后进先出的性质:
入栈:栈的插⼊操作叫做进栈/压栈/⼊栈,⼊数据在栈顶。
出栈:栈的删除操作叫做出栈。出数据也在栈顶。
入栈:
出栈
简单来说:
我们可以认为:栈是一个存放东西的罐子,罐子是封闭的,存取物品都在罐子顶部。
图示:

栈结构:
栈的实现可以通过数组或链表来实现栈的代码结构。
但栈的操作都是在栈顶,如果我们选取链表,我们每次操作都需要在链表的尾部操作,即每次都要遍历到链表的尾部O(n),那样的耗费较大,而数组作为随机存储结构,访问值通过下标,时间复杂度为O(1),更适合做为栈的结构。
cpp
typedef int type;
typedef struct Stack
{
type* date;
int size;
int capacity;
}Stack;
二、栈的实现
1.栈的初始化:
初始化是栈使用前的必要步骤,需完成内存分配、初始状态定义,由于我们使用的为顺序结构,所以实现与顺序表的大致相同:
void STInit(Stack* h);
代码:
cpp
void STInit(Stack* h)
{
h->date = NULL;
h->capacity = h->size = 0;
}
本函数为动态数组实现的栈(可自动扩容) 的初始化函数,核心目标是将栈初始化为空状态,为后续动态内存分配奠定基础。
cppvoid STInit(Stack* h) { h->date = NULL; // 初始化为空指针,不分配内存 h->capacity = 0; // 初始容量为0 h->size = 0; // 初始元素个数为0 }
2.栈的销毁:
栈的销毁核心是释放已分配的内存空间,避免内存泄漏,它的实现与顺序表的相同。
void STDestory(Stack* h)
代码:
cpp
void STDestory(Stack* h)
{
if (h->date)
{
free(h->date);
}
h->date = NULL;
h->size = h->capacity = 0;
}
讲解:
cppvoid StackDestroy(Stack* ps) { if (ps == NULL) return; free(ps->data); // 释放数组内存 ps->data = NULL; // 避免野指针 h->size = h->capacity = 0; // 重置容量 }
3.栈的入栈:
入栈(Push)是栈的核心操作之一,遵循"后进先出(LIFO)"原则,需根据存储结构(顺序栈)实现,同时处理容量不足等边界情况。简单来说,其操作可以看成顺序表的尾部插入。
void STPush(Stack* ps, type x)
前面曾说,其操作可以看成顺序表的尾部插入,我们通过顺序表结构可以得知在操作过程中,需要有扩容之处:
所以,需要写出扩容函数:
bool kuo(Stack* h);
代码:
cpp
bool kuo(Stack* h)
{
if (h->capacity == h->size)
{
int newcapacity = h->capacity == 0 ? 4 : 2 * h->capacity;
type* p = (type*)realloc(h->date, newcapacity * sizeof(type));
if (p)
{
h->date = p;
h->capacity = newcapacity;
}
else
{
printf("扩容失败\n");
return false;
}
}
return true;
}
解释:
本函数是栈(
Stack)的动态扩容函数,当栈的元素数量(size)达到当前容量(capacity)时触发,通过realloc调整内存空间,避免栈溢出。
通过实现了扩容函数,我们接下来,可以实现入栈函数了:
void STPush(Stack* ps, type x)
代码:
cpp
bool kuo(Stack* h)
{
if (h->capacity == h->size)
{
int newcapacity = h->capacity == 0 ? 4 : 2 * h->capacity;
type* p = (type*)realloc(h->date, newcapacity * sizeof(type));
if (p)
{
h->date = p;
h->capacity = newcapacity;
}
else
{
printf("扩容失败\n");
return false;
}
}
return true;
}
void STPush(Stack* h, type x)
{ assert(h);
if (!kuo(h))
{
printf("入栈失败:扩容失败,无法添加元素\n");
return;
}
h->date[h->size++] = x;
}




讲解:本代码与顺序表中的尾插入一样,所以不过多讲解:
cppbool kuo(Stack* h) { // h 是指向栈结构体的指针,返回 bool 表示扩容是否成功 if (h->capacity == h->size) { // 1. 判断是否需要扩容:容量 == 当前元素数 // 2. 计算新容量:若初始容量为 0(空栈),则扩容至 4;否则翻倍 int newcapacity = h->capacity == 0 ? 4 : 2 * h->capacity; // 3. 申请新内存:用 realloc 调整数据数组大小(h->date 是原数组地址) type* p = (type*)realloc(h->date, newcapacity * sizeof(type)); if (p) { // 4. 若内存申请成功(p 非 NULL) h->date = p; // 更新栈的数组指针为新地址 h->capacity = newcapacity; // 更新容量为新值 } else { // 5. 若内存申请失败(如堆空间不足) printf("扩容失败\n"); // 打印错误信息 return false; // 返回 false 表示扩容失败 } } return true; // 无需扩容或扩容成功,返回 true }
cppvoid STPush(Stack* h, type x) { // h 是栈指针,x 是待入栈元素 // 1. 先调用扩容函数:若扩容失败(返回 false),则入栈失败 assert(h); //断言:若 h 为 NULL,程序直接终止并报错 if (!kuo(h)) { printf("入栈失败:扩容失败,无法添加元素\n"); // 提示错误 return; // 直接返回,不再执行后续入栈 } // 2. 若扩容成功(或无需扩容),则将 x 存入数组末尾,并更新元素数(size++) h->date[h->size++] = x; }
4.栈的出栈:
出栈即为删去栈顶的数据,若栈不为空,就直接ps->size--就可以了,不需要其它多余的操作,实现起来很简单的:
函数:
void STPop(Stack* h);
代码实现:
cpp
void STPop(Stack* h)
{ assert(h);
if (h->size)
{
h->size--;
}
}
讲解:

移除栈顶元素(通过减少
size实现,不直接删除内存数据)。
cppvoid STPop(Stack* h) { // h 是指向栈结构体的指针,无返回值 assert(h); //断言:若 h 为 NULL,程序直接终止并报错 if (h->size) { // 1. 判断栈是否非空:若 size > 0(栈内有元素) h->size--; // 2. 栈顶元素索引 = size - 1,size-- 即"逻辑删除"栈顶元素 } }
5.取栈顶元素:
由于使用顺序结构顺序表来实现的栈的结构,所以栈访问值的时间复杂度与顺序表是一样的,O(1),直接通过下标访问:
函数:
void STPop(Stack* h);
代码实现:
cpp
type STTop(Stack* h)
{
assert(h);
return (h->size > 0) ? h->date[h->size - 1] : '\0';
}
讲解:
cpp
type STTop(Stack* h) { // type 为栈元素类型(如 char、int)
assert(h); // 1. 断言:确保 h 非空(避免空指针解引用)
// 2. 条件表达式:栈非空返回栈顶元素,空栈返回 '\0'
return (h->size > 0) ? h->date[h->size - 1] : '\0';
}
通过使用三目表达式来实现的。
6.获取栈中元素个数:
功能:返回栈中元素个数.
cpp
int STSize(Stack* h)
{
assert(h);
return h->size;
}
本功能很简单,直接返回size值就是有效元素个数。
7.打印栈内元素:
因为type我默认为int类型,所以打印为:
cpp
void STPrint(Stack* h)
{
assert(h);
int i;
for (i = 0; i < h->size; i++)
{
printf("%d ", h->date[i]);
}
printf("\n");
}
三、栈测试代码
接下来,我将展示本次所写的代码以及对应的测试代码:
本代码通过三个文件实现:
1.h
cpp
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>
typedef int type;
typedef struct Stack
{
type* date;
int size;
int capacity;
}Stack;
void STInit(Stack* h);
void STDestory(Stack* h);
void STPush(Stack* h, type x);
bool kuo(Stack* h);
void STPop(Stack* h);
type STTop(Stack* h);
int STSize(Stack* h);
void STPrint(Stack* h);
1.c
cpp
#include"1.h"
void STInit(Stack* h)
{
h->date = NULL;
h->capacity = h->size = 0;
}
void STDestory(Stack* h)
{
if (h->date)
{
free(h->date);
}
h->date = NULL;
h->size = h->capacity = 0;
}
bool kuo(Stack* h)
{
if (h->capacity == h->size)
{
int newcapacity = h->capacity == 0 ? 4 : 2 * h->capacity;
type* p = (type*)realloc(h->date, newcapacity * sizeof(type));
if (p)
{
h->date = p;
h->capacity = newcapacity;
}
else
{
printf("扩容失败\n");
return false;
}
}
return true;
}
void STPush(Stack* h, type x)
{
assert(h);
if (!kuo(h))
{
printf("入栈失败:扩容失败,无法添加元素\n");
return;
}
h->date[h->size++] = x;
}
void STPop(Stack* h)
{
assert(h);
if (h->size)
{
h->size--;
}
}
type STTop(Stack* h)
{
assert(h);
if (h->size)
{
return h->date[h->size - 1];
}
}
int STSize(Stack* h)
{
assert(h);
return h->size;
}
void STPrint(Stack* h)
{
assert(h);
int i;
for (i = 0; i < h->size; i++)
{
printf("%d ", h->date[i]);
}
printf("\n");
}
main.c
cpp
#include"1.h"
void test()
{
Stack h;
STInit(&h);
STPush(&h, 10); //10
STPrint(&h);
STPush(&h, 20); //10 20
STPrint(&h);
STPush(&h, 30); //10 20 30
STPrint(&h);
STPop(&h); //10 20
STPrint(&h);
STPop(&h); //10
STPrint(&h);
printf("%d", STSize(&h)); //1
STDestory(&h);
}
int main()
{
test();
}
结果:

四、栈的相关例题:
20. 有效的括号 - 力扣(LeetCode)

根据题的意思,我们可得知:
判断字符串是否有效,核心在于验证括号的匹配顺序 和类型对应 ,可通过栈实现高效验证。
解题思路
- 栈存储左括号 :遍历字符串,遇到左括号(
(、{、[)时入栈。- 右括号匹配检查:遇到右括号时,若栈为空(无对应左括号)或栈顶左括号类型不匹配,则字符串无效;否则弹出栈顶左括号(匹配成功)。
- 最终栈为空:遍历结束后,栈若为空表示所有括号均匹配,否则无效。
图例:初始状态:

第一个字符:

第二个字符:

第三个字符:

第四个字符:

栈为空,为true。
代码实现:

cpp
typedef char type;
typedef struct Stack
{
type* date;
int size;
int capacity;
}Stack;
void STInit(Stack* h)
{
h->date = NULL;
h->capacity = h->size = 0;
}
void STDestory(Stack* h)
{
if (h->date)
{
free(h->date);
}
h->date = NULL;
h->size = h->capacity = 0;
}
bool kuo(Stack* h)
{
if (h->capacity == h->size)
{
int newcapacity = h->capacity == 0 ? 4 : 2 * h->capacity;
type* p = (type*)realloc(h->date, newcapacity * sizeof(type));
if (p)
{
h->date = p;
h->capacity = newcapacity;
}
else
{
printf("扩容失败\n");
return false;
}
}
return true;
}
void STPush(Stack* h, type x)
{
assert(h);
if (!kuo(h))
{
printf("入栈失败:扩容失败,无法添加元素\n");
return;
}
h->date[h->size++] = x;
}
void STPop(Stack* h)
{
assert(h);
if (h->size)
{
h->size--;
}
}
type STTop(Stack* h)
{
assert(h);
return (h->size > 0) ? h->date[h->size - 1] : '\0';
}
bool isValid(char* s)
{ Stack h;
STInit(&h);
int i=0;
while(s[i])
{
if(s[i]=='('||s[i]=='['||s[i]=='{')
{
STPush(&h,s[i]);
}
else
{ if(h.size==0)
{
return false;
}
char t=STTop(&h);
if((s[i]==')'&&t!='(')||(s[i]==']'&&t!='[')||(s[i]=='}'&&t!='{'))
{
return false;
}
STPop(&h);
}
i++;
}
if(h.size==0)
{
return true;
}
return false;
}
由于题中,只给了函数的代码,我们想使用栈,需要复用栈的代码(做题倒也不是一定这么麻烦,到C++部分,我们是可以直接使用栈所对应的容器来实现我们的目的的):
先介绍下函数:
|-------------|-------------------------------------------------|
|STPush| 入栈:先扩容(若需),再将元素添加到栈顶(size++)。 |
|STPop| 出栈:仅修改size--(不释放内存,下次入栈可覆盖)。 |
|STTop| 获取栈顶元素:栈非空返回date[size-1],空栈返回'\0'(此处未用到)。 |
|STDestroy| 销毁栈:释放动态数组内存,重置date、size、capacity。 |
|kuo| 扩容:容量不足时,按 2倍 扩容(初始容量为4),失败返回false。 |
核心思想:利用栈的"先进后出"特性,左括号入栈,右括号与栈顶左括号匹配。
- 初始化栈 :创建
Stack h并调用STInit(&h)初始化。- 遍历字符串 :循环处理每个字符
s[i]:
- 情况1:遇到左括号(
([{)
- 直接入栈(
STPush(&h, s[i])),等待后续右括号匹配。- 情况2:遇到右括号(
)]})
- 检查栈空 :若栈为空(
h.size == 0),说明右括号无匹配的左括号,返回false。- 匹配栈顶左括号 :
- 获取栈顶元素
t = STTop(&h)(左括号)。- 若右括号与
t不匹配(如')'对应'['),返回false。- 若匹配,出栈(
STPop(&h)),继续检查下一个字符。- 遍历结束后检查栈空 :
- 若栈为空(
h.size == 0),所有左括号均匹配,返回true。- 若栈非空,存在未匹配的左括号,返回
false。
以文字形式,演示:
(以
s = "({})"为例)
步骤 字符 操作 栈内元素(size) 结果 1 (入栈 ['('](1)- 2 {入栈 ['(', '{'](2)- 3 }右括号,匹配栈顶 {['('](1)匹配成功 4 )右括号,匹配栈顶 ([](0)匹配成功 5 结束 栈空,返回 true- 有效
需要注意点:
- 如果刚开始就是右侧括号,栈此时无值,要写检查代码检查。
总结
以上就是今天要讲的内容,本篇文章涉及的知识点为:栈的概念和结构,栈的实现,栈的题知识的相关内容 的相关内容,为本章节知识的内容,希望大家能喜欢我的文章,谢谢各位,下篇文章,我将会讲解数据结构:**队列,**接下来的内容我会很快更新。


