数据结构|数据结构(三)——详解栈和队列及完整代码实现


文章目录

  • 一.栈
    • 1.1 栈的逻辑结构
    • 1.2 栈的顺序存储结构及实现
      • 1.2.1 顺序栈的实现——入栈
      • 1.2.2顺序栈的实现——出栈
      • 1.2.3 两栈共享空间
    • 1.3 栈的链接存储结构及实现
      • 1.3.1 链栈的实现——入栈
      • 1.3.2 链栈的实现——出栈
    • 1.4 顺序栈和链栈的比较
  • 二.队列
    • 2.1 队列的逻辑结构
    • 2.2 队列的顺序存储结构及实现
      • 2.2.1 循环队列
      • 2.2.2 循环队列的实现——入队
      • 2.2.3 循环队列的实现——出队
      • 2.2.4 循环队列的实现——读队头元素
    • 2.3 队列的链接存储结构及实现
      • 2.3.1 链队列的实现——构造函数
      • 2.3.2 链队列的实现——入队
      • 2.3.3 链队列的实现——出队
    • 2.4 循环队列和链队列的比较
  • 三 .栈和队列完整代码实现
    • 3.1 顺序栈类模板实现:
    • 3.2 链队列的代码实现:
    • 3.3 循环队列实现代码
    • 3.4 顺序栈,两栈共享,链栈的应用

两种特殊的线性表——栈和队列: 【数据结构|数据结构(三)——详解栈和队列及完整代码实现】
? 从数据结构角度看,栈和队列是操作受限的线性表,他们的逻辑结构相同。
? 从抽象数据类型角度看,栈和队列是两种重要的抽象数据类型。
一.栈 1.1 栈的逻辑结构 栈的相关定义:
1.栈:限定仅在表的一端进行插入和删除操作的线性表。
2.允许插入和删除的一端称为栈顶,另一端称为栈底。
3.? 空栈:不含任何数据元素的栈。
数据结构|数据结构(三)——详解栈和队列及完整代码实现
文章图片

如上图所示,栈的插入和删除是有一定条件的,即栈顶的元素先出栈,下面的元素才能接着出,如果栈顶的元素 a 3 a3 a3没有出栈,那么下面的元素 a 2 、 a 1 a2、a1 a2、a1是不可能出栈的,就比如往弹夹里面压子弹一样,最上面的一发子弹没有射出去,下面的子弹自然不可能出去了——这就是栈的操作特性:后进先出
1.2 栈的顺序存储结构及实现 顺序栈是指栈的顺序存储结构,那么如何改造数组来实现栈的顺序存储呢?
数据结构|数据结构(三)——详解栈和队列及完整代码实现
文章图片

如上图所示,我们先确定用数组的哪一端表示栈底,假设我们用a[0]端作为栈底,附设指针top指示栈顶元素在数组中的位置。
数据结构|数据结构(三)——详解栈和队列及完整代码实现
文章图片

如果a2进栈的,就让top++,指向a2对应的位置
数据结构|数据结构(三)——详解栈和队列及完整代码实现
文章图片

a3进栈,就让top++,指向a3对应的位置,同理,如果a3出栈了,就让top–,这样就用数组来实现栈的顺序存储,当栈空的适合,就让top=-1,而栈满的适合,则让top=数组的最大值-1.
1.2.1 顺序栈的实现——入栈
实现代码如下:
template voidseqStack ::Push ( DataTypex) { if (top == MAX_SIZE-1)throw“溢出”; top++; data[top] = x; }

如果栈没有满的话,就让top++,然年把x压栈到数组中即可,而这两步可以合并为一步:
数据结构|数据结构(三)——详解栈和队列及完整代码实现
文章图片

1.2.2顺序栈的实现——出栈
template DataTypeseqStack :: Pop ( ) { if (top == -1)throw“溢出”; x = data[top--]; returnx; }

原理很简单,再此处不多说啦
1.2.3 两栈共享空间
如果在一个程序中需要同时使用具有相同数据类型的两个栈,如何顺序存储这两个栈呢?
我们可以采用顺序栈单向延伸——使用一个数组来存储两个栈,即两栈共享空间:
使用一个数组来存储两个栈,让一个栈的栈底为该数组的始端,另一个栈的栈底为该数组的末端,两个栈从各自的端点向中间延伸。
数据结构|数据结构(三)——详解栈和队列及完整代码实现
文章图片

