栈的相关知识及运用
栈是一个特殊的线性表,只能在表的一端进行操作(即是栈顶)有一个特别的性质,“先进后出”。
存储结构
可以用顺序栈(即是数组)和链栈存储(即为链表)
栈的特殊之处
限制在表的一端进行插入和删除操作。 能够进行操作的一端是可移动端,称之为栈顶,通常用一个“栈顶指针top
”指示,它的位置会随着操作而发生变化;与此相对,表的另一端是固定端,即称之为栈底。
下面我们来模拟一下栈的数据存储和读取的方式。
假设现在有三个元素1
、2
、3
,按照先小后大的顺序存进栈之中。
- 先将
1
存进栈中
而后将2
、3
继续存入栈中,就会得到:
当我们想要将数据取出来时,指针会下移,会按照3
、2
、1
的顺序依次读取出来
从以上举例我们不难发现,栈的两个重要操作
-
出栈
-
入栈
栈的相关操作
一、栈的结构
栈结构的实现,我们可以使用链表和数组两种方式进行实现
链表版本:
typedef struct Stack{
int data;
struct Stack* next;
}Stack;
数组版本:
typedef struct Stack{
int data[100];
int top;//栈顶
int bottom;//栈底
}Stack;
二、栈的初始化
初始化就是栈的申请空间,注意申请空间为空的情况;
链表版本:
Stack* initstack(){
Stack* s =(Stack*)malloc(sizeof(Stack));
if(s==NULL){
//分配失败
return 0;
}
s->data = 0;
s->next = NULL;
return s;
}
数组版本:
在数组版本之中,从头到尾只需要申请一次内存就足够;
Stack* initstack(){
Stack* s =(Stack*)malloc(sizeof(Stack));
if(s==NULL){
//分配失败
return 0;
}
s->top = s->bottom = 0;
return s;
}
三、栈的判断
判断栈是否为空,若是则返回1
,反之返回0
;
链表版本:
int isEmpty(Stack* s)//传入的s为栈顶的指针,在链表版本中,栈顶data存储栈中节点个数
{
if(s->data==0||s->next==NULL){
return 1;
}
else{
return 0;
}
}
数组版本
int isEmpty(Stack* s){
if(s->top==s->bottom)//当栈顶和栈底相等时,说明栈为空
{
return 1;
}
else{
return 0;
}
}
四、数据入栈
进栈操作,类似于线性表(即链表)的插入操作,即“增”操作,但由于栈的特殊性,只允许在栈顶进行操作,所以可称为进栈操作,即在栈顶增加元素,有点类似于单链表操作中的头插法。
链表版本
链表版本中,每一次数据的入栈都需要另外申请空间来进行操作;
void push(Stack* s,int data){
Stack* new =(Stack*)malloc(sizeof(Stack));
new->data=data;
new->next=s->next;
s->next=node;
s->data++;
}
数组版本
void push(Stack* s,int data){
data[s->top]=data;
s->top++;
}
五、出栈
链表版本
int pop(Stack* s){
if(isEmpty){
return -1;
}
else{
Stack new=s->next;
int data=new->data;
s->next=new->next;
free(new);//数据出栈之后需要将栈中的内存释放掉
return data;
}
}
数组版本
int pop(Stack* s,int data){
if(s->top!=s->bottom){//栈非空
data=s->data[s->top-1];//栈顶内容输出
s->top--;//栈顶减1
return data;
}
}
六、栈的遍历
栈的遍历即将栈中的元素打印出来,到栈底为停止。
链表版本
void pritntStack(Stack* s){
Stack* new = s->next;
while(new){
printf("%d->",new->data);
node=node->next;
}
printf("NULL");
}
数组版本
void pritntStack(Stack* s){
while(s->top!=s->bottom){
printf("%d->",s->data[s->top-1]);
s->top--;
}
printf("NULL");
}
其实从以上操作,我们不难看出其实,数组版本的代码更加简洁以及易懂,即数组更加符合栈的相关结构,个人觉得使用数组会更加方便。
栈的相关运用
反转链表
从题目之中,我们不难看出,栈的数据结构似乎完美的契合此题,我们只需要将链表的数据压入栈之中,然后再将栈之中的内容进行遍历,就能够成功地将链表反转。
实现该过程的代码如下
struct ListNode {
int val;
struct ListNode *next;
};
struct ListNode* reverseList(struct ListNode* head) {
if(head==NULL||head->next==NULL){
return head;
}
struct ListNode* stack=(struct ListNode*)malloc(sizeof(struct ListNode));
stack->next=NULL;
struct ListNode* list=head;
while(list!=NULL){
struct ListNode* next=list->next;//将数据压入栈之中
list->next=stack->next;
stack->next=list;
list=next;
}
struct ListNode* newHead = stack->next;//返回栈顶的指针
free(stack);//注意将内存释放
return newHead;
}
括号匹配算法
知道了栈的相关结构,我们还可以完成括号的匹配算法,来完成括号的配对,随手举出几个例子
{({[]})}
、(){[]}()
、()()[]
,我们发现与左括号相匹配的右括号都是按序进行匹配的。
以{({[]})}
为例
当接受到第一个左括号{
时,第一个左括号进行等待匹配。
而当第二个左括号(
被读进来时,第二个括号的匹配的等级更高,先进行匹配
接下来的{
与[
也是如此.
我们不难发现,越后面读入的左括号,等待匹配的优先度更高,结合栈的思想,我们可以将已经读到的左括号压入栈中,在读到左括号时,将右括号从栈中读出,查看是否匹配,若匹配则重复以上步骤,直至由括号组成的字符串结束,否则直接退出返回flase
。
OK,知道思路了之后我们就要开始编程的实现啦。
栈的结构
elem用来存储由括号组成的数组,top
为栈顶的指针
#define MAX 1000;
typedef struct
{
char elem[MAX];
int top;
}Stack;
初始化
Stack* intiStack(){
Stack* s =(Stack*)malloc(sizeof(Stack));
if(s==NULL){
//分配失败
return 0;
}
s->top=-1;
return s;
}
入栈
int push(Stack* s,char x){
if(s->top==MAX-1){
return -1;
}
s->top++;
s->elem[s->top] = x;
return 1;
}
出栈
int pop(Stack* s,char *x){
if(s->top == -1){
return -1;
}
else{
*x=s->elem[s->top];
s->top--;
return 1;
}
}
判断
int isEmpty(Stack* s){
if (S->top == -1) //如果栈顶指针为-1,说明栈为空
{
return 1;
}
else //否则,栈不为空
{
return -1;
}
括号匹配
int BracketMatch(char *x){
Stack* a = intiStack();
int len=strlen(x);
for(int i=0;i<len;i++){
if(x[i]=='{'&&x[i]=='('&&x[i]=='['){
push(s,x[i]);//对左括号进行入栈操作
}
else{
if(isEmpty(s)){
return false;//若读入右括号时,栈为空,说明没有现成的左括号与之相匹配,返回false
}
char temp;
pop(s,temp);//进行出栈操作
//若括号类型不匹配则返回false
if(x[i] == ')' && temp!= '(') {
return false;
}
if(x[i] == ']' && temp!= '[') {
return false;
}
if(x[i] == '}' && temp!= '{') {
return false;
}
}
}
return StackEmpty(S);//!!最后检查栈是否为空,若为空则说明仍有未匹配的项
}
双栈
拿到这道题,一个直接的想法应该是,我们创建个栈,将栈顶元素赋值为min
,然后将栈中元素不断取出,与min
进行比较。
若只有一个栈,当然如何操作都可以,但如果有一堆数量很大的栈让你去找出其最大值,是不是会计较麻烦呢?
我们也可以设计一个函数和一个栈的结构,让栈的最小值直接存储在栈之中,这样子对最小值的调用会更加地方便。
于是我们用上了双数组,将最小值与正常的栈的元素分开存储,图例如下
#define max 1000;
//x[MAX]为存储正常元素,min[MAX]为存储最小元素
typedef struct {
int x[MAX];
int min[MAX];
int x_top;
int min_top;//x_top和min_top分别为两个栈的栈顶
int min_value;
} Stack;
Stack* minStackCreate() {
Stack *s = (Stack*)malloc(sizeof(Stack));
s->min_top=s->x_top=-1;
s->min_value=0;
return s;
}
void minStackPush(Stack* s, int x) {
if((s->x_top )< MAX)
{
if(s->x_top == -1){
s->min_value = x;//若min栈没有元素,x即为最小值
}
s->x[++(s->x_top)] = x;
if(x < (s->min_value)){
s->min_value = x; //当前元素比min_value小时改变min_value
}
s->min[++(s->min_top)] = s->min_value;//将min中的栈顶赋值给min_value,即将最小值转入
}
}
void StackPop(MinStack* obj) {
--(obj->x_top);
--(obj->min_top);
if(obj->min_top != -1)//判断栈不为空
{
s->min_value =s->min[s->min_top];//出栈后将min_value变成min_stack的栈顶元素
}
}
int minStackTop(Stack* s) {
return s->x[s->x_top];//返回x栈顶元素
}》
int minStackMin(Stack* s) {
return s->min_value;
}//将最小值直接输出,一目了然。
总结
本文讨论了栈的相关内容,完成了出入栈,遍历栈等操作,进行了例题巩固,用栈实现反转链表,括号匹配算法等内容,在日常中解决问题有着相当大的作用,希望大家能够通过本文粗略地窥见关于栈的运用,理解相关知识。