一、特殊的线性表——栈
1.栈:限定仅在表尾进行插入和删除操作的线性表。
栈的特性:先进后出(first in last out);允许删除和插入的一端称为栈顶,另一端称为栈底;插入包括入栈、进栈、压栈,删除包括出栈、弹栈。
注:栈只是对表插入和删除操作的位置进行了限制,并没有限定插入和删除操作进行的时间。
2.栈的抽象数据类型定义:
DestroyStack
前置条件:栈已存在
输入:无
功能:销毁栈
输出:无
后置条件:释放栈所占用的存储空间
Push
前置条件:栈已存在
输入:元素值x
功能:在栈顶插入一个元素x
输出:如果插入不成功,抛出异常
后置条件:如果插入成功,栈顶增加了一个元素
Pop
前置条件:栈已存在
输入:无
功能:删除栈顶元素
输出:如果删除成功,返回被删元素值,否则,抛出异常
后置条件:如果删除成功,栈减少了一个元素
GetTop
前置条件:栈已存在
输入:无
功能:读取当前的栈顶元素
输出:若栈不空,返回当前的栈顶元素值
后置条件:栈不变
Empty
前置条件:栈已存在
输入:无
功能:判断栈是否为空
输出:如果栈为空,返回1,否则,返回0
后置条件:栈不变
endADT
(一)、栈的顺序存储结构与实现
1.顺序栈:栈的顺序存储结构。
通常把数组中下标为0的一端作为栈底,同时附设变量top指示栈顶元素在数组中的位置。栈空时:top=-1,栈满时:top=StackSize-1;入栈:top加1;出栈:top减1。
2. 顺序栈的实现:将栈的抽象数据类型定义在顺序栈存储结构下用c++的类实现。成员变量实现顺序栈的存储结构,成员变量实现栈的基本操作。
顺序栈类的声明:
const int StackSize=100;//根据实际问题具体定义
template <class T>
class seqStack
{
public:
seqStack ( ) ;//构造函数,初始化一个空栈
~seqStack ( );//析构函数
void Push ( T x );//入栈操作,将元素x入栈
T Pop ( );//出栈操作,将栈顶元素弹出
T GetTop ( );//取栈顶元素(并不删除)
bool Empty ( );//判断站是否为空
private:
T data[MAX_SIZE];//存放栈元素的数组
int top;//栈顶元素在数组中的下标
}
(1)构造函数——顺序栈的初始化:初始化一个空的顺序栈只需将栈顶指针top置为-1。
(2)析构函数——顺序栈的销毁:顺序栈是静态存储分配,在顺序栈变量退出作用域时自动释放顺序栈所占存储空间,因此顺序栈无须销毁,析构函数为空。
(3)入栈操作
template <class T>
void seqStack<T>::Push ( T x)
{
if (top==MAX_SIZE-1) throw “溢出”;
data[++top]=x;//top++;data[top]=x;(注意top++和++top的区别)
}
(4)出栈操作
template <class T>
T seqStack<T>:: Pop ( )
{
if (top==-1) throw “溢出”;
x=data[top];
top--;
return x;
}
(5)取栈顶元素
template <class T>
T seqStack<T>::GetTop ( )
{
if (Empty()) throw ”空栈” ;
return data[top];
}
(6)判断是否为空
template <class T>
bool seqStack<T>::Empty ()
{
if (top==-1)
return true;
return false;
}
★两栈共享空间(双端栈):使用一个数组来存储两个栈,让一个栈的栈底为该数组的始端,另一个栈的栈底为该数组的末端,两个栈从各自的端点向中间延伸。
栈1为空时:top=-1;栈2为空时:top=Stack_Size;栈满:top1=top2-1。
--两栈共享空间类的声明:
const int Stack_Size=100;
template <class T>
class BothStack
{
public:
BothStack( );
~BothStack( );
void Push(int i, T x);
T Pop(int i);
T GetTop(int i);
bool Empty(int i);
private:
T data[Stack_Size];
int top1, top2;
};
--两栈共享空间的实现——插入
1. 如果栈满,则抛出上溢异常;
2. 判断是插在栈1还是栈2;
2.1 若在栈1插入,则
2.1.1 top1加1;
2.1.2 在top1处填入x;
2.2 若在栈2插入,则
2.2.1 top2减1;
2.2.2 在top2处填入x;
template <class T>
void BothStack<T>::Push(int i, T x )
{
if (top1==top2-1) //判断是否栈满
throw "上溢";
if (i==1)
data[++top1]=x;
if (i==2)
data[--top2]=x;
}
--两栈共享空间的实现——删除
1.若是在栈1删除,则
1.1 若栈1为空栈,抛出下溢异常;
1.2 删除并返回栈1的栈顶元素;
2.若是在栈2删除,则
2.1 若栈2为空栈,抛出下溢异常;
2.2 删除并返回栈2的栈顶元素;
template <class T>
T BothStack<T>::Pop(int i){
if (i==1) {
if (top1== -1) throw "下溢";
return data[top1--];
}
if (i==2) {
if (top2==StackSize) throw "下溢";
return data[top2++];
}
}
--判断某个栈空的算法:
template <class T>
bool BothStack<T>::Empty(int i)
{
if(i==1){
if(top1==-1) return 1;
else return 0;
}
if(i==2) {
if(top2==StackSize) return 1;
else return 0;
}
}
--取某个栈栈顶的算法:
template <class T>
T BothStack<T>::GetTop(int i)
{
if(i==1) {
if (top1!=-1) return data[top1];
}
if(i==2) {
if(top2!=StackSize) return data[top2];
}
}
(二)、栈的链式存储结构及实现
1.链栈:栈的链接存储结构。将链头作为栈顶,方便操作;链栈不需要附设结点。
2.链栈的类声明:
template <class T>
class LinkStack
{
public:
LinkStack( ) {top=NULL;}//构造函数,初始化一个空链栈
~LinkStack( );//析构函数
void Push(T x); //入栈操作,将元素x入栈
T Pop( ); //出栈操作,将栈顶元素出栈
T GetTop( );//取栈顶元素(并不删除)
bool Empty( );//判断链栈是否为空
private:
Node<T> *top; //栈顶指针即链栈的头指针
}
(1)链栈的实现——插入(入栈)(没有判断栈满)
template <class T>
void LinkStack<T>::Push(T x)
{
s=new Node<T>;
s->data=x;
s->next=top;
top=s;
}
(2)删除(出栈)
template <class T>
T LinkStack<T>::Pop( )
{
if (top==NULL)
throw "下溢";
x=top->data;
p=top;
top=top->next; //不能用top--
delete p;
return x;
}
(3)析构(链栈的销毁)
template <class T>
LinkStack<T>::~LinkStack( )
{
while (top)
{
Node<T> *p;
p=top->next;
delete top;
top=p;
}
}
(三)、顺序栈和链栈的比较
1.时间性能:相同,都是常数时间O(1)。
2.空间性能:
1)顺序栈:有元素个数的限制和空间浪费的问题。
链栈:没有栈满的问题,只有当内存没有可用空间时才会出现栈满,但是每个元素都需要一个指针域,从而产生了结构性开销。
2)结论:当栈的使用过程中元素个数变化较大时,用链栈是适宜的,反之,应该采用顺序栈。
二、特殊线性表——队列
队列是只允许在一端进行插入操作,而另一端进行删除操作的线性表;队列的特性:先进先出。允许插入(也称入队、进队)的一端称为队尾,允许删除(也称出队)的一端称为队头。不含任何数据元素的队列称为空队列。
(一)、顺序队列:队列的顺序存储结构
1.队头指针和队尾指针
1)、队头指针指向队列中的第一个元素之前的元素,队尾指针指向队列中的最后一个元素
2)、队头指针指向队列中的第一个元素,队尾指针指向队列中的最后一个元素的后一个位置
假溢出:当元素被插入到数组中下标最大的位置上之后,队列的空间就用尽了,尽管此时数组的低端还有空闲空间,这种现象叫做假溢出。
解决假溢出——循环队列
2.循环队列:将存储队列的数组头尾相接。
3.如何确定不同的队空、队满的判定条件?
方法一:附设一个存储队列中元素个数的变量num,当num=0时队空,当num=QueueSize时为队满;
方法二:修改队满条件,浪费一个元素空间,队满时数组中只有一个空闲单元;
方法三:设置标志flag,当front=rear且flag=0时为队空,当front=rear且flag=1时为队满。
队满的条件:(rear+1) %QueueSize==front;队空:rear==front.
4.循环队列类的声明:
const int QueueSize=100;
template <class T>
class CirQueue{
public:
CirQueue( );
~ CirQueue( );
void EnQueue(T x);
T DeQueue( );
T GetQueue( );
bool Empty( ){
if (rear==front) return true;
return false;
};
private:
T data[QueueSize];
int front, rear;
};
(1)入队
template <class T>
void CirQueue<T>::EnQueue(T x)
{
if ((rear+1) % QueueSize ==front) throw "上溢";
rear=(rear+1) % QueueSize;
data[rear]=x;
}
(2)出队
template <class T>
T CirQueue<T>::GetQueue( )
{
if (rear==front) throw "下溢";
i=(front+1) % QueueSize;
return data[i];
}
(3)队列长度
template <class T>
int CirQueue<T>::GetLength( )
{
if (rear==front) throw "下溢";
len=(rear-front+ QueueSize) % QueueSize;
return len;
}
(二)队列的链接存储结构及实现
1.链队列:队列的链接存储结构。队头指针(front)即链表的头指针(frist)。
2.链队列类的声明:
template <class T>
class LinkQueue
{
public:
LinkQueue( );
~LinkQueue( );
void EnQueue(T x);
T DeQueue( );
T GetQueue( );
bool Empty( );
private:
Node<T> *front, *rear;
};
(1)构造函数
template <class T>
LinkQueue<T>::LinkQueue( )
{
front=new Node<T>;
front->next=NULL;
rear=front;
}
(2)入队
void LinkQueue<T>::EnQueue(T x)
{
s=new Node<T>;
s->data=x;
s->next=NULL;
rear->next=s;
rear=s;
}
(3)出队
T LinkQueue<T>::DeQueue( )
{
if (rear==front) throw "下溢";
p=front->next;
x=p->data;
front->next=p->next;
delete p;
if (front->next==NULL) rear=front;
return x;
}
(三)循环队列和链队列的比较
链队列:没有队列满的问题,只有当内存没有可用空间时才会出现队列满,但是每个元素都需要一个指针域,从而产生了结构性开销。