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