线性表是我们学习数据结构所接触的第一类数据结构,它是最基本,最简单且最常用的数据结构。仔细想一下,在学习数据结构前,你应该有一门编程语言基础。在你以往的编程中,‘数组’这个东西一定没少用吧,没错,数组就是线性表的一种,最简单最普通的线性表。以及链表,动态的分配内存是不是自由度感觉很高呢?其实想一下你在学程序语言时已经将线性表这类数据结构的基础打好了哦
接下来我们开始介绍我们两种常用的特殊线性表之一 — 栈
定义先摆上:限定仅在表尾进行插入和删除操作的线性表
光看定义肯定是不能很好的理解,我们来举个生活中常用的栈的例子:
手枪的子弹夹,我们往里塞子弹的时候,最先塞进去的子弹被塞到了最底部,第一个射出的子弹应该是你最后塞进去的,这就是简单的栈的例子。
从中我们看出栈的特性:后进先出 最先进栈的元素最后出栈。栈底就是弹夹底部,栈顶就是弹夹顶部射出子弹那端
现在我们理解了栈的性质,那么我们要如何用计算机语言实现呢?
我们已经知道如何使用数组这样一个线性表,栈本质上就是对普通线性表加以限制,数组可以在头端增删,也可在尾端增删,那么我们只需修改增删的操作,把它限制成只能在一端增删,不就成了?然后再将下标0的位置设为栈底,数组尾部设为栈顶,我们就基本完成的栈这一数据结构的基本形态。
那么接下来我们将栈这一数据结构用顺序结构,也就是数组实现
#include <iostream>
using namespace std;
/*栈的抽象数据类型*/
template<class T>
class Stack
{
private:
int top;
int bottom;
int MAXSIZE;
T *elements;
public:
/*构造器将栈顶与栈底置0*/
Stack()
{
this->MAXSIZE = 100;
elements = new T[MAXSIZE];
this->top = 0;
this->bottom = 0;
}
~Stack()
{
Destory();
}
/*入栈操作*/
void Push(T e)
{
if (this->IsFull())
{
cout << "栈满" << endl;
return;
}
this->elements[top] = e;
this->top++;
}
/*出栈操作,用e返回出栈元素*/
void Pop(T &e)
{
if (this->IsEmpty())
{
cout << "空栈" << endl;
return;
}
e = this->elements[top - 1];
this->top--;
}
/*判断是否为空栈*/
bool IsEmpty()
{
if (this->top == this->bottom)
return true;
else return false;
}
/*判断是否栈满*/
bool IsFull()
{
if (top == MAXSIZE)
return true;
else return false;
}
/*返回栈元素个数*/
int Length()
{
return this->top;
}
/*获取栈顶元素*/
T GetTop()
{
return this->elements[top - 1];
}
/*将栈清空*/
void Clear()
{
this->top = this->bottom;
}
/*销毁栈*/
void Destory()
{
delete[]elements;
this->top = this->bottom;
}
/*从底向上遍历*/
void Traverse()
{
for (int i = this->bottom; i < this->top; i++)
{
cout << elements[i] << " ";
}
}
};
代码很简单,我就一次性贴出来了。我们使用两个指针top,bottom用来指向栈顶与栈底,最初的初始化操作将栈顶与栈顶置为下标0的位置。top始终指向栈顶元素的下一个位置,bottom指向0作为栈底
入栈过程入下图所示:
接下来是出栈过程:
出栈过程只是下移栈顶指针,数据仍然存在但是无法访问,接下来的入栈会将其再次覆盖
栈空与栈满的两种状态:
top==bottom==0时栈空,top==MaxSize时top指到头了也就是栈满
怎么样,是不是很简单呢?这完全就是对数组的基本操作。相信有程设计语言基础的你可以轻松理解。
使用数组来存储栈是很方便的,因为限制了增删的位置在末尾,所以不用担心修改的时间效率。但是数组的不可避免的弊端就是必须事先确定栈的大小,如果栈满就需要编程的方式扩充大小。这时你可能想到使用链表,没错,链表的动态增删是一个很好的选择,但是在介绍链栈之前我要先介绍另一个提高空间利用率的手段:两栈共享空间
假设现在需要使用两个相同类型的栈,如果各自开辟一个数组,会出现一种情况,一个栈满了,但另一个栈还有剩余空闲,所以我们为什么不想办法利用空闲的空间呢? 因此我们可以通过某种手段,让这两个栈共享一个空间。
如图,我们的思路就是将两个栈顶指针放在数组两端,入栈时不断向中间移动,我们可以很容易的知道当top1==top2时栈满
接下来是代码的实现,与单栈的差别就是少了bottom指针,多了个top指针,多了个枚举类型标记选择要操作哪个栈
#include<iostream>
using namespace std;
/*栈名标识*/
enum StackNum
{
first, second
};
template<class T>
class ShStack
{
private:
int top1;
int top2;
int MAXSIZE;
T *elements;
public:
/*初始化*/
ShStack()
{
this->top1 = 0;
this->MAXSIZE = 100;
this->top2 = this->MAXSIZE - 1;
this->elements = new T[this->MAXSIZE];
}
/*进栈*/
void Push(T e, StackNum num)
{
if (this->IsFull())
{
cout << "栈满" << endl;
return;
}
if (num == first)
{
this->elements[this->top1] = e;
this->top1++;
}
else
{
this->elements[this->top2] = e;
this->top2--;
}
}
/*出栈*/
void Pop(T &e, StackNum num)
{
if (this->IsEmpty())
{
cout << "空栈" << endl;
return;
}
if (num == first)
{
e = this->elements[this->top1 - 1];
this->top1--;
}
else
{
e = this->elements[this->top2 + 1];
this->top2++;
}
}
/*获取栈顶元素*/
T GetTop(StackNum num)
{
if (num == first)
return this->elements[this->top1 - 1];
else if (num == second)
return this->elements[this->top2 + 1];
}
/*获取栈长*/
int Length(StackNum num)
{
if (num == first)
return this->top1;
else if (num == second)
return this->MAXSIZE - this->top2;
}
/*判断是否栈满*/
bool IsFull()
{
if (this->top1 == this->top2)
return true;
else return false;
}
/*判断是否为空栈*/
bool IsEmpty()
{
if (this->top1 == 0 && this->top2 == this->MAXSIZE - 1)
return true;
else return false;
}
/*从底向上遍历*/
void Traverse(StackNum num)
{
if (num == first)
{
for (int i = 0; i < this->top1; i++)
cout << this->elements[i] << " ";
}
else if (num == second)
{
for (int i = this->MAXSIZE - 1; i > this->top2; i--)
cout << this->elements[i] << " ";
}
}
/*销毁栈*/
void Destory()
{
delete[]elements;
top1 = 0;
top2 = MAXSIZE - 1;
}
};
使用这样的手段,需要两个栈是相同类型,且具有空间需求相反的关系,一方增长,一方就缩短,这样共享栈才有意义,否则两栈同时增长,对于这种数据结构很容易快速满栈,这样不能很好的处理问题。因此在数据结构的设计时要根据具体情况与需求使用相应的处理策略,这样才能更有效的利用空间