栈章节预习问答

1.两栈的共享空间是如何实现的

  如果我们有两个相同类型的栈,我们为他们各自开辟了数组空间,极有可能第一个栈已经满了,再进栈就溢出了,而另一个栈还有很多存储空间空闲。这时,我们完全可以用一个数组两存储两个栈。      
我们的做法如下图,数组有两个端点,两个栈有两个栈底,让一个栈的栈底为数组的始端,即下标为0处,另一个栈为数组的末端,即下标为数组长度n-1处。这样,两个栈如果增加元素,就是两端点向中间延伸。    
 其实关键思路是:他们是在数组的两端,向中间靠拢。top1和top2是栈1和栈2的栈顶指针,可以想象,只要他们两不见面,两个栈就可以一直使用。     
从这里也就可以分析出来,栈1为空时,就是top1等于-1时;而当top2等于n时,即是栈2为空时,那么什么时候栈满呢?     想想极端的情况,若栈2是空栈,栈1的top1等于n-1时,就是栈1满了。反之,当栈1为空栈时,top2等于0时,为栈2满。但更多的情况,其实就是刚才说的,两个栈见面之时,也就是两个指针之间相差1时,即top1+1==top2为栈满。     两栈共享空间的结构的代码如下:
typedefstruct
{    
ElemType data[MAXSIZE];
inttop1;//栈1栈顶指针inttop2;//栈2栈顶指针
}SqDoubleStack; 
     对于两栈共享空间的push方法,我们除了要插入元素值参数外,还需要有一个判断是栈1还是栈2的栈号参数stackNumber。插入元素的代码如下:
