栈的相关知识

栈的相关知识及运用


栈是一个特殊的线性表,只能在表的一端进行操作(即是栈顶)有一个特别的性质,“先进后出”。

存储结构

可以用顺序栈(即是数组)和链栈存储(即为链表)

栈的特殊之处

限制在表的一端进行插入和删除操作。 能够进行操作的一端是可移动端,称之为栈顶,通常用一个“栈顶指针top”指示,它的位置会随着操作而发生变化;与此相对,表的另一端是固定端,即称之为栈底。

下面我们来模拟一下栈的数据存储和读取的方式。

假设现在有三个元素123,按照先小后大的顺序存进栈之中。

请添加图片描述

  • 先将1存进栈中

请添加图片描述

而后将23继续存入栈中,就会得到:

请添加图片描述

当我们想要将数据取出来时,指针会下移,会按照321的顺序依次读取出来

请添加图片描述

从以上举例我们不难发现,栈的两个重要操作

  • 出栈

  • 入栈

栈的相关操作

一、栈的结构

栈结构的实现,我们可以使用链表和数组两种方式进行实现

链表版本:

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");
}

其实从以上操作,我们不难看出其实,数组版本的代码更加简洁以及易懂,即数组更加符合栈的相关结构,个人觉得使用数组会更加方便。

栈的相关运用

反转链表

[206. 反转链表]

从题目之中,我们不难看出,栈的数据结构似乎完美的契合此题,我们只需要将链表的数据压入栈之中,然后再将栈之中的内容进行遍历,就能够成功地将链表反转。

实现该过程的代码如下

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);//!!最后检查栈是否为空,若为空则说明仍有未匹配的项
}
双栈

[LCR 147. 最小栈]

拿到这道题,一个直接的想法应该是,我们创建个栈,将栈顶元素赋值为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;
}//将最小值直接输出,一目了然。

总结

本文讨论了栈的相关内容,完成了出入栈,遍历栈等操作,进行了例题巩固,用栈实现反转链表,括号匹配算法等内容,在日常中解决问题有着相当大的作用,希望大家能够通过本文粗略地窥见关于栈的运用,理解相关知识。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值