数据结构(四)-栈与栈的应用(递归、四则运算表达式)(Java、C语言)

今天我们来学习栈与栈的应用,并以此来学习栈在递归算法,四则运算表达式中的应用

一、栈的定义

大家在写程序的时候应该都遇到过一个异常叫做“栈溢出”那这个栈和我们今天要学习的栈有什么异同呢?
在这里插入图片描述
栈溢出就是缓冲区溢出的一种。
由于缓冲区溢出而使得有用的存储单元被改写,往往会引发不可预料的后果。程序在运行过程中,为了临时存取数据的需要,一般都要分配一些内存空间,通常称这些空间为缓冲区。如果向缓冲区中写入超过其本身长度的数据,以致于缓冲区无法容纳,就会造成缓冲区以外的存储单元被改写,这种现象就称为缓冲区溢出。缓冲区​长度一般与用户自己定义的缓冲变量的类型有关。栈溢出就是缓冲区溢出的一种。

我们今天要学习的栈是一种存储数据的结构,它表示,在存储和删除数据的时候只能在栈顶进行添加和删除的操作

栈的定义:栈是限定仅在表尾进行插入和删除操作的线性表。

二、栈的抽象数据类型

ADT Stack {
   数据对象:
   D = {ai | ai ∈ ElemSet,i = 1,2,3,....,n, n ≥ 0}  // ElemSet 表示元素的集合
   数据关系:
   R1={<ai-1, ai> | ai-1 , ai∈D,i=2...,n}  // ai-1为前驱,ai为后继
   约定 an 端为栈顶,a1 端为栈底
   基本操作:初始化、进栈、出栈、取栈顶元素等
}  ADT Stack

InitStack(&S)初始化操作
操作结果:构造一个空栈 S

DestroyStack(&S)销毁栈操作
初始条件:栈S已存在
操作结果:栈S被销毁

StackEmpty(S)判定栈是否为空栈
初始条件:栈S已存在
操作结果:若栈S为空栈,则返回TRUE,
若栈S为非空,则返回FALSE。
                  
StackLength(S)求栈的长度
初始条件:栈S已存在
操作结果:返回栈S的元素个数,即栈的长度

GetTop(S,&e)取栈顶元素
初始条件:栈S已存在且非空
操作结果:用e返回S的栈顶元素

ClearStack(&S)栈指控操作
初始条件:栈S已存在
操作结果:将S清为空栈

Push(&S,e)入栈操作
初始条件:栈S已存在
操作结果:插入元素e为新的栈顶元素

Pop(&S,&e)出栈操作
初始条件:栈S存在且非空
操作结果:删除栈SD栈顶元素an,并用e返回其值

三、栈的顺序存储结构(C语言、Java)

顺序存储结构我们在线性表的时候已经了解过了,栈也是一种特殊的线性表,只不过它只能在栈顶进行插入和删除的操作,而其他操作,例如查找等则没有这个限制,因此,在实现栈的时候,与实现线性表并没有太大差别。

而对于顺序存储结构,可以使用数组来实现。我们将数组的0角标的元素作为栈底,方便我们进行存储。这里只需要增加一个top变量来进行标识栈顶元素即可。

上代码,基本和线性表相似,只有插入和删除不一样,不过你可以理解为线性表的表尾进行插入和删除操作即可。

我觉得线性表弄懂了这个应该很好理解

#include "stdio.h"    
#include "stdlib.h"   

#include "math.h"  
#include "time.h"

#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0
#define MAXSIZE 20 /* 存储空间初始分配量 */

typedef int Status; 
typedef int SElemType; /* SElemType类型根据实际情况而定,这里假设为int */

/* 顺序栈结构 */
typedef struct
{
        SElemType data[MAXSIZE];
        int top; /* 用于栈顶指针 */
}SqStack;

Status visit(SElemType c)
{
        printf("%d ",c);
        return OK;
}

/*  构造一个空栈S */
Status InitStack(SqStack *S)
{ 
        /* S.data=(SElemType *)malloc(MAXSIZE*sizeof(SElemType)); */
        S->top=-1;
        return OK;
}

/* 把S置为空栈 */
Status ClearStack(SqStack *S)
{ 
        S->top=-1;
        return OK;
}

/* 若栈S为空栈,则返回TRUE,否则返回FALSE */
Status StackEmpty(SqStack S)
{ 
        if (S.top==-1)
                return TRUE;
        else
                return FALSE;
}

