前言:
目前,我们对链表剖析已经有一定的程度了,终于伴随着我对栈的学习,我们将开启新的篇章。
我将一点点介绍栈,并举出一道例题帮助理解栈的使用。
正文:
栈是什么?
栈是数据结构的一种,而数据结构是一种编程的思维以及功能的实现。
我们有时候想,让数据后进先出,就像往一个羽毛球桶一次次地塞入羽毛球,最后一个羽毛球是最先放入的,最后一次放入的球必然先被拿出来。
我们把这种能让先进入的数据最后一个出去的这种编程思想,叫做栈,一种数据结构。
正规的定义是:栈是一种特殊的线性表,其只允许在固定的一段进入插入和删除元素操作,进行数据插入与删除的一段叫做栈顶,另一端叫做栈底,栈中元素遵循后进先出的原则
栈的功能有哪一些?
栈的功能是由很多个部分构成的,它包括:
1.压栈/
2.出栈/(它只是取出来读并没有删除,往往我们在写实际使用栈的时候这个功能与删除功能一同出现)
3.取出一个栈元素后的删除/
4.判断当前栈是否为空/
5.判断当前栈的元素个数/
6.销毁栈
栈的实现方法?
栈究竟是用数组?还是单链表?亦或是双链表?
都可以,但是双链表还比单链表多开一个指针的空间那还不如用单链表
可单链表与数组比起来的话,你说数组每次都要扩容很不方便,但是我们也应该看到数组的扩容是输入很多次才扩一次容,而且我们开辟空间不是一种奢侈行为,再者我们知道数组与链表在读取数据的时候,数组表现得更好,因为数组读取前总会先将数据从内存中转移到寄存器中,而数组中元素的地址又是连续的,一口气就可以转移很多干净完整的数据到寄存器,数据在寄存器中直接地读取,而链表的每一个内存空间大概并不是连续的而每一次转移cpu的底层逻辑是转移多一点的数据,当cpu转移数据到寄存器的时候甚至有可能把节点接着临近地址中的数据给提取到了,这就会造成污染,所以我们综合思考,还是选择数组比较好一些。
头文件中的声明
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>
typedef int undefine;
typedef struct stacknode {
undefine* a;
int size;
int capacity;
}sn, * snp;
//初始化栈
void initstacknode(snp mystack);
//销毁栈
void destorystack(snp mystack);
//添加栈元素
void stackplush(snp mystack, undefine x);
//删除栈元素
void detelestack(snp mystack);
//取出栈元素
undefine stackout(snp mystack);
//判断栈是否为空
bool ifisempty(snp mystack);
void emptyorno(snp mystack);
//返回元素的个数
int stacknum(snp mystack);
//打印栈
void printstack(snp mystack);
初始化我们的栈
我们可以在main函数里写:
sn s;
接着调用这个函数:
initstacknode(&s);
这个函数将我们的结构体实例初始化了,以便更好地操作。
void initstacknode(snp mystack) {
//如果你压根没有定义一个栈结构实例,那你初始化个der,来个assert
assert(mystack);
//capacity的作用是用来标记当前占用的空间值以及辅助计算未来要开的空间值
mystack->capacity = 0;
//size不仅表明当前栈中有多少元素,而且它还可以作为即将录入元素的下标
mystack->size = 0;
//a动态指针,随时为其分配一段新的内存空间。
mystack->a = NULL;
}
入栈怎么入?
我们设想了元素应该先入后出,好,怎么入我们也得实现一下。
void stackplush(snp mystack,undefine x) {
assert(mystack);
//这巧妙地把capacity和size为0的时候也算入进去了
if (mystack->capacity == mystack->size) {
//新的空间为多少?如果你是0那就先给你给4单位的空间吧,如果不是那就开个2*capacity单位的空间吧。
int newcapacity = mystack->capacity == 0 ? 4 : 2 * (mystack->capacity);
//别忘了int占4个字节,你得用它来再×newcapacity
snp newstack = (snp)realloc(mystack->a, newcapacity * sizeof(int));
//这是一个防止扩容失败的举动,你想想不用一个中间量newstack那如果扩容失败了原来的
//数组中的数据不但被销毁了,你也没有得到新的空间,这就会造成不可逆的损失。
if (newstack == NULL) {
perror("realloc wrong");
exit(1);
}
mystack->a = newstack;
mystack->capacity = newcapacity;
}
mystack->a[mystack->size] = x;
//别忘了size++
mystack->size++;
}
出栈怎么出?
void detelestack(snp mystack) {
assert(mystack);
//当栈已经为空了就报错
assert(mystack->size != 0);
//别看只是size--,你想哎呀原来的数还没有真正被删除对不对,但是随着后续的操作
//它完全可被覆盖(再压栈一次)/销毁(最终消除栈)
mystack->size--;
}
怎么读取栈元素?
undefine stackout(snp mystack) {
assert(mystack);
//空了就别读取了
assert(mystack->size != 0);
//并不是size下标那位,是size-1
return mystack->a[mystack->size - 1];
}
栈怎么判空?
bool ifisempty(snp mystack) {
assert(mystack);
//size为0返回true
if (mystack->size == 0)return true;
//否则返回false
return false;
}
void emptyorno(snp mystack) {
assert(mystack);
//可写在main内的初始化后作为提示
if (ifisempty(mystack)){
printf("注意当前的栈为空,请先压栈否则会报错\n");
}
else {
printf("当前的栈不为空,你可以放心操作\n");
}
}
返回栈元素个数?
int stacknum(snp mystack) {
assert(mystack);
return mystack->size;
}
在实践中理解栈:
题目:有一个字符串,字符串内是字符'[',']','{','}','(',')'的括号组合,要判断这些括号之间是否匹配恰当。
思路:我们要比较括号匹不匹配,最后一个左括号一定要先匹配,符合栈这种编程思维,所以我们用栈来实现这个程序,遇到左括号就拉入栈中,接着指针往下一个括号字符走去,下一个如果还是左括号继续入栈,如果不是这时候就要与刚刚入栈的那个先比一下了,取出栈顶元素,同时删除,使得栈顶元素更新。
我们不得不思考特殊情况,如果一开始就是一个右括号,这种情况下我们用来装左括号的栈是不是为空,这样的情况下返回是false,还有一种情况,如果我一直遇到的都是左括号,没有右括号,那我们就需要在最终再判定一下栈是否为空,空才是真正合格,不为空说明不合格,因为每一对括号的匹配都得删除一次栈顶,如果对数没问题的话肯定都把栈的元素一个个都删除完了。
bool isValidBrackets(char*a,snp s1) {
while (*a != '\0') {
if (*a == '[' || *a == '{' || *a == '(') {
stackplush(s1, *a);
}
else if (*a == ')' || *a == '}' || *a == ']') {
//巧妙地解决了如果第一个遇到的就是右括号和右括号多于左括号的情况。
if (ifisempty(s1))return false;
char k1 = stackout(s1);
detelestack(s1);
//不匹配的话,说明这些括号就是不合格的
if ((*a == ')' && k1 != '(') ||
(*a == ']' && k1 != '[') ||
(*a == '}' && k1 != '{')) {
return false;
}
}
a++;
}
if (!ifisempty(s1)) return false;
return true;
}
gitee地址:
