棧章節預習問答

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.棧與遞歸關係
我們都知道遞歸分爲前進階段和回退階段(前進階段:一層一層的調用函數的階段;回退階段:滿足退出條件得到結果,一層一層返回運算的階段)。遞歸過程回退順序是前進順序的逆序。在退回過程中,可能要執行某些動作,包括恢復在前行過程能中存儲起來的某些數據。 
這種存儲某些數據,並在後面又以存儲的逆序恢復這些數據,以提供之後使用的需求,顯然很符合棧的數據結構,因此,編譯器使用棧實現遞歸就沒什麼好驚訝的了。



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章