/* 返回S的元素个数,即栈的长度 */
int StackLength(SqStack S)
{ 
        return S.top+1;
}

/* 若栈不空,则用e返回S的栈顶元素,并返回OK;否则返回ERROR */
Status GetTop(SqStack S,SElemType *e)
{
        if (S.top==-1)
                return ERROR;
        else
                *e=S.data[S.top];
        return OK;
}

/* 插入元素e为新的栈顶元素 */
Status Push(SqStack *S,SElemType e)
{
        if(S->top == MAXSIZE -1) /* 栈满 */
        {
                return ERROR;
        }
        S->top++;				/* 栈顶指针增加一 */
        S->data[S->top]=e;  /* 将新插入元素赋值给栈顶空间 */
        return OK;
}

/* 若栈不空,则删除S的栈顶元素,用e返回其值,并返回OK;否则返回ERROR */
Status Pop(SqStack *S,SElemType *e)
{ 
        if(S->top==-1)
                return ERROR;
        *e=S->data[S->top];	/* 将要删除的栈顶元素赋值给e */
        S->top--;				/* 栈顶指针减一 */
        return OK;
}

/* 从栈底到栈顶依次对栈中每个元素显示 */
Status StackTraverse(SqStack S)
{
        int i;
        i=0;
        while(i<=S.top)
        {
                visit(S.data[i++]);
        }
        printf("\n");
        return OK;
}

int main()
{
        int j;
        SqStack s;
        int e;
        if(InitStack(&s)==OK)
                for(j=1;j<=10;j++)
                        Push(&s,j);
        printf("栈中元素依次为:");
        StackTraverse(s);
        Pop(&s,&e);
        printf("弹出的栈顶元素 e=%d\n",e);
        printf("栈空否:%d(1:空 0:否)\n",StackEmpty(s));
        GetTop(s,&e);
        printf("栈顶元素 e=%d 栈的长度为%d\n",e,StackLength(s));
        ClearStack(&s);
        printf("清空栈后,栈空否:%d(1:空 0:否)\n",StackEmpty(s));
        
        return 0;
}

看完了书上的C语言版的栈,我们来自己写一下Java版的栈


package Text;
public class Stack {
    private int top = -1;
    private final int DEFAULT_SIZE = 6;
    private String[] arr = new String[DEFAULT_SIZE];
    public boolean push(String value) {
        if(top == arr.length -1) {   
            return false;
        }
        ++top;
        arr[top] = value;
        return true;
    }
    public boolean pop() {
        if(top == -1) {
            return false;
        }
        String temp = arr[top];
        top--;
        return true;
    }
    public boolean isEmpty() {
        return top == -1;
    }
    public String getTop() {
        if(top == -1) {    //栈为空
            return null;
        }
        return arr[top];
    }
    public static void main(String[] args) {
        Stack stack = new Stack();
        System.out.println(stack.isEmpty());
        stack.push("abc");                   // 进栈
        stack.push("def");
        stack.push("ggg");
        System.out.println(stack.isEmpty());
        System.out.println(stack.getTop());
        System.out.println(stack.pop());    // 出栈
        System.out.println(stack.getTop()); // 获取栈顶元素
    }
}

四、栈的链式存储结构(C语言、Java)

还是一样的,与线性表的链式存储结构相似,只不过这里的指针变量不是指向后一个元素的了,而是指向前一个元素,一直指到第一个元素,第一个元素的指针域为空,至于出栈操作,很简单,只要把top指针指向要删除元素的底下一个元素即可,然后把要删除的栈顶元素释放内存即可。上代码

#include "stdio.h"    
#include "stdlib.h"   

#include "math.h"  
#include "time.h"

#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0
#define MAXSIZE 20 /* 存储空间初始分配量 */

typedef int Status; 
typedef int SElemType; /* SElemType类型根据实际情况而定,这里假设为int */


/* 链栈结构 */
typedef struct StackNode
{
        SElemType data;
        struct StackNode *next;
}StackNode,*LinkStackPtr;


typedef struct
{
        LinkStackPtr top;
        int count;
}LinkStack;

Status visit(SElemType c)
{
        printf("%d ",c);
        return OK;
}

/*  构造一个空栈S */
Status InitStack(LinkStack *S)
{ 
        S->top = (LinkStackPtr)malloc(sizeof(StackNode));
        if(!S->top)
                return ERROR;
        S->top=NULL;
        S->count=0;
        return OK;
}

