目录
栈和队列被称为插入和删除受限制的线性表。
🍁一、栈的基本概念
🌕(一)栈的概念:
①:栈是一种特殊的线性表,其只允许在固定的一端进行插入和删除元素操作。 进行数据插入和删除操作的一端 称为栈顶,另一端称为栈底。 栈中的数据元素遵守后进先出 LIFO ( Last In First Out )的原则。②:压栈:栈的插入操作叫做进栈 / 压栈 / 入栈, 入数据在栈顶 。③:出栈:栈的删除操作叫做出栈。 出数据也在栈顶 。![]()
🍁二、栈的顺序存储结构(栈的实现)
注意:栈的实现一般可以使用数组或者链表实现,相对而言数组的结构实现更优一些。因为数组在尾上插入数据的代价比较小。
🌕(一)、特点
①:栈的顺序存储结构简称“顺序栈”;
②:顺序栈利用一组连续地址的存储单元(数组)依次存放从栈低到栈顶的数据元素,通常用一维数组存放栈的元素,同时设top“箭头”(有些地方称top为“指针”,但不是C语言中的指针,top是一个整型变量,所以为了避免混淆,小编称其为“箭头”)指示栈顶元素的当前位置,空栈时top值为0。
🌕(二)、静态实现
因为栈是特殊的线性表,所以栈的实现和线性表差不多,只是多了一个top“箭头”;
#define N 10 typedef struct Stack { int data[N]; int top; }Stack;
静态实现有一个很大的缺点就是扩容不方便,所以具体实现我们会使用动态实现;
🌕(三)、动态实现(优先)
因为线性表在之前的文章已经实现过了 ,而栈的实现和线性表差不多,所以小编就快速实现;具体操作可以参考小编之前的文章;
⭐️ (三.1)、定义
在原基础上多了一个top箭头用于指向栈顶位置;
typedef int DataType; typedef struct Stack { DataType* head; int top;//指向栈顶 int catacity;//现有最大空间数 }ST;
⭐️(三.2)、初始化
//初始化 void STinit(ST* ps) { assert(ps); //刚开始没有元素,所以top指向0 ps->top = 0; ps->catacity = 0; ps->a = NULL; }
⭐️(三.3)、销毁
//销毁 void STDestroy(ST* ps) { assert(ps); free(ps->a); ps->top = 0; ps->catacity = 0; }
⭐️(三.4)、入栈(插入元素)
因为栈是只能在栈顶进行操作,所以不存在头插尾插的概念,插入元素就叫“入栈”。
而且也不会说在某个位置插入元素,所以我们扩容不需要单独创建一个函数,这里只有入栈一个操作涉及扩容。
//入栈 void STPush(ST* ps, DataType x) { assert(ps); //空间满了进行增容 if (ps->top == ps->catacity) { //第一次catacity值为0,所以判断一下给予赋值 int newCatacity = (ps->catacity == 0 ? 4 : ps->catacity * 2); //使用realloc函数进行增容,刚开始a为NULL的话realloc函数的作用和malloc相同 DataType* tmp = realloc(ps->a, sizeof(DataType) * newCatacity); //检查是否增容成功 if (tmp == NULL) { perror("realloc"); return; } ps->a = tmp; ps->catacity = newCatacity; } //插入 ps->a[ps->top] = x; ps->top++; }
⭐️(三.5)、出栈
出栈非常简单,只需要将top值减1即可;
因为top值减1,则下次就不会访问到出栈的元素,而之后入栈也会将其覆盖,所以只需要将top值减1,但是,首先我们要检查一下是否为空栈,如果top==0,则为空栈,此时需要提示出栈失败;
//出栈 void SLPop(ST* ps) { assert(ps); //ps->top==0时为空栈 if (0 == ps->top) { printf("栈为空,出栈失败!\n"); return; } //出栈 --ps->top; }
⭐️(三.6)、获取栈的元素个数
//获取栈的元素个数 int STSize(ST* ps) { assert(ps); return ps->top; }
⭐️(三.7)、判断是否为栈空
//判断是否为栈空
bool STEmpty(ST* ps)
{
assert(ps);
return ps->top == 0;
}
注意:后面(三.6)和(三.7)这两个操作只有一句代码,有的小伙伴就有疑问,为什么不直接使用,而是要去封装成一个函数?
答:对于数据结构,我们尽量不要自己去访问结构的数据,尽量提供这些接口函数去使用,因为对于我们程序员才知道top指向栈顶,所以可以使用top,但对于用户角度,他根本不知道top代表什么,他只知道通过这些函数接口去实现操作。
⭐️(三.8)、获取栈顶元素
//获取栈顶元素 DataType STTop(ST* ps) { assert(ps); if (0 == ps->top) { printf("栈为空,获取失败!\n"); return NULL; } return ps->a[ps->top - 1]; }
⭐️(三.9)、遍历栈
值得注意的是,遍历栈时,为了体现栈的后进先出的特性,我们不会对遍历操作重新封装成函数,而是边遍历边出栈,遍历完后,栈变为空栈,这和后期栈的使用场景有着特别的联系。
🍁三、经典例题——有效的括号
(一)、题目
(二)、解答
首先我们要知道这道题必须要确定:
1.括号的数量匹配
2.括号的顺序要匹配
所以我们有以下思路:
①:遇到左括号我们就入栈;②:遇到右括号,我们就出栈,将栈顶的左括号拿出来进行匹配;
如果可以匹配,则继续判断下一个符号,是左括号就进栈,是右括号就出栈匹配;
如果不匹配,则返回false。
好处:
①:这样可以确保每一个左括号都是与其相邻的最近的右括号相匹配;
②:到最后,
如果栈为空时,外面还有右括号,则数量不匹配;
如果栈不为空,但外面的符号为空,则数量不匹配,返回false;
(三)、代码实现
bool isValid(char* s) { ST st; //初始化 STinit(&st); //匹配 while (*s) { switch (*s) { //左括号入栈 case '(': case '[': case '{': STPush(&st, *s); break; //右括号出栈进行匹配 //我们只需要判断不匹配的情况,因为匹配的时候默认继续,不匹配则直接返回false case ')': case ']': case '}': //如果有右括号,但此时栈为空栈,所以此时数量不匹配 if (STEmpty(&st)) { STDestroy(&st); return false; } //出栈匹配 DataType topval = STTop(&st); STPop(&st); //不匹配 if ((*s == '(' && topval != '(') || (*s == '[' && topval != '[') || (*s == '{' && topval != '{') ) { STDestroy(&st); return false; } break; } s++; } //当外面的符号匹配完后,看栈是否为空,不为空则数量不匹配。 //布尔值:0表示false,非0表示true bool ret = STEmpty(&st); STDestroy(&st); return ret; }
🍁四、顺序栈测试源代码
(一)、main.c
#include "stack.h" void test1() { ST st; STinit(&st); STPush(&st, 1); STPush(&st, 1); STPush(&st, 1); STPush(&st, 1); STPush(&st, 1); STPush(&st, 1); while (!STEmpty(&st)) { printf("%d ", st.a[st.top-1]); STPop(&st); } printf("\n"); STDestroy(&st); } bool isValid(char* s) { ST st; //初始化 STinit(&st); //匹配 while (*s) { switch (*s) { //左括号入栈 case '(': case '[': case '{': STPush(&st, *s); break; //右括号出栈进行匹配 //我们只需要判断不匹配的情况,因为匹配的时候默认继续,不匹配则直接返回false case ')': case ']': case '}': //如果有右括号,但此时栈为空栈,所以此时数量不匹配 if (STEmpty(&st)) { STDestroy(&st); return false; } //出栈匹配 DataType topval = STTop(&st); STPop(&st); //不匹配 if ((*s == '(' && topval != '(') || (*s == '[' && topval != '[') || (*s == '{' && topval != '{') ) { STDestroy(&st); return false; } break; } s++; } //当外面的符号匹配完后,看栈是否为空,不为空则数量不匹配。 //布尔值:0表示false,非0表示true bool ret = STEmpty(&st); STDestroy(&st); return ret; } int main() { test1(); //例题"有效的括号" char* arr1 = "((([[{}]])))"; char* arr2 = ")"; char* arr3 = "("; bool is1 = isValid(arr1); bool is2 = isValid(arr2); bool is3 = isValid(arr3); //因为c语言中没有bool值的说法,所以打印时可以用一个条件运算符辅助 printf("%s\n", (is1 ? "ture" : "false")); printf("%s\n", (is2 ? "ture" : "false")); printf("%s\n", (is3 ? "ture" : "false")); return 0; }
(二)、Stack.c
#include "stack.h" //初始化 void STinit(ST* ps) { assert(ps); //刚开始没有元素,所以top指向0 ps->top = 0; ps->catacity = 0; ps->a = NULL; } //销毁 void STDestroy(ST* ps) { assert(ps); free(ps->a); ps->top = 0; ps->catacity = 0; } //入栈 void STPush(ST* ps, DataType x) { assert(ps); //空间满了进行增容 if (ps->top == ps->catacity) { //第一次catacity值为0,所以判断一下给予赋值 int newCatacity = (ps->catacity == 0 ? 4 : ps->catacity * 2); //使用realloc函数进行增容,刚开始a为NULL的话realloc函数的作用和malloc相同 DataType* tmp = realloc(ps->a, sizeof(DataType) * newCatacity); //检查是否增容成功 if (tmp == NULL) { perror("realloc"); return; } ps->a = tmp; ps->catacity = newCatacity; } //插入 ps->a[ps->top] = x; ps->top++; } //出栈 void STPop(ST* ps) { assert(ps); //ps->top==0时为空栈 if (0 == ps->top) { printf("栈为空,出栈失败!\n"); return; } //出栈 --ps->top; } //获取栈的元素个数 int STSize(ST* ps) { assert(ps); return ps->top; } //判断是否为栈空 bool STEmpty(ST* ps) { assert(ps); return ps->top == 0; } //获取栈顶元素 DataType STTop(ST* ps) { assert(ps); if (0 == ps->top) { printf("栈为空,获取失败!\n"); return ps->a[ps->top - 1]; } }
(三)、Stack.h
#define _CRT_SECURE_NO_WARNINGS 1 #pragma once #include<stdio.h> #include<assert.h> #include<stdlib.h> #include<stdbool.h> //typedef struct Stack //{ // int data[N]; // int top; //}Stack; typedef int DataType; typedef struct Stack { DataType* a; int top;//指向栈顶 int catacity;//现有空间大小 }ST; //初始化 void STinit(ST*ps); //销毁 void STDestroy(ST* ps); //入栈 void STPush(ST* ps, DataType x); //出栈 void STPop(ST* ps); //获取栈的元素个数 int STSize(ST* ps); //判断是否为栈空 bool STEmpty(ST* ps); //获取栈顶元素 DataType STTop(ST* ps);
🍁五、栈的链式存储结构
1.栈的链式存储结构简称“链式栈”;
2.其组织形式与单链表相似,链表的尾部结点是栈底,链表的头部结点是栈顶;
3.由于只在链栈的头部进行操作,所以链栈没有必要设置头结点。
本次知识到此结束,希望对你有所帮助!