1、棧的定義:
棧(stack)限定僅在表位進行插入和刪除操作的線性表。其特殊性就在於該限制性。棧底固定,在棧頂進行入棧和出棧。
2、棧的順序存儲
構建棧結構如下:包含一個數組data[]和一個棧頂指示標top。數組用來存儲數據;top用來標識棧頂位置。
typedef struct
{
SElemType data[MAXSIZE];
int top; //用於棧頂指針
}SqStack;
/************************************************************************/
/* 順序棧 */
/************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <io.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; //stack元素類型
//定義順序棧結構
typedef struct
{
SElemType data[MAXSIZE];
int top; //用於棧頂指針
}SqStack;
Status visitStack(SElemType c)
{
printf("%d",c);
return OK;
}
//構造一個空棧S
Status InitStack(SqStack *S)
{
S->top=-1;
return OK;
}
//把一個已有棧S清空
Status ClearStack(SqStack *S)
{
S->top=-1;
return OK;
}
//判斷棧S是否爲空棧。是返回TRUE,否返回FALSE
Status isEmptyStack(SqStack S)
{
if (S.top==-1)
return TRUE;
else
return FALSE;
}
//計算棧S中元素的個數
int StackLength(SqStack S)
{
return S.top+1; //因爲元素是從下標0開始存放
}
//若棧不爲空,取棧頂元素e,並返回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)
{
printf("棧滿\n");
return ERROR;
}
else
{
S->top++;
S->data[S->top]=e;
return OK;
}
}
//若棧不爲空,刪除棧頂元素,並用e返回其值,返回OK;否則,返回ERROR
Status Pop(SqStack *S, SElemType *e)
{
if (S->top == -1)
return ERROR;
else
{
*e=S->data[S->top];
S->top--;
return OK;
}
}
//從棧底到棧頂顯示元素
Status StackTraverse(SqStack S)
{
int i=0;
while(i<=S.top)
{
visitStack(S.data[i]);
i++;
}
printf("\n");
return OK;
}
int main()
{
SqStack s;
int e;
if (InitStack(&s)==OK)
{
for (int j=1;j<=10;j++)
{
Push(&s,j);
}
}
printf("棧中元素依次爲:");
StackTraverse(s);
Pop(&s,&e);
printf("彈出棧頂元素e=%d\n",e);
printf("棧頂元素出棧之後,棧中元素依次爲:");
StackTraverse(s);
}
3、兩棧共享空間
原因是:單個棧處理的時候,數組固定,移動和刪除、數組大小等都受限,浪費空間。因此提出一種共享方式。
兩個棧對應的兩個棧頂,分別處於數組的兩端,向中間靠攏。只要top1和top2不見面,那麼棧中就有空間可供使用。
結構如下:
//定義兩個棧共享空間的結構體
typedef struct
{
SElemType data[MAXSIZE];
int top1; //棧1的棧頂指針
int top2; //棧2的棧頂指針
}SqDoubleStack;
/************************************************************************/
/* 兩棧共享空間結構 */
/************************************************************************/
//定義兩個棧共享空間的結構體
typedef struct
{
SElemType data[MAXSIZE];
int top1; //棧1的棧頂指針
int top2; //棧2的棧頂指針
}SqDoubleStack;
//初始化共享棧
Status InitStack(SqDoubleStack *S)
{
S->top1=-1;
S->top2=MAXSIZE; //數據存放在0~MAXSIZE-1之間由兩端向中間
return OK;
}
//把共享棧置空
Status ClearStack(SqDoubleStack *S)
{
S->top1=-1;
S->top2=MAXSIZE;
return OK;
}
//判斷共享棧是否爲空棧
Status isEmptyStack(SqDoubleStack S)
{
if (S.top1==-1 && S.top2==MAXSIZE)
return TRUE;
else
return FALSE;
}
//返回S中元素個數:要分別判斷棧1和棧2
int StackLength(SqDoubleStack S)
{
int len1=S.top1-(-1);
int len2=MAXSIZE - S.top2;
return len1+len2;
}
//插入e爲新棧頂元素
Status Push(SqDoubleStack *S, SElemType e, int stackNumber)
{
if (S->top1==S->top2)
{
printf("棧滿\n");
return ERROR;
}
if (stackNumber==1) //給棧1插入
{
S->data[++S->top1] = e;
}
if (stackNumber==2)//給棧2插入
{
S->data[--S->top2] =e;
}
return OK;
}
//遍歷共享棧
Status StackTraverse(SqDoubleStack S)
{
int i;
i=0;
while(i<=S.top1)
{
visitStack(S.data[i++]);
}
i=S.top2;
while(i<MAXSIZE)
{
visitStack(S.data[i++]);
}
printf("\n");
return OK;
}
int main()
{
printf("--------------------------------");
SqDoubleStack ss;
int ee;
int i;
if (InitStack(&ss)==OK)
{
for (i=0;i<=5;i++)
{
Push(&ss,i,1);
}
for( i=MAXSIZE;i>=MAXSIZE-3;i--)
{
Push(&ss,i,2);
}
}
int len=StackLength(ss);
printf("len=%d\n",len);
StackTraverse(ss);
getchar();
}
4、棧的鏈式存儲結構(LinkStack)
棧的鏈式存儲結構簡稱爲鏈棧。
相比於線性表的鏈式存儲結構,鏈棧①不需要頭結點;②讓top和頭指針合二爲一;③插入和刪除元素只能在頭指針處進行。
定義鏈表結構如下:
//定義鏈棧結構
struct StackNode
{
SElemType data;
struct StackNode *next;
};
typedef struct StackNode StackNode;
typedef struct StackNode* LinkStackPtr; //鏈棧指針
typedef struct
{
LinkStackPtr top;
int count;
}LinkStack;
/************************************************************************/
/* 3、棧的鏈式存儲結構——鏈棧 */
/************************************************************************/
//定義鏈棧結構
struct StackNode
{
SElemType data;
struct StackNode *next;
};
typedef struct StackNode StackNode;
typedef struct StackNode* LinkStackPtr; //鏈棧指針
typedef struct
{
LinkStackPtr top;
int count;
}LinkStack;
//構造一個空的鏈棧S
Status InitStack(LinkStack *S)
{
S->top=(LinkStackPtr)malloc(sizeof(StackNode));
if (S->top==NULL)
return ERROR;
S->top=NULL;
S->count=0;
return OK;
}
//把S置爲空的鏈棧,要一個一個釋放數據
Status ClearStack(LinkStack *S)
{
LinkStackPtr p,q;
p=S->top;
while (p!=NULL)
{
q=p;
p=p->next;
free(q);
}
S->count =0;
return OK;
}
//判斷,若棧爲空棧,則返回TRUE,否則返回FALSE
Status isStackEmpty(LinkStack S) //因爲還存放數據個數,因此可直接通過數據量【判斷
{
if (S.count==0)
return TRUE;
else
return FALSE;
}
//返回鏈棧中的元素個數
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 m= (LinkStackPtr)malloc(sizeof(StackNode));
m->data=e;
m->next=S->top;
S->top=m;
S->count++;
return OK;
}
//若棧不爲空,刪除棧頂元素
Status Pop(LinkStack *S,SElemType *e)
{
LinkStackPtr p;
if (isStackEmpty(*S))
return ERROR;
*e=S->top->data;
p=S->top;
S->top=S->top->next;
S->count--;
free(p); //注意:鏈表在添加的時候要分配新的存儲空間;同理,刪除的時候要釋放空間
return OK;
}
Status visitLinkStack(SElemType c)
{
printf("%d",c);
return OK;
}
//遍歷鏈棧
Status StackTraverse(LinkStack S)
{
LinkStackPtr p;
p=S.top;
while(p)
{
printf("%d ",p->data);
// visitLinkStack(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);
}
5、棧的應用
首先,說一下棧的作用:這裏,引用大話數據結構這本書中舉的例子,非常有道理。
問題:有數組和鏈表直接實現所要求的功能就好,爲什麼還要引入棧這種數據結構??
回答:類比到生活中。人有兩隻腳走路,那麼爲什麼還要乘火車汽車和飛機出行?!爲了節省時間和精力。同理,棧的引入是爲了簡化程序設計中的問題。若僅使用數組等,就要分散精力去考慮數組的下標增減等細節問題,反而掩蓋問題的本質。
接下來,討論棧的應用:
1、應用1——遞歸。
定義:將一個直接調用自己或通過一系列的調用語句間接地調用自己的函數,成爲遞歸函數。
迭代VS遞歸:一般結束的條件,迭代使用的是循環結構,遞歸使用選擇結構。
棧和遞歸:遞歸的退回的順序是它前行順序的逆序。在退回過程中,可能又要執行某些操作,包括恢復在前行過程中存儲的某些數據。這種處理思路,非常適合棧這種數據結構。因此用棧去實現遞歸。
即:①前行階段,對於每一層遞歸,函數的局部變量、參數值、以及返回地址等都被壓入棧中;②退回階段,位於棧頂的局部變量、參數值和返回地址等被彈出,用於返回調用層次中執行代碼的其餘部分,恢復調用狀態。
2、應用2——四則運算表達式求值
中綴運算符與前綴、後綴之間的轉換。
①中綴轉後綴:從前向後遍歷,數字入棧後直接出棧,操作符經判斷再出棧。操作符判斷其優先級,若是最低優先級,則先將棧中所有符號出棧,再讓該符號入棧。
②中綴轉前綴:從後向前遍歷,借用兩個棧,一個存數字,一個存符號,符號遇到括號就輸出壓入數字棧;數字入棧結束,將所有的符號從符號棧中出棧到數字棧。最後數字棧出棧結果即爲前綴表達式。
具體可參考博客:
https://blog.csdn.net/Annfan123/article/details/52088320