栈和队列
一、栈
1.栈的相关定义
(1)栈是限定在仅在表的一端进行插入和删除操作的线性表。
允许插入和删除的一端称为栈顶,另一端为栈底,不含任何数据元素的栈成为空栈。
(2)插入元素叫入栈,删除元素叫出栈。
(3)栈中元素的特性——后进先出。
2.栈的顺序存储结构(顺序栈)
数组下标为0的一端表示栈底,top表示栈顶元素在数组中的位置。
设存储栈数组长度StackSize,栈空时top=-1, 栈满时top=StackSize-1。(注意数组下标从0开始)
3.顺序栈的实现
(1)声明
const int StackSize = 10;
template <typename DataType>
class StackSize
{
public:
SeqStack(); //构造函数,初始化一个空栈
~SeqStack();
void Push(DataType x) //入栈操作,将元素x入栈
DataType Pop(); //出栈操作,将栈顶元素谈弹出
int Empty(); //判断栈是否为空
private:
DataType data[StackSize]; //存放栈元素的数组
int top; //栈顶元素在数组的下标(即定义栈的指针top)
};
(2)构造函数
初始化一个空的顺序栈,需将栈顶指针top置为-1
template <typename DataType>
SeqStack::SeqStack()
{
top = -1;
}
(3)析构函数
template <typename DataType>
SeqStack::~SeqStack()
{
}
(4)入栈操作
在栈中插入元素x,只需将栈顶位置top+1,然后在top的位置填入元素x
template <typename DataType>
void SeqStack<DataType>::Push(DataType x)
{
if (top == StackSize-1) throw"上溢";
data[++top] = x; //top先自增移动top再取x的值
}
注:++top是先自增再取值,top++是先取值再自增。
(5)出栈操作
取出栈顶元素,top-1;
template <typename DataType>
DataType SeqStack<DataType>::Pop()
{
DataType x;
if (top==-1) throw"下溢";
x = data[top--]; //先把元素的值赋值给x,top再-1
return x;
}
(6)取栈顶元素
template <typename DataType>
DataType SeqStack<DataType>::GetTop()
{
if(top==-1)
throw"下溢异常";
else
return data[top];
}
(7)判空操作
template <typename DataType>
int SeqStack<DataType>::Empty()
{
if(top==-1)
return 1;
else
return 0;
}
4.链栈的相关定义
栈的链接存储结构称为链栈,常用单链表表示。
注:有指针指向的一端作为栈顶。
5.链栈的实现
(1)初始化
template <typename DataType>
class StackSize
{
public:
LinkStack(); //初始化空链栈
~LinkStack();
void Push(DataType x) //入栈,将元素x入栈
DataType Pop(); //出栈,将栈顶元素取出
int Empty();
private:
Node<DataType> *top; //栈顶指针即链栈的头指针
};
(2)初始化
由于链栈不带头结点,初始化一个空链栈只需将栈顶指针top置空。
template <typename DataType>
LinkStack::LinkStack()
{
top = new Node; //生成头结点
top->next = nullptr; //将头结点的指针域置空
}
(3)析构——链栈销毁
template <typename DataType>
LinkStack::~LinkStack()
{
Node *q = nullptr;
while(top!=nullptr) //释放链栈的每一个结点的存储空间
{
q = top; //暂存被释放结点
top = top->next; //top指向被释放的下一个结点
delete q;
}
}
(4)入栈操作
只需关注栈顶
template <typename DataType>
void LinkStack<DataType>::Push(DataType x)
{
Node<DataType> *s = nullptr;
s = new Node<DataType>;
s->data = x; //数据域
s->next = top; //将结点s插到栈顶
}
(5)出栈操作
只需处理栈顶
template <typename DataType>
DataType LinkStack<DataType>::Pop()
{
Node<DataType> *p = nullptr;
DataType x;
if (top==nullptr) throw"下溢";
x = top->data; p = top; //暂存栈顶元素
top = top->next; //将栈顶元素摘链
delete p; //删除暂存元素
return x;
}
(6)取栈顶元素
只需返回栈顶指针所指结点的数据域,不用修改栈顶指针
template <typename DataType>
DataType LinkStack<DataType>::GetTop()
{
if(top==nullptr)
throw"下溢异常";
else
return top->data;
}
(7)判空操作
判断top是否为空
template <typename DataType>
int LinkStack::Empty()
{
if(top==nullptr)
throw"下溢异常";
else
return 0;
}
二、队列
1.队列相关定义
(1)队列只允许在一端进行插入,在另一端进行删除操作的线性表。
(2)允许插入的一端称为队尾,允许删除的一端称为队头。
(3)队列中的元素除具有线性关系外,还具有先进先出的特性。
2.顺序队列的存储结构
(1)队列的顺序结构称为顺序队列。
(2)队列元素必须存储在前n个单元的入队出队:
队头元素放在数组下标为0的一端,入队相当于追加不需移动元素。
(3)去掉前n个单元条件的入队出队:
设置队头、队尾两个位置变量front指向队头元素的前一个位置,rear指向队尾元素的位置。
队头队尾都能活动,入队时rear+1,出队时front+1。
3.循环队列的存储结构
(1)假溢出:随队列的插入删除,队列向数组的高端移动,产生单向移动性,当元素插入到数组下标最大位置后,数组空间用尽,低端有空间也不能用,这种现象叫假溢出。
(2)解决假溢出的方法:将存储队列的数组看成首位相接的循环结构,允许队列从数组下标最大的位置延续到最小的位置。(通过取模操作实现)
设存储队列的数组长度为QueueSize,则操作语句为rear=(rear+1)%QueueSize,这种首尾相接的循环结构叫循环队列。(一开始rear在QueueSize-1的位置,再+1取余使rear到0的位置)
(3)循环队列的判空、判满
队空条件:front = rear
队满条件:(rear+1)%QueueSize = front牺牲1个数组元素空间使队尾与队头差1。
4.循环队列的实现
(1)声明
const int QueueSize = 100;
template<typename DataType> //定义模板类CirQueue
class CirQueue
{
public:
CirQueue(); //初始化空队列
~CirQueue();
void EnQueue(DataType x) //入队操作,将元素x入队
DataType DeQueue(); //出队操作,将队头元素出队
DataType GetHead(); //取队头元素,不删除
int Empty();
private:
DataType data[QueueSize]; //存放队头元素的数组
int front,rear; //队头队尾指针
};
(2)构造函数——循环队列初始化
初始化空的队列只需将队头front和队尾rear同时指向数组的某一位置,一般为数组高端即
rear = front = QueueSize-1
template <typename DataType>
CirQueue::CirQueue()
{
rear = front =QueueSize-1;
}
(3)析构函数
退出作用域时自动释放,函数为空
template <typename DataType>
CirQueue::~CirQueue()
{
}
(4)入队操作
将队尾位置rear在循环意义下+1,再将待插元素x插入队尾。
template <typename DataType>
void CirQueue<DataType>::EnQueue(DataType x)
{
if ((rear+1)%QueueSize==front) throw"上溢"; //队满
rear = (rear+1)%QueueSize; //队尾指针在循环意义下+1
data[rear] = x; //在队尾处插入元素x
}
(5)出队操作
将队头位置front在循环意义下+1,读取并返回队头元素。
template <typename DataType>
DataType CirQueue<DataType>::GetHead()
{
if (rear==front) throw"下溢"; //队空
front = (front+1)%QueueSize; //队头元素在循环意义下+1
return data[front]; //返回出队前队头的元素
}
(6)取队头元素
与出队类似,不用改变队头的位置
template <typename DataType>
DataType CirQueue<DataType>::GetHead()
{
if (rear==front) throw"下溢"; //队空
return data[(front+1)%QueueSize]; //注意不修改队头指针的位置
}
(7)判空操作
只需判断front 是否=rear
template <typename DataType>
{
if(rear==front)
throw"下溢";
else
return 0;
}
5.队列的链接存储
(1)队列的链接存储叫链队列,常用单链表表示。
(2)设置队头指针指向头结点,队尾指针指向终端结点。
6.链队列的实现
(1)声明
template <typename DataType>
class LinkQueue
{
public:
LinkQueue(); //初始化空的链队列
~LinkQueue();
void EnQueue(DataType x) //入队,将元素x入队
DataType DeQueue(); //出队,队头元素出队
DataType GetHead(); //取链队列队头元素
int Empty();
private:
Node<DataType>*front,*rear; //队头和队尾指针
};
(2)构造函数——链队列的初始化
申请头结点,让队头指针和队尾指针都指向头结点。
template <typename DataType>
LinkQueue<DataType>::LinkQueue()
{
Node<DataType>*s = nullptr;
s = new Node<DataType>; s->next = nullptr;
front = rear = s; //将队头指针队尾指针都指向头结点s
}
(3)析构函数——链队列的销毁
与单链表类似
template <typename DataType>
LinkQueue<DataType>::~LinkQueue()
{
Node<DataType>*p = front;
while(front!==nullptr)
{
front = front->next;
delete p;
p = front;
}
}
(4)入队操作
插入操作只在链表尾部进行,链队列带头结点,空链队列和非空链队列操作语句一致。
template <typename DataType>
void LinkQueue<DataType>::EnQueue(DataType x)
{
Node<DataType>*s = nullptr;
s = new Node<DataType>; //申请结点s
s->data = x; s->next = nullptr; //s的数据域为x,s的next为空
rear->next = s; rear = s; //将结点s插入到队尾
}
方法二
不带头结点的链队列入队操作,空链队列和非空链队列操作语句不同。
空链入队时front需要指向s
(5)出队操作
链队列的删除操作只考虑在链表头部进行,注意队列长度=1的特殊情况。
template <typename DataType>
DataType LinkQueue<DataType>::DeQueue()
{
DataType x;
Node<DataType>*p = nullptr;
if (rear==front) throw"下溢";
p = front->next; x = p->data; //暂存队头元素
front->next=p->next; //将队头元素所在结点摘链
if (p->next==nullptr) rear = front; //出队前队列长度为1
delete p;
return x;
}
(6)取队头元素
取队头元素只需返回第一个数据元素结点的数据域
front->next->data
template <typename DataType>
DataType LinkQueue::GetQueue()
{
if(front==rear)
throw"下溢异常";
else
return front->next->data;
}
(7)判空操作
判断front是否=rear
template <typename DataType>
int LinkQueue::Empty()
{
if(front==rear)
return 1; //链队列为空
else
return 0;
}
三、拓展提高
1.两栈共享空间
思想:利用顺序栈的单向延伸性,使用一个数组存储两个栈,一个栈位于数组的始端一个栈位于数组末端,两栈各自向中间延伸,避免了存储空间的浪费。
条件:两个栈空间需求有相反的需求,一个栈增长时一个栈缩短。
(1)top1和top2分别为栈1和栈2的栈顶,StackSize为数组大小。
(2)栈1的底位于数组下标为0的一端,栈2的底位于下标为StackSize-1的一端。
2.两栈共享空间的实现
(1)声明
设整型变量i只有1、2两个值,取i=1表示对栈1操作,取i=2表示对栈2进行操作
const int StackSize = 100;
template <typename DataType>
class BothStack
{
public:
BothStack(); //两栈分别初始化
~BothStack();
void Push(int i,DataType x) //入栈操作,将元素x压入栈i
DataType Pop(int i); //出栈操作,对栈i执行出栈操作
DataType GetTop(int i); //取栈i的栈元素
int Empty(int i); //判断栈i是否为空
private:
DataType data[StackSize]; //存放两个栈的数组
int top1,top2; //两个栈的栈顶指针,分别为各自栈顶元素的在数组的下标
};
(2)入栈操作
数组中没有空闲单元时栈满,此时栈1栈顶和栈2栈顶元素相邻。
即top1 = top2-1或top2 = top1+1
入栈1时top1+1,入栈2时top2-1
template <typename DataType>
void BothStack::Push(int i,DataType x)
{
if (top1==top2-1) throw"上溢"; //判断是否栈满
if (i==1) data[++top1] = x; //栈1插入
if (i==2) data[--top2] = x; //栈2插入
}
(3)出栈操作
top1 = -1时栈1为空,top2 = StackSize时栈2为空
template <typename DataType>
DataType BothStack::Pop(int i)
{
if (i==1)
{
if (top==-1)
throw"下溢";
return data[top1--];
}
if (i==2)
{
if (top2==StackSize)
throw"下溢";
return data[top2++]; //注意栈2删除元素,top2是+1
}
}
3.双端队列
允许队列在两端进行插入删除操作称为双端队列。
两端插入一端删除称为二进一出,两端删除一端插入称为一进二出。
双端队列可采用循环队列的存储方式,在队头入队时需先将新元素x插入到front处,队头front在循环意义下-1,在队尾出队时,先将rear处队尾元素暂存,再把rear在循环意义下-1。
4.双端队列的实现
没写
5.括号匹配问题
施工中。。。。
6.表达式求值问题
施工中。。。。