栈
栈的基本概念
栈(Stack)是只允许在一端进行插入或删除操作的线性表。首先栈是一种线性表,但限定这种线性表只能在某一端进行插入和删除操作,如图所示。
栈顶(Top)。线性表允许进行插入删除的那一端。
栈底(Bottom)。固定的,不允许进行插入和删除的另一端。
空栈:不含任何元素的空表
栈的基本操作
栈的顺序存储结构
1.顺序栈的实现
采用顺序存储的栈称为顺序栈,它利用一组地址连续的存储单元存放自栈底到栈顶的数据元素,同时附设一个指针(top)指示当前栈顶元素的位置。
栈的顺序存储类型可描述为:
#define MaxSize 50 //定义栈中元素的最大个数
template<typename T>
struct SqStack {
T data[MaxSize]; //存放栈中元素
int top; //栈顶指针
};
栈顶指针: s.top,初始时设置s.top=-1;栈顶元素:s.data [s.top]。
进栈操作:栈不满时,栈顶指针先加1,再送值到栈顶元素。
出栈操作:栈非空时,先取栈顶元素值,再将栈顶指针减1。
栈空条件:s.top==-1;
栈满条件:s.top==MaxSize-1;栈长:s.top+1。
由于顺序栈的入栈操作受数组上界的约束,当对栈的最大使用空间估计不足时,有可能发生栈上溢,此时应及时向用户报告消息,以便及时处理,避免出错。
2.顺序栈的基本运算
栈操作的示意图如图所示,图(a)是空栈,图©是A、B、C、D、E共5个元素依次入栈后的结果,图(d)是在图©之后E、D、C的相继出栈,此时栈中还有2个元素,或许最近出栈的元素C、D、E仍在原先的单元存储着,但top指针已经指向了新的栈顶,元素C、D、E已不在栈中,读者应通过该示意图深刻理解栈顶指针的作用。
下面是顺序栈上常用的基本运算的实现。
#include <iostream>
using namespace std;
#define MaxSize 8 //定义栈中元素的最大个数
template<class T>
class SqStack {
private:
T data[MaxSize];
int top;
public:
//缺省构造函数
SqStack() {
this->top = -1;
}
//初始化
void InitStack() {
this->top = -1;//初始化栈顶指针
}
//栈判空
bool StackEmpty() {
if (this->top == -1) //栈空
return true;
else //不空
return false;
}
//进栈
bool Push(T x) {
if (this->top == MaxSize - 1) //栈满,报错
return false;
this->data[++this->top] = x; //指针先加1,再入栈
return true;
}
//出栈
bool Pop(T &x) {
if (this->top == -1) //栈空,报错
return false;
x = this->data[this->top--]; //先出栈,指针再减1
return true;
}
//读取栈顶元素
bool GetTop(T &x) {
if (this->top == -1) //栈空,报错
return false;
x = this->data[this->top]; //x记录栈顶元素
return true;
}
//仅为读取栈顶元素,并没有出栈操作,因此原栈顶元素依然保留在栈中。
//查看栈中所有元素
void display() {
if (this->StackEmpty())
return;
cout << "all elements in SqStack:\t";
for (int i = 0; i <= this->top; i++)
cout << this->data[i] << "\t";
}
};
int main() {
//测试
SqStack<int> sqStack;
int x;
//进栈测试
for (int i = 0; i < 10; i++)
if (sqStack.Push(i))
cout << "push " << i << " ok!" << endl;
else
cout << "push " << i << " error! The SqStack is full!" << endl;
sqStack.display();
cout << endl;
//出栈测试
for (int i = 0; i < 10; i++)
if (sqStack.Pop(x))
cout << "pop " << x << " ok!" << endl;
else
cout << "pop error! The SqStack is empty!" << endl;
sqStack.display();
cout << endl;
return 0;
}
运行结果:
push 0 ok!
push 1 ok!
push 2 ok!
push 3 ok!
push 4 ok!
push 5 ok!
push 6 ok!
push 7 ok!
push 8 error! The SqStack is full!
push 9 error! The SqStack is full!
all elements in SqStack: 0 1 2 3 4 5 6 7
pop 7 ok!
pop 6 ok!
pop 5 ok!
pop 4 ok!
pop 3 ok!
pop 2 ok!
pop 1 ok!
pop 0 ok!
pop error! The SqStack is empty!
pop error! The SqStack is empty!
3.共享栈
利用栈底位置相对不变的特性,可让两个顺序栈共享一个一维数组空间,将两个栈的栈底分别设置在共享空间的两端,两个栈顶向共享空间的中间延伸,如图所示。
两个栈的栈顶指针都指向栈顶元素,top0=-1时О号栈为空,top1=Maxsize时1号栈为空;仅当两个栈顶指针相邻(top1-top0=1)时,判断为栈满。当0号栈进栈时top0先加1再赋值,1号栈进栈时top1先减1再赋值;出栈时则刚好相反。
共享栈是为了更有效地利用存储空间,两个栈的空间相互调节,只有在整个存储空间被占满时才发生上溢。其存取数据的时间复杂度均为O(1),所以对存取效率没有什么影响。
栈的链式存储结构
采用链式存储的栈称为链栈,链栈的优点是便于多个栈共享存储空间和提高其效率,且不存在栈满上溢的情况。通常采用单链表实现,并规定所有操作都是在单链表的表头进行的。这里规定链栈没有头结点,Lhead指向栈顶元素,如图所示。
栈的链式存储结构可描述为:
template<typename T>
struct LinkNode {
T data; //数据域
LinkNode *next; //指针域
};
采用链式存储,便于结点的插入与删除。链栈的操作与链表类似,入栈和出栈的操作都在链表的表头进行。需要注意的是,对于带头结点和不带头结点的链栈,具体的实现会有所不同。
下面是链栈上常用的基本运算的实现。
#include <iostream>
using namespace std;
template<typename T>
struct LinkNode {
T data; //数据域
LinkNode<T> *next; //指针域
};
template<class T>
class LinkStack {
private:
LinkNode<T> *Lhead;
public:
//缺省构造函数
LinkStack() {
this->Lhead = nullptr;
}
//初始化
void InitStack() {
this->Lhead = nullptr;//初始化栈顶指针
}
//栈判空
bool StackEmpty() {
if (this->Lhead == nullptr) //栈空
return true;
else //不空
return false;
}
//进栈
bool Push(T x) {
//新建一个结点
LinkNode<T> *p = new LinkNode<T>;
p->data = x;
//将结点插入到链栈的头部
p->next = this->Lhead;
this->Lhead = p;
return true;
}
//出栈
bool Pop(T &x) {
if (this->StackEmpty()) //栈空,报错
return false;
x = this->Lhead->data; //先出栈,再从链表中删除结点
LinkNode<T> *p = this->Lhead;
this->Lhead = this->Lhead->next;
delete p;
return true;
}
//读取栈顶元素
bool GetTop(T &x) {
if (this->StackEmpty()) //栈空,报错
return false;
x = this->Lhead->data; //x记录栈顶元素
return true;
}
//查看栈中所有元素
void display() {
if (this->StackEmpty()) {
cout << "The LinkStack is empty!";
return;
}
cout << "all elements in LinkStack:\t";
LinkNode<T> *p = this->Lhead;
while (p != nullptr) {
cout << p->data << "\t";
p = p->next;
}
}
};
int main() {
LinkStack<int> linkStack;
int x;
//进栈测试
for (int i = 0; i < 10; i++) {
linkStack.Push(i);
cout << "push " << i << " ok!" << endl;
}
linkStack.display();
cout << endl;
//读取栈顶元素测试
if (linkStack.GetTop(x))
cout << "top element of LinkStack:" << x << endl;
//出栈测试
for (int i = 0; i < 11; i++)
if (linkStack.Pop(x))
cout << "pop " << x << " ok!" << endl;
else
cout << "pop error! The LinkStack is empty!" << endl;
linkStack.display();
cout << endl;
return 0;
}
运行结果:
push 0 ok!
push 1 ok!
push 2 ok!
push 3 ok!
push 4 ok!
push 5 ok!
push 6 ok!
push 7 ok!
push 8 ok!
push 9 ok!
all elements in LinkStack: 9 8 7 6 5 4 3 2 1 0
top element of LinkStack:9
pop 9 ok!
pop 8 ok!
pop 7 ok!
pop 6 ok!
pop 5 ok!
pop 4 ok!
pop 3 ok!
pop 2 ok!
pop 1 ok!
pop 0 ok!
pop error! The LinkStack is empty!
The LinkStack is empty!