/* 把S置为空栈 */
Status ClearStack(LinkStack *S)
{ 
        LinkStackPtr p,q;
        p=S->top;
        while(p)
        {  
                q=p;
                p=p->next;
                free(q);
        } 
        S->count=0;
        return OK;
}

/* 若栈S为空栈,则返回TRUE,否则返回FALSE */
Status StackEmpty(LinkStack S)
{ 
        if (S.count==0)
                return TRUE;
        else
                return FALSE;
}

/* 返回S的元素个数,即栈的长度 */
int StackLength(LinkStack S)
{ 
        return S.count;
}

/* 若栈不空,则用e返回S的栈顶元素,并返回OK;否则返回ERROR */
Status GetTop(LinkStack S,SElemType *e)
{
        if (S.top==NULL)
                return ERROR;
        else
                *e=S.top->data;
        return OK;
}

/* 插入元素e为新的栈顶元素 */
Status Push(LinkStack *S,SElemType e)
{
        LinkStackPtr s=(LinkStackPtr)malloc(sizeof(StackNode)); 
        s->data=e; 
        s->next=S->top;	/* 把当前的栈顶元素赋值给新结点的直接后继,见图中① */
        S->top=s;         /* 将新的结点s赋值给栈顶指针,见图中② */
        S->count++;
        return OK;
}

/* 若栈不空,则删除S的栈顶元素,用e返回其值,并返回OK;否则返回ERROR */
Status Pop(LinkStack *S,SElemType *e)
{ 
        LinkStackPtr p;
        if(StackEmpty(*S))
                return ERROR;
        *e=S->top->data;
        p=S->top;					/* 将栈顶结点赋值给p,见图中③ */
        S->top=S->top->next;    /* 使得栈顶指针下移一位,指向后一结点,见图中④ */
        free(p);                    /* 释放结点p */        
        S->count--;
        return OK;
}

Status StackTraverse(LinkStack S)
{
        LinkStackPtr p;
        p=S.top;
        while(p)
        {
                 visit(p->data);
                 p=p->next;
        }
        printf("\n");
        return OK;
}

int main()
{
        int j;
        LinkStack s;
        int e;
        if(InitStack(&s)==OK)
                for(j=1;j<=10;j++)
                        Push(&s,j);
        printf("栈中元素依次为:");
        StackTraverse(s);
        Pop(&s,&e);
        printf("弹出的栈顶元素 e=%d\n",e);
        printf("栈空否:%d(1:空 0:否)\n",StackEmpty(s));
        GetTop(s,&e);
        printf("栈顶元素 e=%d 栈的长度为%d\n",e,StackLength(s));
        ClearStack(&s);
        printf("清空栈后,栈空否:%d(1:空 0:否)\n",StackEmpty(s));
        return 0;
}

再来写一下Java实现的栈的链式存储结构,这因为也是线性表,所以怎么用Java实现的线性表可以去我之前的博客中查看。当然,Java中一般是用源码的,也可以看源码。

package text;

public class LinkStack {
    private Node header = new Node();   // 创建头节点
    class Node {
        String value;
        Node next;      
    }
    public boolean push(String value) {
        Node n = new Node();
        n.value = value;
        if(header.next == null) {
            header.next = n;
            return true;
        }
        n.next = header.next;
        header.next = n;
        return true;
    }
    public boolean pop() {
        if(header.next == null) {
            return false;
        }
        header.next = header.next.next;
        return true;
    }
    public String getTop() {
        if(header.next != null) {
            return header.next.value;
        }
        return null;
    }
    public boolean isEmpty() {
        return header.next == null ? true : false;
    }
    public static void main(String[] args) {
        LinkStack stack = new LinkStack();
        System.out.println(stack.isEmpty());  
        stack.push("aaa");                   // 进栈
        stack.push("bbb");
        stack.push("ccc");
        stack.push("ddd");
        System.out.println(stack.isEmpty());
        System.out.println(stack.getTop());  
        System.out.println(stack.pop());    
        System.out.println(stack.getTop());  
    }
}

上面的Java代码没有实现泛型化,也可以把它写成泛型,可以更加贴近Java源代码。

五、顺序存储结构与链式存储结构的对比

如果栈的使用过程中元素变化不可预料,有时很小,有时非常大,那么最好是用链栈,反之,如果它变化在可控范围内,建议使用顺序栈会好一点。

六、栈的作用

