3.1 栈
3.1.1 栈的逻辑结构
1.栈(stack)是限定仅在表尾进行插入和删除操作的线性表,允许插入和删除的而一端称为栈顶,另一端称为栈底。不含任何元素的栈称为空栈。
2.栈的特性:后进先出(在任何时候出栈的元素都只能是栈顶元素,即最后入栈者最先出栈)。
3. 栈的抽象数据类型定义
ADT Stack
Data
栈中元素具有相同类型及后进先出的特性,相邻元素具有前驱和后继关系
Operation
InitStack
前置条件:栈不存在
输入:无
功能:栈的初始化
输出:无
后置条件:构造一个空栈
DestroyStack
前置条件:栈已存在
输入:无
功能:销毁栈
输出:无
后置条件:释放栈所用的存储空间
Push
前置条件:栈已存在
输入:元素值x
功能:入栈操作,在栈顶插入一个元素x
输出:如果插入不成功,则抛出异常
后置条件:如果插入成功,则栈顶增加了一个元素
Pop
前置条件:栈已存在
输入:无
功能:出栈操作,删除栈顶元素
输出:如果删除成功,返回被删元素值,否则,抛出异常
后置条件:如果删除成功,则栈顶少了一个元素
GetTop
前置条件:栈已存在
输入:无
功能:取栈顶元素,读取当前的栈顶元素
输出:若栈不空,返回当前的栈顶元素值
后置条件:栈不变
Empty
前置条件:栈已存在
输入:无
功能:判空操作,判断栈是否为空
输出:如果栈为空,返回1,否则,返回0
后置条件:栈不变
endADT
3.1.2 栈的顺序存储结构及实现
1. 栈的顺序存储结构——顺序栈
通常把数组中下标为0的一端作为栈底 。 指针top指示栈顶元素在数组中的位置。
StackSize:存储栈元素的数组长度。 栈空:top= -1 栈满:top=StackSize - 1
2. 顺序栈的实现
将栈的抽象数据类型定义在顺序栈存储结构下用c++中的类实现。由于栈元素的数据类型不能确定,因此采用c++的模板机制。
const int StackSize=10; // 10只是示例性的数据,可以根据实际问题具体定义
template
class SeqStack
{
public:
SeqStack(){top= -1;} //构造函数,初始化一个空栈
~ SeqStack(){ } //析构函数为空
Void push(DataType x); //入栈操作,将元素x入栈
DataType pop(); //出栈操作,将栈顶元素弹出
DataType GetTop(){if(top!=-1) return data [top];} //取栈顶元素
Int Empty(){top==-1?return 1:return 0;}
Private:
Datatype data[StckMaxsize];
Int top;
} ;
(1) 栈的初始化
初始化一个空栈只需将栈顶指针top置为-1。
(2)入栈操作
在栈中插入元素x只需将栈顶指针top加1,然后在top指向的位置填入元素x,算法如下:
template
void SeqStack
{
If(top==StackSize-1) throw”上溢”;
data[++top=x;]
}
(3)出栈操作
删除栈顶元素只需取出栈顶元素,然后将栈顶指针top减1,算法如下:
Template
DataType SeqStack
{
If(top==-1) throw”下溢”;
X=data[top--];
return x;
}
(4)取栈顶元素
取栈顶元素只是将top指向的栈顶元素取出,并不修改栈顶指针。
(5)判空操作
顺序栈的判空操作只需判断top==-1是否成立,如果成立,则栈为空,返回1;如果不成,则栈非空,返回0。
3. 两栈共享空间
两共享占空间入栈算法push
Template
Void BothStack
{
If(top1==top2-1) throw”上溢”;
If(i==1)data[++top1]=x;
If(i==2)data[--top]=x;
}
两栈共享空间出栈算法Pop
Template
DataType BothStack
{
If(i==1)
If(top1==-1)throw“下溢”;
Return data[top--];
}
If(i==2)
If(top2==StackSize)throw”下溢”;
Return data[top2++];
}
}
3.13 栈的链接存储结构及实现
1. 栈的链接存储结构——链栈
栈的链接存储结构称为链栈。
2. 链栈的实现
⑴构造函数
构造函数的作用是初始化一个空链栈,由于链栈不带头结点,因此只需将栈顶指针top置为空。
⑵入栈操作
链栈入栈算法Push
Template
Void LinkStack
{
S=new Node;s->data=x;
s->next=top;top=s;
}
⑶出栈操作
链栈出栈算法Pop
Template
DataType LinkStack
{
If(top==NULL)throw“下溢”;
X=top->data;p=top;
Top=top->next;
Delete p;
Return x;
}
⑷取栈顶元素
取栈顶元素只需返回栈顶指针top所指结点的数据域。
⑸判空操作
链栈的判空操作只需判断top==NULL是否成立。如果成立,则栈为空,返回1;如果不成立,则栈非空,返回0。
⑹析构函数
链栈的析构函数需要将链栈中所有结点的存储空间释放,算法与单链表类的析构函数类似,算法略。
3.14 顺序栈和链栈的比较
实现顺序栈和链栈的所有基本操作的算法都只需要常数时间,因此唯一可以比较的是空间性能。初始时顺序栈必须确定一个固定的长度,所以有存储元素个数的限制和空间浪费的问题。链栈没有栈满的问题,只有当内存没有可用空间时才会出现栈满,但是每个元素都需要一个指针域,从而产生了结构性开销。所以当栈的使用过程中元素个数变化较大时,用链栈是适宜的;反之,应该采用顺序栈。
3.2 队 列
3.2.1队列的逻辑结构
1.定义:队列只允许在一端进行插入操作,在另一端进行删除操作的线性表。
允许插入的一端称为队尾,允许删除的一端称为队头。(先进先出)
2.队列的抽象数据类型定义
ADT Queue
InitQueue
前置条件:队列不存在
输入:无
功能:初始化队列
输出:无
后置条件:创建一个空队列
DestroyQueue
前置条件:队列已存在
输入:无
功能:销毁队列
输出:无
后置条件:释放队列所占用的存储空间
EnQueue
前置条件:队列已存在
输入:元素值x
功能:入队操作,在队尾插入一个元素
输出:如果插入不成功,抛出异常
后置条件:如果插入成功,队尾增加了一个元素
DeQueue
前置条件:队列已存在
输入:无
功能:出队操作,删除队头元素
输出:如果删除成功,返回被删元素值,否则,抛出删除异常
后置条件:如果删除成功,队头减少了一个元素
GetQueue
前置条件:队头已存在
输入:无
功能:读取队头元素
输出:若队列不空,返回队头元素
后置条件:队列不变
Empty
前置条件:队列不存在
输入:无
功能:判空操作,判断队列是否为空
输出:如果队列为空,返回1,否则返回0
后置条件:队列不变
endADT
3.2.2队列的顺序存储结构及实现
循环队列:队列的头尾相接的顺序存储结构称为循环队列。
约定:队头指针front指向队头元素的前一个位置,队尾指针rear指向队尾元素。
假溢出:当元素被插入到数组中下标最大的位置上之后,队列的空间就用尽了,尽管此时数组的低端还有空闲空间,这种现象叫做“假溢出”。
队空和队满的判定:队空front=rear
队满(rear+1)%QueueSize==front
循环队列的实现
const int QueueSize=100; //定义存储队列元素的数组的最大长度
template
class CirQueue
{
public:
CirQueue(){front=rear=QueueSize;} //构造函数,初始化空队列
~CirQueue(){} //析构函数为空
void EnQueue(DataType x); //入队操作,将元素x入队
DataType DeQueue(); //出队操作,将队头元素出队
DataType GetQueue(); //读取队头元素(并不删除)
int Empty(){front==rear?return 1:return 0;} //判断队列是否为空
private:
DataType data[QueueSize]; //存放队列元素的数组
int front,rear; //队头和队尾指针
};
(1) 构造函数
构造函数的作用是初始化一个空的循环队列,只需将队头指针和队尾指针同时指向数组的某一个位置,一般是数组的高端,即rear=front=QueueSize-1。
(2) 入队操作
template
void CirQueue
{
if ((rear+1)%QueueSize==front) throw"上溢";
rear=(rear+1)%QueueSize; //队尾指针在循环意义下加1
data[rear]=x; //在队尾处插入元素
}
(3) 出队操作
template
DataType CirQueue
{
if (rear==front) throw"下溢";
front=(front+1)%QueueSize; //队头指针在循环意义下加1
return data[front]; //读取并返回出队前的队头元素
}
(4) 读取队头元素
template
DataType CirQueue
{
if(rear==front) throw"下溢";
i=(front+1)%QueueSize; //注意不要给队头指针赋值
return data[i];
}
(5) 判空操作
循环队列的判空操作只需要判定front==rear是否成立。如果成立,则队列为空,返回1;如果不成立,则队列非空,返回0。
3.2.3 队列的链式存储结构及实现
链队列:队列的链式存储结构称为链队列。
链队列的实现
template
class LinkQueue
{
public:
LinkQueue(); //构造函数,初始化一个空的链队列
~LinkQueue(); //析构函数,释放链队列中各结点的存储空间
void EnQueue(DataType x); //入队操作,将元素x入队
DataType DeQueue(); //出队操作,将队头元素出队
DataType GetQueue(); //取链队列的队头元素
int Empty(){front==rear?return 1: return 0;}//判断链队列是否为空
private:
Node
};
(1) 构造函数
template
LinkQueue
{
Node
s=new Node
front=rear=s; //将队头指针队尾指针都指向头结点s
}
(2) 析构函数
template
LinkQueue
{
Node
while (front!=NULL)
{
p=front->next;
delete front;
front=p;
}
}
(3)入队操作
template
void LinkQueue
{
Node
s=new Node
s->next=NULL;
rear->next=s; //将结点s插入到队尾
rear=s;
}
(4)出队操作
template
DataType LinkQueue
{
Node
int x;
if(rear==front) throw"下溢";
p=front->next;x=p->data;//暂存队头元素
front->next=p->next; //将队头元素所在结点摘链
if(p->next==NULL) rear=front;//判断出队前队列长度是否为1
delete p;
return x;
}
(5)读取
template
DataType LinkQueue
{
if(front!=rear)
return front->next->data;
}
template
{
if(front==rear)
return 1;
else
return 0;
}
3.3 应用举例
3.3.1 栈的应用举例——表达式求值
3.3.2栈的应用举例——火车车厢重排
思想火花——直觉可能是错误的