栈和队列
(1)从数据结构角度看,栈和队列是操作受限的线性表,他们的逻辑结构相同。
(2)从抽象数据类型角度看,栈和队列是两种重要的抽象数据类型。
一、 栈
(1)栈的逻辑结构:
1、栈的定义
栈:限定仅在表的一端进行插入和删除操作的线性表。
允许插入和删除的一端称为栈顶,另一端称为栈底。
空栈:不含任何数据元素的栈。
插入:入栈、进栈、压栈
删除:出栈、弹栈
栈的操作特性:后进先出
2、 栈的抽象数据类型定义
ADT Stack
Data
栈中元素具有相同类型及后进先出特性,
相邻元素具有前驱和后继关系
Operation
InitStack
前置条件:栈不存在
输入:无
功能:栈的初始化
输出:无
后置条件:构造一个空栈
DestroyStack
前置条件:栈已存在
输入:无
功能:销毁栈
输出:无
后置条件:释放栈所占用的存储空间
Push
前置条件:栈已存在
输入:元素值x
功能:入栈操作,在栈顶插入一个元素x
输出:如果插入不成功,抛出异常
后置条件:如果插入成功,栈顶增加了一个元素
Pop
前置条件:栈已存在
输入:无
功能:出栈操作,删除栈顶元素
输出:如果删除成功,返回被删元素值,否则,抛出异常
后置条件:如果删除成功,栈减少了一个元素
GetTop
前置条件:栈已存在
输入:无
功能:读取当前的栈顶元素
输出:若栈不空,返回当前的栈顶元素值
后置条件:栈不变
Empty
前置条件:栈已存在
输入:无
功能:判断栈是否为空
输出:如果栈为空,返回1,否则,返回0
后置条件:栈不变
endADT
(2)栈的顺序存储结构及实现
1、栈的顺序存储结构——顺序栈
栈的顺序存储结构称之为顺序栈。
通常把数组中下标为0的一端作为栈底,同时附设指针top指示栈顶元素在数组中的位置。(top=数组元素的下标)
设存储栈元素的数组长度为StackSize,则:
栈空时,栈顶指针top==-1;
栈满时,栈顶指针top==StackSize-1.
入栈时,top=top+1;
出栈时,top=top-1.
2、 顺序栈的实现
类的声明
const int StackSize=100;
template <class DataType>
class seqStack
{
public:
seqStack ( ) {top=-1;}
~seqStack ( ){}
void Push ( DataType x );
DataType Pop ( );
DataType GetTop ( ){if(top!=-1) return data[top];}
int Empty ( ){top==-1?return 1:return 0;}
private:
DataType data[StackSize];
int top;
};
入栈Push
template <class DataType>
void SeqStack<DataType> ::Push ( DataType x)
{
if (top == StackSize -1) throw “上溢”;
top++;
data[top] = x;
}
出栈Pop
template <class DataType>
DataType seqStack<DataType> :: Pop ( )
{
if (top == -1) throw “下溢”;
x = data[top--];
return x;
}
3、 两栈共享空间
两栈共享空间:使用一个数组来存储两个栈,让一个栈的栈底为该数组的始端,另一个栈的栈底为该数组的末端,两个栈从各自的端点向中间延伸。
栈1的底固定在下标为0的一端;
栈2的底固定在下标为StackSize-1的一端。
top1和top2分别为栈1和栈2的栈顶指针;
StackSize为整个数组空间的大小
栈1为空:top 1=-1;
栈2为空:top 2=StackSize;
栈满:top 2=top1 + 1;
入栈:top 1++; top 2--;
出栈:top 1--; top 2++;
类的声明:
const int StackSize=100;
template <class DataType>
class BothStack
{
public:
BothStack( ){top 1=-1;top 2= StackSize;}
~BothStack( ){}
void Push(int i, DataType x);
DataType Pop(int i);
DataType GetTop(int i);
int Empty(int i);
private:
DataType data[StackSize];
int top1, top2;
};
入栈Push
template <class DataType>
void SeqStack<DataType> ::Push (int I, DataType x)
{
if (top 1== top 2 - 1) throw “上溢”;
if(i==1) data[++top 1]=x;
if(i==2) data[--top 2]=x;
}
出栈Pop
template <class DataType>
DataType seqStack<DataType> :: Pop ( int i)
{
if(i==1)
{
if(top 1==-1) throw“下溢”;
return data[top1--];
}
if(i==2)
{
if(top 2==StackSize) throw“下溢”;
return data[top2++];
}
}
(3)栈的链接存储结构及实现
1、栈的链接存储结构——链栈
栈的链接存储结构称之为链栈。
将链头作为栈顶,方便操作;链栈不需要附设头结点
2、 链栈的实现
类的声明
template <class DataType>
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<DataType> *top;
};
入栈Push
template <class DataType>
void LinkStack<DataType> ::Push(DataType x)
{
s = new Node<DataType>;
s->data = x;
s->next = top;
top = s;
}
出栈Pop
template <class DataType>
DataType LinkStack<DataType> ::Pop( )
{
if (top == NULL) throw "下溢";
x = top->data;
p = top;
top = top->next;
delete p;
return x;
}
(4) 顺序栈和链栈的比较
时间性能:相同,都是常数时间O(1)。
空间性能:
顺序栈:有元素个数的限制和空间浪费的问题。
链栈:没有栈满的问题,只有当内存没有可用空间时才会出现栈满,但是每个元素都需要一个指针域,从而产生了结构性开销。
总之,当栈的使用过程中元素个数变化较大时,用链栈是适宜的,反之,应该采用顺序栈。
二、队列
(1) 队列的逻辑结构
定义
队列:只允许在一端进行插入操作,而另一端进行删除操作的线性表。
允许插入(也称入队、进队)的一端称为队尾,允许删除(也称出队)的一端称为队头。
空队列:不含任何数据元素的队列。
队列的操作特性:先进先出
队列的抽象数据类型定义
ADT Queue
Data
队列中元素具有相同类型及先进先出特性,
相邻元素具有前驱和后继关系
Operation
InitQueue
前置条件:队列不存在
输入:无
功能:初始化队列
输出:无
后置条件:创建一个空队列
DestroyQueue
前置条件:队列已存在
输入:无
功能:销毁队列
输出:无
后置条件:释放队列所占用的存储空间
EnQueue
前置条件:队列已存在
输入:元素值x
功能:在队尾插入一个元素
输出:如果插入不成功,抛出异常
后置条件:如果插入成功,队尾增加了一个元素
DeQueue
前置条件:队列已存在
输入:无
功能:删除队头元素
输出:如果删除成功,返回被删元素值
后置条件:如果删除成功,队头减少了一个元素
GetQueue
前置条件:队列已存在
输入:无
功能:读取队头元素
输出:若队列不空,返回队头元素
后置条件:队列不变
Empty
前置条件:队列已存在
输入:无
功能:判断队列是否为空
输出:如果队列为空,返回1,否则,返回0
后置条件:队列不变
endADT
(5)队列的顺序存储结构及实现
1、队列的顺序存储结构——循环队列
设置队头、队尾两个指针
约定:队头指针front指向队头元素的前一个位置,
队尾指针rear指向队尾元素。
假溢出:当元素被插入到数组中下标最大的位置上之后,队列的空间就用尽了,尽管此时数组的低端还有空闲空间,这种现象叫做假溢出
循环队列:将存储队列的数组头尾相接。
队空:rear=front
队满:(rear+1)%QueueSize=front
2、 循环队列的实现
类的声明:
const int QueueSize=100;
template <class DataType>
class CirQueue
{
public:
CirQueue( ){front=rear=QueueSize-1;}
~ CirQueue( ){}
void EnQueue(DataType x);
DataType DeQueue( );
DataType GetQueue( );
int Empty( ){front==rear?return 1:return 0;}
private:
DataType data[QueueSize];
int front, rear;
};
入队EnQueue
template <class DataType>
void CirQueue<DataType> ::EnQueue(DataType x)
{
if ((rear+1) % QueueSize == front) throw "上溢";
rear =(rear+1) % QueueSize;
data[rear] = x;
}
出队DeQueue
template <class DataType>
DataType CirQueue<DataType> ::DeQueue( )
{
if (rear == front) throw "下溢";
front= (front + 1) % QueueSize;
return data[front];
}
读队头元素GetQueue
template <class DataType>
DataType CirQueue<DataType> ::GetQueue( )
{
if (rear == front) throw "下溢";
i = (front + 1) % QueueSize;
return data[i];
}
(6) 队列的链接存储结构及实现
1、队列的链接存储结构——链队列
队列的链接存储结构称之为链队列。
队头指针即为链表的头指针。
2、链队列的实现
类的声明
template <class DataType>
class LinkQueue
{
public:
LinkQueue( );
~LinkQueue( );
void EnQueue(DataType x);
DataType DeQueue( );
DataType GetQueue( );
int Empty( ){front==rear?return 1:return 0;}
private:
Node<DataType> *front, *rear;
};
构造函数LinkQueue
template <class DataType>
LinkQueue<DataType> ::LinkQueue( )
{
s = new Node<DataType>;
s->next = NULL;
rear = front=s;
}
入队EnQueue
template <class DataType>
void LinkQueue<DataType> ::EnQueue(DataType x)
{
s = new Node<DataType>;
s->data = x;
s->next = NULL;
rear->next = s;
rear = s;
}
出队DeQueue
template <class DataType>
DataType LinkQueue<DataType> ::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;
}
(7) 循环队列和链队列的比较
时间性能:循环队列和链队列的基本操作都需要常数时间O (1)。
空间性能:
1、循环队列:必须预先确定一个固定的长度,所以有存储元素个数的限制和空间浪费的问题。
2、链队列:没有队列满的问题,只有当内存没有可用空间时才会出现队列满,但是每个元素都需要一个指针域,从而产生了结构性开销。