3.1栈
3.1.1栈的定义及基本运算
栈是限制在表的一端进行插入和删除的线性表。允许插入、删除的这一端称为栈顶,另一个固定端称为栈底。当表中没有元素时称为空栈。
栈又称为后进先出的线性表,简称LIFO表。
栈的基本操作
(1)栈初始化:Init_Stack(s)
操作结果是构造了一个空栈。
(2)判栈空:Empty_Stack(s)
操作结果是若s为空栈则返回1,否则返回0。
(3)入栈:Push_Stack(s,x)
操作结果是在栈s的顶部插入一个新元素x,x成为新的栈顶元素。
(4)出栈:Pop_Stack(s)
在栈s存在且非空的情况下,操作结果是将栈s的顶部元素从栈中删除。
(5)读栈顶元素:Top_Stack(s)
在栈s存在且非空情况下,操作结果是读取栈顶元素,栈不变化。
3.1.2栈的存储实现和运算实现
1.顺序栈
栈中的数据元素用一个预设的足够长度的一维数组来实现:
datatype data[MAXSIZE]
栈底位置可以设置在数组的任一个端点,而栈顶是随着插入和删除而变化的,用一个int top来作为栈顶的指针,指明当前栈顶的位置,将data和top封装在一个结构中,顺序栈的类型描述如下:
#define MAXSIZE 1024
typedef struct
{datatype data[MAXSIZE];
int top;
}SeqStack;
定义一个指向顺序栈的指针: SeqStack *s;
通常0下标端设为栈底,这样空栈时栈顶指针top=-1;入栈时,栈顶指针+1,即s->top++;出栈时,栈顶指针减1,即s->top–。
(1)置空栈
首先建立栈空间,然后初始化栈顶指针。
SeqStack *Init_SeqStack(){
SeqStack *s;
s=new SeqStack;//申请空间
if(!s)
{
cout<<"空间不足"<<endl;
return NULL;//未申请到足够大的空间,返回空指针
}
else
{
s->top=-1;//初始化栈顶指针
return s;//申请到栈空间,返回栈空间地址
}
}
(2)判空栈
int Empty_SeqStack(SeqStack *s){
if(s->top==-1)
return 1;//栈顶指针指向栈底,空栈
else
return 0;
}
(3)入栈
int Push_SeqStack(SeqStack *s,DataType x){
if(s->top==MAXSIZE-1)
return 0;//栈满不能入栈,返回错误代码0
else{
s->top++;//栈顶指针向上移动
s->data[s->top]=x;//将x置入新的栈顶
return 1;//入栈成功,返回成功代码1
}
}
(4)出栈
int Pop_SeqStack(SeqStack *s,DataType *x){
if(Empty_SeqStack(s))
return 0;//栈空不能出栈,返回错误代码0
else{
*x=s->s->data[s->top];//保存栈顶元素值
s->top--;//栈顶指针向下移动
return 1;//返回成功代码1
}
}
(5)取栈顶元素
DataType Top_SeqStack(SeqStack*s){
if(Empty_SeqStack(S))
return 0;//栈空没有栈元素,返回错误代码0
else
return s->data[s->top];//返回栈顶元素值
}
需要说明的几点:
1.对于顺序栈,入栈时,首先判栈是否已满,栈满时,不能入栈;否则出现空间溢出,引起错误,这种现象称为上溢。
栈满的条件为:s->top==MAXSIZE-1
2.出栈和读栈顶元素操作,先判栈是否为空,为空时不能操作,否则产生错误。通常栈空时常作为一种控制转移的条件。
2.链栈
通常链栈用单链表表示,因此其结点结构与单链表的结构相同:
typedef struct node
{
datatype data;
struct node*next;
}StackNode,*LinkStack;
说明top为栈顶指针:LinkStack top;
因为栈中的主要运算是在栈顶插入、删除,显然在链表的头部做栈顶是最方便的,而且没有必要像单链表那样为了运算方便附加一个头结点。
(1)置空栈
LinkStack Init_LinkStack(){
return NULL;//返回栈顶(空指针)
}
(2)判栈空
int Empty_LinkStack(LinkStack top){
if(top==NULL)
return 1;//栈顶指针为空,空栈
else
return 0;
}
(3)入栈
LinkStack Push_LinkStack(LinkStack top,DataType x){
StackNode *s;
s=new StackNode;//申请新的栈顶结点空间
s->data=x;//将元素值置入结点数据域
s->next=top;//原栈顶结点作为新结点后继
top=s;//将新节点置为栈顶
return top;
}
(4)出栈
LinkStack Pop_LinkStack(LinkStack top,DataType *x){
StackNode *p;
if(top==NULL)
return NULL;//栈空不能出栈,返回空指针
else
{
*x=top->data;//保存栈顶元素值
p=top;
top=top->next;//置新的栈顶指针
delete p;//释放原栈顶元素节点空间
return top;//出栈成功,返回新的栈顶指针
}
}
3.2队列
3.2.1队列的定义及基本运算
在实际问题中经常使用一种”先进先出“的数据结构:即插入在表一端进行,删除在表另一端进行,这种数据结构称为队或队列,把允许插入的一端叫队尾(rear),把允许删除的一段叫队头(front)。
队列也是一种运算受限制的线性表,又叫先进先出表。
在队列上进行的基本操作有:
(1)队列初始化:Init_Queue(q)
操作结果是构造一个空队。
(2)入队操作:In_Queue(q,x)
在队q存在的情况下,操作结果是对已存在的队列q,插入一个元素x到队尾。
(3)出队操作:Out_Queue(q,x)
队q存在且非空的情况下,操作结果是删除队首元素,并返回其值。
(4)读队头元素:Front_Queue(q,x)
队q存在且非空的情况下,操作结果是读出队头元素,并返回其值。
(5)判队空操作:Empty_Queue(q)
队q存在时,操作结果是若q为空则返回1,否则返回0.
3.2.2队列的存储实现及运算实现
1.顺序队:顺序存储的队称为顺序队
因为队的队头和队尾是活动的,因此,除了队列的数据区外还要有队头、队尾两个指针。
顺序队的类型定义如下:
define MAXSIZE ...//根据实际需要设置队列的最大容量
typedef struct
{
datatype data[MAXSIZE];/*队员的存储空间*/
int rear,front;/*队头队尾指针*/
}SeQueue;
定义一个指向队的指针变量:
SeQueue *sq;
相关说明:
·申请一个顺序队的存储空间:
sq=new SeQueue;
·队列的数据区为:sq->data[0]---sq->data[MAXSIZE-1]
·队头指针:sq->front
·队尾指针:sq->rear
·设队头指针指向队头元素前面一个位置,队尾指针指向队尾元素(这样的设置是为了某些运算的方便,并不是唯一的方法)。
·置空队则为:sq->front=sq->rear=-1;
·在不考虑溢出的情况下,入队操作队尾指针加1,指向新位置后,元素入队。操作如下:
sq->rear++;
sq->data[sq->rear]=x;
·在不考虑队空的情况下,出队操作队头指针加1,表明队头元素出队。操作如下:
sq->front++;
x=sq->data[sq->front];/*原队头元素送x中*/
·队中元素的个数:m=(sq->rear)-(sq->front);
·队满时:m=MAXSIZE; 队空时:m=0;
但是,随着入队出队的进行,会使整个队列整体向后移动,就出现了这样现象:队尾指针已经移到了最后,再有元素入队就会出现溢出,而事实上此时队中并未真的“满员”,这种现象为“假溢出” 。
解决假溢出的方法之一是将队列的数据区data[0… MAXSIZE-1]看成头尾相接的循环结构,头尾指针的关系不变,将其称为“循环队列”。
因为是头尾相接的循环结构,入队时的队尾指针加1操作修改为:
sq->rear=(sq->rear+1)%MAXSIZE;
出队时的队头指针加1操作修改为:
sq->front=(sq->front+1)%MAXSIZE;
所以在队满的情况下有front=rear。但是在队空的情况下也有front==rear。
那么我们怎么解决这个问题呢?
方法之一就是附设一个存储队中元素个数的变量如num,当num==0时队空,当num ==MAXSIZE时为队满。
另一种方法是少用一个元素空间,,这种情况下队满的条件是(rear+1)%MAXSIZE==front
循环队列的类型定义如下(采用前面的方法一区分队空和队满)
typedef struct
{ datatype data[MAXSIZE]; /*数据的存储区*/
int front,rear; /*队头队尾指针*/
int num; /*队中元素的个数*/
}c_SeQueue; /*循环队*/
⑴ 置空队
C_SeQueue *Init_SeQueue(){
C_SeQueue q;
q=new C_SeQueue;//申请循环队列存储空间
q->front=q->rear=MAXSIZE-1;//队首、队尾指针置初值
q->num=0;//队列数据元素个数置初值
return q;
}
⑵ 入队
int In_SeQueue(C_SeQueue *q,DataType x){
if(q->num==MAXSIZE)
{
cout<<"队满"<<endl;
return 0;//队满不能入队,返回错误代码0
}
else
{
q->rear=(q->rear+1)%MAXSIZE;//队尾指针后移
q->data[q->rear]=x;//将新数据元素插入队尾
q->num++;//队列数据元素个数增1
return 1;//入队完成,返回成功代码1
}
}
⑶ 出队
int Out_SeQueue(C_SeQueue *q,DataType *x){
if(q->num==0)
{
cout<<"队空"<<endl;
return 0;//队空不能出队,返回错误代码0
}
else
{
q->front=(q->front+1)%MAXSIZE;//队首指针后移
*x=q->data[q->front];//读出队首元素保存
q->num--;//队列数据元素个数减1
return 1;//出对完成,返回成功代码1
}
}
⑷ 判队空
int Empty_SeQueue(C_SeQueue *q)
{
if(q->num==0)
return 1;//队列中数据元素个数为0,队空
else
return 0;
}
2:链队:链式存储的队称为链队
和链栈类似,用单链表来实现链队,根据队的FIFO原则,为了操作上的方便,我们分别需要一个头指针和尾指针。
头指针front和尾指针rear是两个独立的指针变量,从结构性上考虑,通常将二者封装在一个结构中。
链队的描述如下:
typedef struct node
{ datatype data;
struct node *next;
}QNode; /*链队结点的类型*/
typedef struct
{
QNnode *front, *rear;
}LQueue; /*将头尾指针封装在一起的链队*/
定义一个指向链队的指针:
LQueue *q;
(1)创建一个带头结点的空队
LQueue *Init_LQueue()
{
LQueue *q;
QNode *p;
q=new LQueue;//申请头尾指针结点
p=new QNode;//申请链队首结点
p->next=NULL;
q->front=q->rear=p;//首尾指针均指向头结点
return q;
}
(2)入队
void In_Queue(LQueue *q,DataType x){
QNode *p;
p=new QNode;//申请新结点
p->data=x;
p->next=NULL;
q->rear->next=p;//将新结点插入队尾
q->rear=p;//队尾指针指向新的结点
}
(3) 判队空
int Empty_LQueue(LQueue *q)
{
if(q->front==q->rear)
return 1;//队首队尾指向同一结点(头结点),队空
else
return 0;
}
(4) 出队
int Out_LQueue(LQueue *q,DataType *x)
{
QNode *p;
if(Empty_LQueue(q)){
cout<<"队空"<<endl;
return 0;
}
else{
p=q->front->next;//p指向队首结点
q->front->next=p->next;//队首指针指向新的队首结点
*x=p->data;//取出原队首数据元素值保存
delete p;//释放原队首结点空间
if(q->front->next==NULL)
q->rear=q->front;//只有一个元素时,出队后队空,修改队尾指针
return 1;
}
}