一、什么是栈
栈是限定仅在表头进行插入和删除操作的线性表。要搞清楚这个概念,首先要明白”栈“原来的意思,如此才能把握本质。”栈“者,存储货物或供旅客住宿的地方,可引申为仓库、中转站,所以引入到计算机领域里,就是指数据暂时存储的地方,所以才有进栈、出栈的说法。
二、两种常用的栈
1、顺序栈
使用连续的内存空间模拟栈的空间,一般使用数组来实现,数组索引为0即为栈底,其次再定义一个变量储存栈顶位置即可,这种栈实现起来比较简单容易操作,适合初学者。
2、链式栈
使用零散的内存空间模拟栈的空间,一般使用链表1来实现,链表尾部即为栈底,链表头部即为栈顶。
三、栈的实现
代码节选自网络
#include<stdio.h>
#include<stdlib.h>
#include<malloc.h>
//定义结点结构体
typedef struct Node
{
int data; //内容
struct Node * pNext; //指向下一结点的指针
} NODE, * PNODE; //NODE等价于struct Node, PNODE等价于struct Node *
//定义栈的结构体
typedef struct Stack
{
PNODE pTop; //栈顶结点
PNODE pBottom; //栈底结点
} STACK, * PSTACK; //STACK等价于struct Stack, PSTACK等价于struct Stack *
//函数声明
void initStack(PSTACK pStack); //对栈进行初始化的函数
void pushStack(PSTACK pStack, int val); //入栈的函数
bool popStack(PSTACK pStack, int * pVal);//出栈的函数,*pVal用来保存出栈的元素内容
void traverseStack(PSTACK pStack); //遍历栈的函数
bool isEmpty(PSTACK pStack); //判断栈是否为空的函数
void clearStack(PSTACK pStack); //清空栈的函数
int main(void)
{
STACK stack; //定义一个栈变量,STACK等价于struct Stack
int val; //用来保存出栈的内容
initStack(&stack); //调用栈的初始化函数
pushStack(&stack, 10); //调用入栈的函数
pushStack(&stack, 20);
pushStack(&stack, 30);
pushStack(&stack, 50);
traverseStack(&stack); //调用遍历栈的函数
//调用出栈的函数
if(popStack(&stack, &val))
printf("出栈成功,出栈的元素值为:%d\n", val);
else
printf(" 出栈失败!");
//调用清空栈的函数
clearStack(&stack);
traverseStack(&stack); //调用遍历栈的函数
system("pause");
return 0;
}
void initStack(PSTACK pStack)
{
//创建一个空结点,让pTop指向它
pStack->pTop = (PNODE)malloc(sizeof(NODE));
if(NULL != pStack->pTop)
{
//将pBottom也指向空节点
pStack->pBottom = pStack->pTop;
//清空空结点的指针域
pStack->pTop->pNext = NULL;
}
else //如果内存分配失败
{
printf("内存分配失败!程序退出!\n");
exit(-1);
}
return;
}
void pushStack(PSTACK pStack, int val)
{
//动态创建一个新结点
PNODE pNew = (PNODE)malloc(sizeof(NODE));
//设置新结点的数据域的值
pNew->data = val;
//将新结点的指针域指向之前建的空节点
pNew->pNext = pStack->pTop; //pStack->pTop不能换成pStack->pBottom
//pTop指向新的结点
pStack->pTop = pNew;
return;
}
bool popStack(PSTACK pStack, int * pVal)
{
if(isEmpty(pStack))
{
return false;
}
else
{
//先保存栈顶元素的地址,然后将pTop指向下一元素,最后释放之前栈顶元素的内存
PNODE rNode = pStack->pTop;
*pVal = rNode->data;
pStack->pTop = rNode->pNext;
free(rNode);
rNode = NULL;
return true;
}
}
void traverseStack(PSTACK pStack)
{
//将栈顶赋给一个临时结点,因为在遍历栈的时候不能销毁栈
PNODE pNode = pStack->pTop;
//循环遍历栈,直到栈底
while(pStack->pBottom != pNode )
{
printf("%d ", pNode->data);
pNode = pNode->pNext;
}
printf("\n");
return;
}
bool isEmpty(PSTACK pStack)
{
if(pStack->pTop == pStack->pBottom)
return true;
else
return false;
}
void clearStack(PSTACK pStack)
{ //栈为空,则退出该函数
if(isEmpty(pStack))
{
return;
}
else
{
//两个结点指针变量用来释放栈中元素的内存
PNODE p = pStack->pTop;
PNODE q = NULL;
//循环释放内存
while(p != pStack->pBottom)
{
q = p->pNext;
free(p);
p = q;
}
//将栈顶和栈底指向同一个指针域为空的结点
pStack->pTop = pStack->pBottom;
return;
}
}
四、栈的存储
1、栈的顺序存储结构
栈的顺序存储结构需要使用一个数组和一个整型变量来实现。利用数组来顺序存储栈中的所有元素,利用整型变量来存储栈顶元素的下标位置,可这个变量称为栈顶指针。
const int MaxSize = 50;
struct Stack
{
ElemType stack[MaxSize];
int top;
};
若要对存储栈的数组空间采用动态分配,则可定义如下:
struct Stack
{
ElemType *stack;
int top;
int MaxSize;
};
top的值为-1表示栈空。
2、栈的链式存储结构
栈的链式存储结构是通过由结点构成的单链表实现的,此时表头指针被称为栈顶指针,由栈顶指针指向的表头结点被称为栈顶结点,整个单链表被称为链栈。对链栈的插入和删除操作是在单链表的表头进行的。
当向一个链栈插入元素时,是把该元素插入到栈顶,即,使该元素结点的指针域指向原来的栈顶结点,而栈顶指针则修改为指向该元素结点,使该结点成为新的栈顶结点。
五、两栈的空间共享
当程序中同时使用两个栈时,可以将两个栈的栈底设在向量空间的两端,让两个栈各自向中间延伸。
当一个栈的元素较多,超过向量空间的一半时,只要另一个栈的元素不多,那么前者就可以占用后者的部分存储空间。只有当整个向量空间被两个栈占满(即两个栈顶相遇)时,才会发生上溢,因此两个栈共享一个长度为m的向量空间
六、栈的抽象数据类型
ADT 栈(stack)
Data
同线性表。元素具有相同的类型,相邻元素具有前驱和后堆关系。
Operation
InitStack ( *S ):初始化操作.建立一个空栈S。
DestroyStack ( *S ):若栈存在,則销毁它。
ClearStack (*S):将栈清空。
StackEmpty ( S ):若栈为空,返回true,否則返回 false。
GetTop (S,*e):若栈存在且非空,用e返回S的栈顶元素。
Push (*S,e):若栈S存在,插入新元素e到栈S中并成为栈頂元素。
Pop (*S,*e):删除栈S中栈顶元素,并用e返回其值。
StackLength (S):返回回栈S的元素个数。
endADT
七、栈的应用
裴波那契数列的计算
四则运算表达式
后缀表达式的定义
递归
中缀转后缀