🎆了解顺序栈和链式栈
- 在数据结构的世界中,栈和顺序表以及链表(链式存储结构)一样,都是线性的存储结构。而栈是一种运算受限的线性表(逻辑结构概念)。而它运算的特殊性就在于其"后进先出"的特性。打个比方就是,你在餐桌上不断的放餐盘,而你拿这些餐盘的时候,你只能从你刚刚放入的最顶上的餐盘开始。
- 而你每次放盘的那个顶端就叫做栈顶(Top),也就是线性表允许插入和删除的一端。
- 放在最底下的盘子,叫做栈底(Bottom),也就是不允许插入和删除的另一端。
- 栈底固定,栈顶浮动。当栈中元素个数为零时,这个栈就叫做空栈。而插入的操作一般叫做进栈(push),而删除的操作(从栈中提取指定元素)叫做出栈(pop)。
现在在介绍栈具体实现的两种方式。众所周知,所有的数据结构都跟链表和数组有着莫大的关联。而栈也是如此。而采用顺序储存的栈就叫做顺序栈,借助数组来存放其从栈底到栈顶的各个数据。而链式栈就是用借助链表实现,我们通常将链表的头部作为栈顶,将尾部作为栈底。
🎆栈的初始化和建立
👇顺序栈
- 首先我们介绍顺序栈的初始化以及简易的相关操作,在开始看代码前我们需要了解一下几个关键点。
- 1.顺序栈是一个结构体,内部由一个int类型的变量和一个存放某种数据类型的数组。
#define MAXsize 20
typedef int Elemtype;
typedef struct odstack {
Elemtype data[MAXsize];
int top;
}ODstack;
- 2.看到代码里面的top这个变量,他就是这个栈的栈顶指针(top表示的是当前栈顶元素的下标)
- 3.对于顺序的简单操作介绍三个(初始化栈,进栈,出栈)
- 4.因为data[ MAXsize ]是从下标0开始。所以s1->top=-1就表示这个栈为空栈,而s1->top=MAXsize-1就表示这个栈为满栈。
- 5.初始化:就是把栈内的top值等于-1
- 6.压栈:判断是否满栈,不是就top先加1,后入栈
- 7.出栈:判断是否空栈,不是就先出栈,再top-1
来看看代码实现👇👇👇👇👇👇👇
#include<stdio.h>
#include<stdlib.h>
#include<stdbool.h>
#define MAXsize 20
typedef int Elemtype;
typedef struct odstack {
Elemtype data[MAXsize];
int top;
}ODstack;
bool initODstack(ODstack* s1) {
s1->top = -1;
return true;
}
bool push(ODstack* s1,Elemtype newdata) {
if (MAXsize-1==s1->top) return false;
else s1->data[++s1->top]=newdata;
return true;
}
bool pop(ODstack* s1,Elemtype *outdata) {
if (-1 == s1->top)return false;
else *outdata = s1->data[s1->top--];
return true;
}
int main(void) {
ODstack s1;
initODstack(&s1);
int i;
for (i = 0;i < 6;i++) {
int newdata;
scanf("%d", &newdata);
push(&s1, newdata);
}
int length = s1.top;
for (i = 0;i <= length;i++) {
Elemtype *outdata=(int*)malloc(sizeof(ODstack));
pop(&s1, outdata);
printf("顶层为:%d\n", *outdata);
}
}
👇链式栈
- 简单的了解完顺序栈的操作,现在让我们来看看链式栈的相关简单操作。链式栈的的总体思路和顺序栈的相同的,只不过它把链表的头部当作栈顶,把链表的尾部作为栈底。
- 那么对于这种链式栈,我们如何进行入栈和出栈操作呢。实现入栈就是将数据从链表的头部插入。而实现出栈就是删除链表头部的首元结点。
- 那么相比顺序栈,链式栈有什么特殊的地方呢。1.不需要头结点。2.因为链表长度是动态的,所以链式栈基本不存在栈满这种情况。3.头指针就是栈顶,空栈相当于头指针指向空。
- 现在让我们看看链式栈的所有相关操作👇👇👇👇
#include<stdio.h>
#include<stdlib.h>
#include<stdbool.h>
typedef struct linkstack {
int data;
struct linkstack* next;
}LINKstack;
LINKstack* initstack(LINKstack* s) {
s = NULL;
return s;
}
bool emptystack(LINKstack* s) {
if (s)return true;
else return false;
}
- 为空栈的判断条件是类似的
- 入栈和出栈👇
LINKstack* push(LINKstack* s, Elemtype newnum) {
LINKstack* newstack=(LINKstack*)malloc(sizeof(LINKstack));
newstack->data = newnum;
newstack->next = s;
s = newstack;
return s;
}
LINKstack* pop(LINKstack* s,Elemtype *newnum) {
if (s==NULL) {
printf("栈已空");
return NULL;
}
else {
*newnum = s->data;
LINKstack* p = s;
s = s->next;
free(p);
return s;
}
}
🎆栈的应用范围
- 简单的了解完了栈,那我们在什么样的情况下才会使用到栈这种结构呢?
- 栈可以在函数调用的时候储存断点,函数递归的时候它会发挥巨大的作用。
- 我们日常生活中上网常使用的浏览器左上角的回退功能,底层就使用了栈存储结构。
- 括号匹配问题(编译器中检查你写的程序括号正确与否)也是栈结构来实现。
- 进制转换,表达式求值,逆序输出等等
🎇简单用栈解决问题——(有效的括号)
- 在两个月前,有一道在力扣上的题目,也就是我们提到的括号匹配问题,就运用到了栈的方法,现在让我们通过具体的例子,更深刻的去理解栈的使用方法
?简易循环实现
- 对于这道题,如果之前我们还没学习过栈的有关知识也是可以的解决的。首先你需要对例子进行自己观察,你也可以自己手写出更长的有效括号去寻找其中的规律。
- 你可以发现,在最后一个左括号的右边必须是一个跟他相对应的右括号。同时我们可以定义一个数组来存储之前还没有匹配的左括号,后面的右括号去进行判断。(整个逻辑等于运用循环和数组去模拟了一个栈的思路)
- 代码如下👇👇
bool isValid(char * s){
if(strlen(s)%2==1)return false;
char l[10000],r[10000];
int i=0,j=0,left=0;
while(*s!='\0')
{
if(*s=='('||*s=='{'||*s=='[')
{
l[++i]=*s;
left++;
}
else if(*s==')')
{
r[j]='(';
if(r[j]!=l[i])
return false;
j++;
i--;
}
else if(*s=='}')
{
r[j]='{';
if(r[j]!=l[i])
return false;
j++;
i--;
}else if (*s==']')
{
r[j]='[';
if(r[j]!=l[i])
return false;
j++;
i--;
}
s++;
}
if(j!=left)return false;
return true;
}
?利用栈的方法解决
- 用栈去解决这道题目是最常用也是最好理解的方法,在之前的循环中我们不知不觉就已经摸到了栈的解决方法,现在我们使用栈来更为准确的写一次。
- 首先我们看一张选自力扣官方答案的图片,它很清楚的展示了栈在这道题上的操作过程。
- 让我们来看代码👇👇👇
char reversech(char s){
if(s==')')return '(';
else if(s=='}')return '{';
else if(s==']')return '[';
return 0;
}
bool isValid(char * s){
int n =strlen(s);
if(n%2!=0)return false;
int i=0;
char stack[n+1];
int top=-1;
for(i=0;i<n;i++){
if(s[i]=='('||s[i]=='{'||s[i]=='['){
stack[++top]=s[i];
}else{
if(top==-1||stack[top]!=reversech(s[i])) return false;
top--;
}
}
return top==-1;
}
?最长有效括号
- 通过上面关于栈的应用,我们想必对这个数据结构已经有了方向,那我们在上一题的基础上再增加难度。我们去寻找这整串括号中最长的有效括号。
我们先关注其中的几个关键点 - 首先有效括号必须以左括号开始
- 分析几种特殊情况 在遇到这种括号时()(),通过我们分析我们发现,如果我们直接找出一整段有效括号(直到发现无效)是错误的,碰到()(()这类情况就可能会错误。什么意思呢,我们不是直接找这个4,而是去进行2+2的操作。
- 怎么计算当前有效括号的长度? 是定义一个curlength=0自增,还是下标相减。
让我们来看一下代码👇👇👇
int longestValidParentheses(char * s){
if(!s)return 0;
int length=strlen(s);
int stack[length+1];
int max=0;
int top=-1;
int curlength=0;
stack[++top]=-1;
for(int i=0;i<length;i++){
if(s[i]=='('){
stack[++top]=i;
}else{
--top;
if(top==-1){
stack[++top]=i;
}else{
curlength=i-stack[top];
if(curlength>max)max=curlength;
}
}
}
return max;
}
除了这个方法以外,还有另外一种题目的转换思路。还是用一个数组去存储没个入栈元素的下标,如果这个左括号匹配成功,就把他置1。最后把剩下来的左括号或右括号置为0。那么问题就变成了找最长连续1的问题,就好解决很多。
🥇写在结尾
- 关于栈的题目还有很多,栈的应用也是很广泛,如果想要提高这方面的编写能力,仍然需要大量的刷题,以及知识面的拓展。
|