Status Push(SqDoubleStack *s , ElemType e , int stackNumber)
 2 {
 3     if(s->top1+1 == s->top2)       //栈已满,不能再push新元素了
 4         return ERROR;
 5     if(stackNumber == 1)           //栈1有元素进栈
 6         s->data[++s->top1] = e;    //若栈1则先top+1后给数组元素赋值
 7     else if(stackNumber == 2)      //栈2有元素进栈
 8         s->data[--s->top2] = e;    //若栈2则先top2-1后给数组元素赋值
 9     return OK;

2.栈的链式存储结构
栈的链式存储结构,简称链栈。
  由于栈只是栈顶在做插入和删除操作,所以栈顶应该放在单链表的头部。另外,都有了栈顶在头部了,单链表中的头结点也就失去了意义,通常对于链栈来说,是不需要头结点的。


  对于链栈来说,基本不存在栈满的情况,除非内存已经没有使用空间了。
  对于空栈来说,链表原来的定义是头指针指向空,那么链栈的空其实就是top=NULL。

typedef struct StackNode {
    SElemType data;
    Struct StackNode *next;
}StackNode;


typedef struct StackNode *LinkStackPtr;


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

3.为什么我们要使用栈,栈的作用有哪些?
一个函数设计里面,有2个问题:
1.是参数传递的问题。传递参数的目的,是为了代码可以重用,让一种方法可以应用到更多的场合,而不需要为N种情况写N套类似的代码。那用什么方法来做参数的传递,可以选择:
     a.为了速度快,使用cpu的寄存器传递参数。这会碰到一个问题,cpu寄存器的数量是有限的,当函数内再想调用子函数的时候,再使用原有的cpu寄存器就会冲突了。想利用寄存器传参,就必须在调用子函数前吧寄存器存储起来,然后当函数退出的时候再恢复。
     b.利用某些ram的区域来传递参数。这和上面a的情况几乎一样,当函数嵌套调用的时候,还是会出现冲突,依然面临要把原本数据保存到其他地方,再调用嵌套函数。并且保存到什么地方,也面临困难,无论临时存储到哪里,都会有上面传递参数一样的困境。
2.函数里面必然要使用到局部变量,而不能总是用全局变量。则局部变量存储到哪里合适,即不能让函数嵌套的时候有冲突,又要注重效率。
以上问题的解决办法,都可以利用栈的结构体来解决,寄存器传参的冲突,可以把寄存器的值临时压入栈里面,非寄存器传参也可以压入到栈里面,局部变量的使用也可以利用栈里面的内存空间,只需要移动下栈指针,腾出局部变量占用的空间。最后利用栈指针的偏移来完成存取。于是函数的这些参数和变量的存储演变成记住一个栈指针的地址,每次函数被调用的时候,都配套一个栈指针地址,即使循环嵌套调用函数,只要对应函数栈指针是不同的,也不会出现冲突。利用栈,当函数不断调用的时候,不断的有参数局部变量入栈,栈里面会形成一个函数栈帧的结构,一个栈帧结构归属于一次函数的调用。栈的空间也是有限的,如果不限制的使用,就会出现典型的栈溢出的问题。有了栈帧的框架在,我们在分析问题的时候,如果能获取到当时的栈的内容,则有机会调查当时可能出现的问题。

4.栈的四则运算表达方式
思想:我们平时输入的四则运算表达式,例如:9*(3-1)+2,属于中缀表达式。我们需要将它转换成后缀表达式:
9 3 1 - * 2 +的形式求值。其中需要两个栈:数字栈和运算符栈。


过程:
逐个读取中缀表达式(char型):9*(3-1)+2
1.如果是数字则压入数字栈(如果是大于一位的数字则需要写个函数转换成int型)
2.如果是'('则压入运算符栈中
3.如果是'+'或者'-',判断一下运算符的栈顶元素,如果是'*','/','+','-'则出栈,调用出栈函数(利用数字栈和运算符栈算出中间结果),然后将该运算符压入运算符栈中
4.如果是'*'或者'/',判断一下运算符的栈顶元素,如果是'*'或者'/',则出栈,调用出栈函数,然后将该运算符压入运算符栈中
5.如果是'(',则直接调用出栈函数,直到将'('出栈为止
6.遍历完中缀表达式后,如果此时运算符栈不为空,则调用出栈函数逐个出栈
7.最后的结果是数字栈的栈顶元素

5.后缀表示法是如何实现的
转化为后缀:从左到右遍历中缀表达式,遇到操作数,输出,遇到操作符,当前操作符的优先级大于栈顶操作符优先级,进栈,否则,弹出栈顶优先级大于等于当前操作符的操作符,当前操作符进栈。


转化为前缀:从右到左遍历中缀表达式,遇到操作数,输出,遇到操作符,当前操作符的优先级大于等于栈顶操作符优先级,进栈,否则,弹出栈顶优先级大于当前操作符的操作符,当前操作符进栈。

6.如何用C语言实现进栈出栈操作
#include<stdio.h>  
#define MAXN 10  
int push(int *stack,int maxn,int *toppt,int x)  
//调用时不加&是因为push(&s,Maxn,&top,i)中s是数组元素,其名字代表数组首地址  
{  
    if(*toppt>=maxn)  
        return 1;//栈满  
    stack[*toppt]=x;//第一步操作,保证元素在0位置处  
    (*toppt)++;  
      
    return 0;  
}  
int pop(int *stack,int *toppt,int *cp)  
{  
    if(*toppt==0)  
        return 1;  
    (*toppt)--;  
    *cp=stack[*toppt];//出栈元素的临时保存位置,结合main函数里出栈元素为i,按地址传值  
        return 0;  
}  
void OutputStack(int *stack,int toppt)  
{  
int i;  
for(i=toppt-1;i>=0;i--)  
{  
    printf("%d",stack[i]);  
    printf("\n");  
}  
}  
  
void main()  
{  
    int s[MAXN],i;  
    int top=0;  
    int op;  
    while(1)  
    {  
        printf("请选择操作,1:进栈  2:出栈  0:退出");  
        fflush(stdin);  
        scanf("%d",&op);  
        switch(op)  
        {  
        case 0:return ;  
        case 1:  
            {  
            printf("请输入进栈元素:");  
            scanf("%d",&i);  
            if(push(s,MAXN,&top,i)==0)  
            {  
                printf("进栈成功,栈内元素:\n");  
                OutputStack(s,top);  
            }  
            else  
                printf("栈满\n");  
            }  
                break;  
        case 2:  
            if(pop(s,&top,&i)==0)  
            {  
                printf("出栈元素为:[%d],栈内元素为:\n",i);  
                OutputStack(s,top);  
            }  
            else  
                printf("栈空\n");  
            break;  
        }  
    }  
}  

7.栈与递归关系
我们都知道递归分为前进阶段和回退阶段(前进阶段:一层一层的调用函数的阶段;回退阶段:满足退出条件得到结果,一层一层返回运算的阶段)。递归过程回退顺序是前进顺序的逆序。在退回过程中,可能要执行某些动作,包括恢复在前行过程能中存储起来的某些数据。 
这种存储某些数据,并在后面又以存储的逆序恢复这些数据,以提供之后使用的需求,显然很符合栈的数据结构,因此,编译器使用栈实现递归就没什么好惊讶的了。



猜你喜欢

转载自blog.csdn.net/cb673335723/article/details/78432268