如上图所示,栈1的底固定在下标为0的一端;栈2的底固定在下标为StackSize-1的一端。
top1和top2分别为栈1和栈2的栈顶指针;Stack_Size为整个数组空间的大小(图中用S表示);
当top1== -1时,栈1为空
当top2= =Stack_Size时,栈2为空
而当top2= =top1+1时,栈满
1.3 栈的链接存储结构及实现 同理,我们应该如何改造链表实现栈的链接存储呢?
数据结构|数据结构(三)——详解栈和队列及完整代码实现
文章图片

如上图所示,我们将链头作为栈顶,方便操作;且链栈不需要附设头结点。
1.3.1 链栈的实现——入栈
数据结构|数据结构(三)——详解栈和队列及完整代码实现
文章图片

如上图所示,让入栈时,我们定义一个新的结点s,然后将x赋值给s的数据部分,然后让s的下一个结点指向top,再修改top指向新入栈的结点s即可。
实现代码如下:
template void LinkStack ::Push(DataType x) { s = new Node; s->data = https://www.it610.com/article/x; s->next = top; top = s; }

1.3.2 链栈的实现——出栈
数据结构|数据结构(三)——详解栈和队列及完整代码实现
文章图片

同理,出栈时,我们先存入x的值便于返回,然后让栈顶指针p指向结点top,修改top,让他指向他的下一个结点,删除结点p即可。
实现代码如下:
template DataType LinkStack ::Pop( ) { if (top == NULL) throw "下溢"; x = top->data; p = top; top = top->next; delete p; return x; }

1.4 顺序栈和链栈的比较 时间性能:相同,都是常数时间O(1)。
空间性能
? 顺序栈:有元素个数的限制和空间浪费的问题。
? 链栈:没有栈满的问题,只有当内存没有可用空间时才会出现栈满,但是每个元素都需要一个指针域,从而产生了结构性开销。
总之,当栈的使用过程中元素个数变化较大时,用链栈是适宜的,反之,应该采用顺序栈。
二.队列 2.1 队列的逻辑结构 队列的相关定义:
1.队列:只允许在一端进行插入操作,而另一端进行删除操作的线性表。
2. 允许插入(也称入队、进队)的一端称为队尾,允许删除(也称出队)的一端称为队头。
3. 空队列:不含任何数据元素的队列。
数据结构|数据结构(三)——详解栈和队列及完整代码实现
文章图片

如上图所示,队列的特性是先出先进,也就和我们排队是一个道理。
2.2 队列的顺序存储结构及实现 如何才能改造数组实现队列的顺序存储呢?
数据结构|数据结构(三)——详解栈和队列及完整代码实现
文章图片

如上图所示,我们设置队头、队尾两个指针,分别约定:让队头指针front指向队头元素的前一个位置,让队尾指针rear指向队尾元素。
数据结构|数据结构(三)——详解栈和队列及完整代码实现
文章图片

当a1、a2依次出队后,对头和队尾指针也跟着相应变化。
2.2.1 循环队列
假溢出:当元素被插入到数组中下标最大的位置上之后,队列的空间就用尽了,尽管此时数组的低端还有空闲空间,这种现象叫做假溢出。
数据结构|数据结构(三)——详解栈和队列及完整代码实现
文章图片

如上图所示,虽然0和1位置还空着,但由于rear指针已经到数组中下标最大的位置,便无法再插入了。
如何解决假溢出呢?
我们设置一个循环队列,将存储队列的数组头尾相接:
数据结构|数据结构(三)——详解栈和队列及完整代码实现
文章图片

这样就能让rear指向前面的元素位置了。
但循环队列并不存在物理的循环结构,用软件方法求模实现,比如上图:
我们求模:(4+1)mod 5=0即可让rear指向0位置了
当需要执行出队操作,出队完成后,front==rear时,就表示队空了:
数据结构|数据结构(三)——详解栈和队列及完整代码实现
文章图片

数据结构|数据结构(三)——详解栈和队列及完整代码实现
文章图片

而当front==rear时,也能表示队满:
数据结构|数据结构(三)——详解栈和队列及完整代码实现
文章图片
如何才能确定不同的队空、队满的判定条件?有三种方法:
方法一:附设一个存储队列中元素个数的变量num, 当num==0时队空,当num==QueueSize时为队满; 方法二:修改队满条件,浪费一个元素空间,队满时数组中只有一个空闲单元; 方法三:设置标志flag,当front==rear且flag==0时为队空, 当front==rear且flag==1时为队满。

数据结构|数据结构(三)——详解栈和队列及完整代码实现
文章图片

在此当(rear+1) mod QueueSize==front时为队满,当rear=front时队空
2.2.2 循环队列的实现——入队
数据结构|数据结构(三)——详解栈和队列及完整代码实现
文章图片

入队的适合,当(rear+1) % QueueSize与front相同时,就说明队满了,否则修改rear的指向,然后x赋值到rear对应的数组元素处即可。
实现代码如下:
template void CirQueue ::EnQueue(DataType x) { if ((rear+1) % QueueSize == front) throw "上溢"; rear =(rear+1) % QueueSize; data[rear] = x; }

2.2.3 循环队列的实现——出队
数据结构|数据结构(三)——详解栈和队列及完整代码实现
文章图片

当a3进行出队操作时,当rear == front,表示队列为空,否则修改front的指向,返回data值即可,因为front指向的是队头元素的前一个位置。
代码实现如下:
template DataType CirQueue ::DeQueue( ) { if (rear == front) throw "下溢"; front = (front + 1) % QueueSize; return data[front]; }

2.2.4 循环队列的实现——读队头元素
数据结构|数据结构(三)——详解栈和队列及完整代码实现
文章图片

实现代码如下:
template DataType CirQueue ::GetQueue( ) { if (rear == front) throw "下溢"; i = (front + 1) % QueueSize; return data[i]; }

2.3 队列的链接存储结构及实现 数据结构|数据结构(三)——详解栈和队列及完整代码实现
文章图片

如上图所示,队头指针即为链表的头指针
2.3.1 链队列的实现——构造函数
数据结构|数据结构(三)——详解栈和队列及完整代码实现
文章图片

构造一个空链表,让队头和队尾指针都指向该结点即可
代码实现如下:
template LinkQueue ::LinkQueue( ) { front = new Node; front->next = NULL; rear = front; }

2.3.2 链队列的实现——入队
数据结构|数据结构(三)——详解栈和队列及完整代码实现
文章图片

如上图所示,如果要插入结点s,那么让s的下一个结点为空,让rear的下一个结点指向s,再修改rear指向s即可
template void LinkQueue ::EnQueue(DataType x) { s = new Node; s->data = https://www.it610.com/article/x; s->next = NULL; rear->next = s; rear = s; }

2.3.3 链队列的实现——出队
数据结构|数据结构(三)——详解栈和队列及完整代码实现
文章图片

如上图所示,我们先让p结点为front的下一个结点,然后让front的下一个结点指向p的下一个结点,删除p结点即可
此处需要注意当队列中只有一个元素时,我们要让rear指向front,然后再删除p结点。
数据结构|数据结构(三)——详解栈和队列及完整代码实现
文章图片

代码实现如下:
template DataType LinkQueue ::DeQueue( ) { if (rear == front) throw "下溢"; p = front->next; x = p->data; front->next = p->next; if (p->next == NULL)rear = front; delete p; return x; }

2.4 循环队列和链队列的比较 时间性能:
? 循环队列和链队列的基本操作都需要常数时间O (1)。
空间性能:
? 循环队列:必须预先确定一个固定的长度, 所以有存储元素个数的限制和空间浪费的问题。 ? 链队列:没有队列满的问题,只有当内存没有可用空间时才会出现队列满, 但是每个元素都需要一个指针域,从而产生了结构性开销。

三 .栈和队列完整代码实现 3.1 顺序栈类模板实现:
#include//cout,cin #include"process.h"//exit() #include"stdio.h"//EOF,NULL //顺序栈类定义 template class SqStack { private: T*base; //栈底指针 int top; //栈顶 int stacksize; //栈容量 public: SqStack(int m); //构建函数 ~SqStack(){delete [] base; top=-1; stacksize=0; }//析构函数 void Push(T x); //入栈 T Pop(); //出栈 T GetTop(); //获取栈顶元素 int StackEmpty(); //测栈空 void ClearStack(); //清空栈 void StackTop(); //返回栈顶指针 void StackTranverse(); //显示栈中元素 }; //顺序栈类实现 template SqStack::SqStack(int m) { base=new T[m]; if(base==NULL) { cout<<"栈创建失败,退出!"< void SqStack::Push(T x) { if(top==stacksize-1) throw "栈满,无法入栈"; top++; base[top]=x; //cout<<"top:"< T SqStack::Pop() { T x; if(top==-1) throw "栈空,不能出栈"; x=base[top--]; return x; }template T SqStack::GetTop() { if(top==-1) throw "栈空,栈顶无元素"; return base[top]; }template int SqStack::StackEmpty() { if(top==-1) return 1; else return 0; }template void SqStack::ClearStack() { top=-1; }template void SqStack::StackTop() {//返回栈顶指针 cout<<"栈顶top= "< void SqStack::StackTranverse() { int i=top; while(i>=0) cout< s(10); //建立容量为20、元素类型为整型的空栈 system("cls"); //执行系统命令cls,清屏 int choice; do {//显示主菜单 cout<<"1-元素入栈\n"; cout<<"2-元素出栈\n"; cout<<"3-取栈顶元素\n"; cout<<"4-置栈空\n"; cout<<"5-测栈空\n"; cout<<"6-显示栈元素\n"; cout<<"7-显示栈顶指针\n"; cout<<"8-退出\n"; cout<<"Enter choice:"; cin>>choice; switch(choice) { case 1://入栈 cout<<"请输入插入元素的值:"; cin>>e; // cout<

3.2 链队列的代码实现:
# include using namespace std; template struct Node{ DataType data; Node *next; }; template //在队列中同样有头结点; class LinkQueue{ public: LinkQueue(); ~LinkQueue(){}; void EnQueue(DataType x); DataType DeQueue(); DataType GetQueue(); int Empty(){ if(front==rear) return 1; else return 0; } private: Node *front,*rear; }; template LinkQueue ::LinkQueue() { Node *s; s=new Node; s->next=NULL; front=rear=s; }template //根据队列的性质,入队只能从队尾; void LinkQueue::EnQueue(DataType x) { Node *s; s=new Node; s->next=NULL; s->data=https://www.it610.com/article/x; rear->next=s; //在队的最后一个元素后面插入s rear=s; //将rear后移;指向队尾元素; }template //根据队列的性质,出队只能从队头; DataType LinkQueue::DeQueue() { if(front==rear) throw "下溢"; Node *p; p=front->next; int x=p->data; front->next=p->next; if(p->next==NULL) rear=front; //判断出队前长度是否为1; 若为1 则使头尾指针相同; delete p; return x; }template DataType LinkQueue::GetQueue() { return front->next->data; //注意这里不改变front的位置; }int main() { LinkQueue a; int b[6]={2,45,12,7,8,3}; for(int i=0; i<5; i++) { a.EnQueue(b[i]); } cout<

3.3 循环队列实现代码
#include//cout,cin #include"process.h"//exit() #include"stdio.h"//EOF,NULL #include "iostream.h" //循环队列类的定义 template class CirQueue { private: DataType *base; // 存储空间基址 int front; // 队头指针 int rear; // 队尾指针 int queuesize; // 队容量 public: CirQueue(int m); //构造空队列 ~CirQueue(); // 析构函数,释放链队各结点的存储空间 void EnQueue(DataType x); // 元素x入队 DataType DeQueue(); // 队顶元素出队 DataType GetHead(); // 取队头元素 DataType GetLast(); //取队尾元素 int QueueEmpty(); // 判队空 int QueueFull(); //判队满 void ClearQueue(); //清空队 void Pointer(); //返回队头、队尾位置 void QueueTranverse(); //遍历队,输出队列元素 }; //循环队列类的实现 template CirQueue::CirQueue(int m)//构造函数 {//创建一空队 base=new DataType[m]; if(base==NULL) { cout<<"队创建失败,退出!"< CirQueue::~CirQueue()//析构函数 {//释放队列所占存储空间 delete [] base; rear=0; front=0; queuesize=0; }template void CirQueue::EnQueue(DataType x) {//元素入队 if((rear+1)%queuesize==front) throw "上溢,无法入队"; base[rear]=x; rear=(rear+1)%queuesize; }template DataType CirQueue::DeQueue() { DataType x; if(front==rear) throw "下溢,不能出队"; x=base[front]; front=(front+1)%queuesize; return x; }template DataType CirQueue::GetHead() { DataType x; if(front==rear) throw "队空,队顶无元素"; x=base[front]; return x; }template DataType CirQueue::GetLast() { DataType x; if(front==rear) throw "队空,队顶无元素"; x=base[rear-1]; return x; }template int CirQueue::QueueEmpty() { if(front==rear) return 1; else return 0; }template int CirQueue::QueueFull() { if((rear+1)%queuesize==front) return 1; else return 0; }template void CirQueue::ClearQueue() { front=rear=0; }template void CirQueue::Pointer() { cout<<"队头front= "<>choice; switch(choice) { case 1://入队 cout<<"请输入插入元素的值:"; cin>>e; // cout<

实验截图如下:
数据结构|数据结构(三)——详解栈和队列及完整代码实现
文章图片

3.4 顺序栈,两栈共享,链栈的应用
#include using namespace std; //栈 const int StackSize=100; template class SeqStack { public: SeqStack(){top=-1; } ~SeqStack(){} void Push(DataType x); DataType Pop(); DataType GetTop(){if(top!=-1) return data[top]; } int Empty() { if(top==-1)return 1; else return 0; } //private: DataType data[StackSize]; int top; }; template//顺序栈入栈算法 void SeqStack::Push(DataType x) { if(top==StackSize-1)throw"上溢"; data[++top]=x; } template//顺序栈出栈算法 DataType SeqStack::Pop() { if(top==-1)throw"下溢"; DataType x=data[top--]; return x; }//两栈共享空间 template class BothStack { public: BothStack(){top1=-1; top2=StackSize; } ~BothStack(){} void Push(int i,DataType); DataType Pop(int i); DataType GetTop(int i); int Empty(int i); private: DataType data[StackSize]; int top1,top2; }; template//两栈共享空间入栈算法 void BothStack::Push(int i,DataType x) { if(top1==top2-1)throw"上溢"; if(i==1)data[++top1]=x; if(i==2)data[--top2]=x; } template//出栈算法 DataType BothStack::Pop(int i) { if(i==1){ if(top1==-1)throw"下溢"; return data[top1--]; } if(i==2){ if(top2==StackSize)throw"下溢"; return data[top2++]; } } template//取栈顶元素 DataType BothStack::GetTop(int i) { if(i==1){ if(top1!=-1) return data[top1]; } if(i==2){ if(top2!=-1) return data[top2]; } } template//判空操作 int BothStack::Empty(int i) { if(i==1) { if(top1==-1) { cout<<"栈1空"< struct Node { DataType data; Node *next; }; template class LinkStack { public: LinkStack(){top=NULL; } ~LinkStack(){}; void Push(DataType x); DataType Pop(); DataType GetTop(){if(top!=NULL)return top->data; } int Empty(); private: Node *top; }; template//链栈入栈算法 void LinkStack::Push(DataType x) { Node *s; s=new Node; s->data=https://www.it610.com/article/x; s->next=top; top=s; } template//链栈出栈算法 DataType LinkStack::Pop() { Node *p; p=new Node; if(top==NULL)throw"下溢"; DataType x=top->data; p=top; top=top->next; delete p; return x; } template//判空操作 int LinkStack::Empty() { if(top==NULL) { cout<<"栈为空"< i1; //创建顺序栈i1 cout<<"top="< i2; //创建两栈共享的栈i2 i2.Push(1,'c'); i2.Push(1,'b'); i2.Push(1,'a'); //向1中压入c,b,a i2.Push(2,'C'); i2.Push(2,'B'); i2.Push(2,'A'); //向2中压入C,B,A cout<<"栈1的栈顶元素是:"< i3; //创建链栈i3 i3.Push(3); i3.Push(2); i3.Push(1); //依次压入3,2,1 cout<<"i3的栈顶元素是:"<

数据结构|数据结构(三)——详解栈和队列及完整代码实现
文章图片

    推荐阅读