栈—线性结构
从数据结构上看,栈和队列也是线性表,其特别之处在于他们的基本操作是线性表操作的子集,即他们是操作受限的线性表
栈:打个简单的比方:空栈就是一个空的水杯,如果你要往里面加水,只能从杯口加入(从栈顶加入),如果你要倒水也只能从杯口倒出(从栈顶删除),那么杯子的杯底就是栈的栈底
对应的来看,栈的插入和删除操作只能在表尾进行。
栈的特点:先进后出(想想水杯)
栈又可以分为顺序栈和链栈
顺序栈
顺序栈是指利用顺序存储的栈(同时设指针top指向栈顶元素在栈中的位置,以base指针指示栈底元素在顺序表中的位置)
所以当base和top相等时,表示为空栈
(1)base为栈底指针,初始化完成后,栈底指针base始终指向栈底的位置,若base的值为NULL时,则表明栈结构不存在。top为栈顶指针,其初始值指向栈底。
每当插入新的栈元素时,指针top增1,删除栈顶元素时,指针top减1。因此,栈空时,top和base相等,都指向栈底,栈非空时,top始终指向栈顶元素的上一个位置。
操作:
(1)初始化
顺序栈的初始化操作就是为顺序栈动态分配一个预定大小的数组空间。
a.为顺序栈动态分配一个最大容量为MAXSIZE的数组空间,使base指向这段空间的基地址,即栈底
b.栈顶指针top初始为base,表示栈为空
c.stacksize置为栈的最大容量MAXSIZE
(2)入栈
入栈操作是指在栈顶插入一个新的元素
a.判断栈是否已满,若满则返回false
b.将新的元素压入栈顶,栈顶指针加1
(3)出栈
将栈顶元素删除
a.判断栈是否为空,如果为空,返回false
b.栈顶指针减1,栈顶元素出栈
(4)取栈顶元素
当栈为非空时,此操作返回当前栈顶的元素的值,栈顶指针保持不变
链栈
顺序栈的操作受到容量的限制,所以我们要学习链栈
链栈是用链式存储结构实现的栈
tips:由于栈的主要操作是在栈顶上插入和删除,所以以链表的头部作为栈顶是最方便的,并且不需要像单链表那样附加一个头节点
操作:
(1)初始化
初始化就是构造一个空栈,因为没必要设头节点,所以直接将栈顶指针置为空即可。
(2)入栈
不需要判断栈是否满,只需要为入栈元素动态分配一个节点空间
a.为入栈元素e分配空间,用指针p指向
b.将新节点数据域置e
c.将新节点插入栈顶
d.修改栈顶指针为p
(3)出栈
需要注意的是,出栈后需要释放栈顶元素的内存空间
a.判断栈是否为空,是则返回false
b.将栈顶元素赋给e
c.临时保存栈顶元素的空间以便释放
d.修改栈顶指针,指向新的栈顶元素
e.释放原栈顶元素的空间
(4)取栈顶元素
返回栈顶元素的值,栈顶指针保持不变
栈与递归
栈有一个重要的应用是在程序设计语言中实现递归
顺序栈
#include<iostream>
using namespace std;
#define ElemType int
#define Maxsize 100
//栈的数据结构
typedef struct stack
{
ElemType *base; //栈底指针
ElemType *top; //栈顶指针
int stacksize; //栈可用的最大容量
}Stack;
//初始化函数
void initStack(Stack &s)
{
s.base = new ElemType[Maxsize];
if (s.base == NULL)
{
cout << "内存分配失败!" << endl;
return;
}
s.top = s.base;
s.stacksize = Maxsize;
}
//入栈
void pushStack(Stack &s,ElemType a)
{
//判断栈是否已满
if (s.top - s.base == s.stacksize)
{
cout << "栈已满!" << endl;
return;
}
*(s.top) = a;
s.top++;
}
void PopStack(Stack &s)
{
//判断栈是否已满
if (s.top - s.base == s.stacksize)
{
cout << "栈已满!" << endl;
exit(0);
}
//栈空
if (s.top == s.base)
{
cout << "栈空!" << endl;
exit(0);
}
s.top--;
}
void GetTopElem(Stack s,ElemType &e)
{
//栈空
if (s.top == s.base)
{
cout << "栈空!" << endl;