刚学栈的时候,可能会有疑问,为什么要设计栈这种有限制性的数据结构呢,直接用线性表不好吗?我也有过这样的疑问,但是当你从实际问题出发的时候,你就会发现它的用处了,比如网页的返回上一个网页,撤销等操作的时候,我们操作的对象都在栈顶,所以对于栈的其他元素我们不需要关注,因此,我们就不用费大力气去使用链表,造成麻烦还不利于程序运行。

七、栈的应用一(递归、斐波那切数列)

什么是递归,相信大家在学习C语言的时候都学过这个,就是函数自己本身调用自己本身。那么递归和栈有什么联系呢?

递归如何执行它的前进和退回阶段,这里不展开说明了,详细说明可以去看一下专门的讲解,这里着重讲一下栈与递归的联系。递归过程退回的顺序是它前进顺序的逆序。在退回 过程中可能要执行某些操作,包括恢复在前进过程中存储起来的某些数据。这种存储某些数据,并在后面又以存储的逆序恢复这些数据,以提供之后使用的需求,这很显然是符合栈的数据结构。可以将递归的前进过程理解为压栈,退回过程理解为处栈,现在的高级语言不需要编写者来管理这个栈,由系统来管理了,想了解的可以去看一下其他资料。

我们来写一下斐波那契数列吧

#include "stdio.h"

/* 斐波那契的递归函数 */
int Fbi(int i)  
{
	if( i < 2 )
		return i == 0 ? 0 : 1;  
    return Fbi(i - 1) + Fbi(i - 2);  /* 这里Fbi就是函数自己,等于在调用自己 */
}  

int main()
{
	int i;
	int a[40];  
	printf("迭代显示斐波那契数列:\n");
	a[0]=0;
	a[1]=1;
	printf("%d ",a[0]);  
	printf("%d ",a[1]);  
	for(i = 2;i < 40;i++)  
	{ 
		a[i] = a[i-1] + a[i-2];  
		printf("%d ",a[i]);  
	} 
	printf("\n");
	
	printf("递归显示斐波那契数列:\n");
	for(i = 0;i < 40;i++)  
		printf("%d ", Fbi(i));  
    return 0;
}

八、栈的应用二(四则运算表达式)

看到四则运算的时候,你有没有疑惑,这四则运算为什么要用栈来实现,不是直接输入进去就行了吗?这里我们要了解的四则运算表达式,是计算机计算四则运算的底层原理。计算机如何计算复杂的四则运算。我们现在编写代码的时候,这个四则运算是已经写好的,不需要我们再去用代码来实现四则运算了。
对于四则运算是有优先级和先后顺序的,而这个先后顺序,先进行的运算会再进行下一步运算,那我们在遍历一个四则运算表达式的时候,进行优先级最大的计算,例如,遇到前括号,我们就进栈,遇到后括号的时候就出栈,将括号中的结果放出来进行下一步计算,这个过程不就是栈的概念吗?

当然,还有后缀表达式,中缀表达式的概念,这个就不展开说了。。。我也不太会。

  • 6
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
递归实现表达式运算需要考虑一个算术表达式的各个元素以及运算符的优先级。下面是一个用C语言实现加减乘除模取模的递归解法的示例代码: ```c #include <stdio.h> int calculate(char *exp, int start); int parseNumber(char *exp, int *start) { int num = 0; while (exp[*start] >= '0' && exp[*start] <= '9') { num = num * 10 + (exp[*start] - '0'); (*start)++; } return num; } int calculate(char *exp, int start) { int num1 = parseNumber(exp, &start); while (exp[start] != '\0' && exp[start] != ')') { char op = exp[start++]; int num2 = parseNumber(exp, &start); switch (op) { case '+': num1 += num2; break; case '-': num1 -= num2; break; case '*': num1 *= num2; break; case '/': num1 /= num2; break; case '%': num1 %= num2; break; default: printf("Invalid operator\n"); return -1; } } return num1; } int main() { char expression[100]; printf("请输入要计算的表达式:"); scanf("%s", expression); int result = calculate(expression, 0); printf("计算结果为:%d\n", result); return 0; } ``` 以上代码中,函数`parseNumber`用于解析一个数值。函数`calculate`用于递归计算表达式,包括优先级高的乘除取模和优先级低的加减。在主函数中,用户输入一个表达式,然后调用`calculate`函数进行计算,并输出结果。 这个递归实现表达式运算的方法可以解析加减乘除取模运算,并且支持嵌套括号。但需要注意的是,这个实现有一些限制,例如不支持负数和浮点数的运算。如果需要更复杂的功能,可能需要更复杂的实现。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值