栈的概念
栈:限定仅在表尾进行插入和删除操作的线性表。
空栈:不含任何元素的栈。
允许插入和删除的一端称为栈顶,另一端称为栈底。
栈的操作特性:后进先出。
顺序栈
顺序栈的类型定义
const int MAXSIZE = 100;
typedef char ElemType;
typedef struct
{
ElemType data[MAXSIZE];
int top;//top 不是一个指针变量
}SqStack;
顺序栈的实现——初始化
void InitStack(SqStack &S)
{
S.top = -1;//将top放置在-1的位置,表示表内无元素
}
顺序栈的实现——入栈
void Push(SqStack &S, ElemType e)
{
if (S.top == MAXSIZE-1)
{cout<<"栈已满"<<endl;
exit(0);}
S.top ++;
S.data[S.top] = e;
}
顺序栈的实现——出栈
ElemType Pop(SqStack &S)
{
if (S.top == -1)
{cout<<"栈已空"<<endl;
exit(0);}
ElemType x = S.data[S.top];
S.top --;
return x;
}
顺序栈的实现——取栈顶元素
ElemType GetTop(SqStack &S)
{
if (S.top == -1)
{
cout<<"栈空"<<endl;
exit(0);}
return S.data[S.top];
}
顺序栈的实现——判断栈空
bool StackEmpty(SqStack &S)
{
return S.top == -1;
}
顺序栈的实现——判断栈满
bool StackFull(SqStack &S)
{
return S.top == MAXSIZE - 1;
}
链栈
栈的链式存储及基本操作
将哪一端作为栈顶?将链头作为栈顶,方便操作。
链栈需要加头结点吗?链栈不需要附设头结点。
链栈的类型定义
typedef char ElemType;
typedef struct
{
ElemType data;
Node *next;
}Node, LinkStack*;
链栈的实现——初始化
void InitStack(LinkStack &L)
{
L = NULL;
}
链栈的实现——入栈
ElemType Push(LinkStack &L, ElemType e)
{
LinkStack p = new Node;
p->data = e;
p->next = L;
L = p;
}
链栈的实现——出栈
ElemType Pop(LinkStack &S)
{
if(S==NULL){
cout<<"空"<<endl;
exit(1);}
ElemType x = S->data;
LinkStack p = S;
S = S->next;
delete p;
return x;
}
链栈的实现——取栈顶元素
ElemType GetTop(LinkStack &S)
{
if(S == NULL)
{
cout<<"栈空"<<endl;
exit(1);
}
return S->data;
}
链栈的实现——判断栈空
bool StackEmpty(LinkStack &S)
{
return S == NULL;
}
链栈的实现——销毁
void Destroy(LinkStack &S)
{
while(S){
LinkStack p = S;
S = S->next;
delete p;}
}
顺序栈和链栈的比较
时间性能:相同,都是常数时间O(1)。
空间性能:
- 顺序栈:有元素个数的限制和空间浪费的问题。
- 链栈:没有栈满的问题,只有当内存没有可用空间时才会出现栈满。但是每个元素都需要一个指针域,从而产生了结构性开销。
因此,当栈使用过程中元素变化较大时,用链栈比较合适,反之,应该采用顺序栈。
例题:十进制转八进制
const int MAXSIZE = 100;
typedef struct
{
ElemType data[MAXSIZE];
int top;
}SeqStack;
void conversion(int n)
{
SqStack S;
InitStack(S);
while(n)
{
Push(S, n%8);
n = n/8;
}
while(!StackEmpty(S))
{
cout<<Pop(S);
}
cout<<endl;
}
循环队列
只允许在一端进行插入操作,而另一端进行删除操作的线性表。
操作特性:先进先出。
实现方法:
- 顺序存储:用一维数组 用顺序存储队列是因为数组是静态申请的,有长度限制,也就有了假上溢问题。
- 链式存储:用链表 节点时动态申请的,没有长度限制,于是没有假上溢问题;同时因为队列要遵循进队和出队的规则,于是链表的插入删除的优势没有发挥。
其中,
- 两个指针有指示作用
- front指向队头存放元素的前一个位置;
- rear指向当前的队尾元素位置。
- 入队:rear+1,写入数据;
出队:front+1,读出数据。 - 如果rear追到front说明队满,如果front追到rear说明队空,但此时如果状态都是front==rear,就存在二义性:牺牲一个存储单元。
三种解决方案:
- 令队列中的一个单元闲置,使得队列非空时,rear和front之间至少间隔一个空闲单元,但是也浪费了一个元素空间。
- 设置一个辅助标志flag,有元素出队列时,flag=false,入队列时,flag=true。则当rear=front && flagtrue时,队满;当rear=front && flagfalse时,队空。
- 使用一个计数器num记录队列中的元素个数:num==0表示空队列,每次进队num++,每次出队num–,当num=MAXSIZE时为满队。
- 队满:当队列添加元素到rear的下面第二个元素时front时,转圈子要碰头了,队满 (rear+1)%MAXSIZE == front
- 队空:当队列删除元素到front == rear时,队空。
循环队列——构建
const int MAXSIZE = 100;
typedef char ElemType;
typedef struct
{
ElemType data[MAXSIZE];
int front, rear;
}SqQueue
循环队列——初始化
void InitQueue(SqQueue &Q)
{
Q.rear = Q.front = 0;
}
循环队列——入队
ElemType Push(SqQueue &Q, ElemType e)
{
if((Q.rear+1) % MAXSIZE == Q.front)
{
cout<<"队满"<<endl;
exit(1);
}
Q.rear = (Q.rear + 1) % MAXSIZE;
Q.data[rear] = e;
}
循环队列——出队
ElemType Pop(SqQueue &Q)
{
if(Q.rear == Q.front)
{
cout<<"队空"<<endl;
exit(1);
}
Q.front = (Q.front + 1)%MAXSIZE;
ElemType x = Q.data[front];
return x;
}
循环队列——判断是否为空
bool QueueEmpty(SqQueue &Q)
{
return Q.rear == Q.front;
}
循环队列——判断是否为满
bool QueueFull(SqQueue &Q)
{
return (Q.rear + 1)%MAXSIZE == Q.front;
}
链队列
跟单链表一样,队头指针为链表的头指针,不装数据(和链栈不同)。
链队列——构造
typedef char ElemType;
typedef struct Node
{
ElemType data;
struct Node *next;
}Node;
typedef struct
{
Node *front;
Node *rear;
}LinkQueue;
链队列——初始化
void InitQueue(LinkQueue &Q)
{
Q.rear = Q.front;
}
链队列——入队
ElemType EnQueue(LinkQueue &Q, ElemType e)
{
Node *eNode = new Node;
eNode->data = e;
eNode->next = NULL;
Q.rear->next = eNode;
Q.rear = eNode;
}
链队列——出队
ElemType DeQueue(LinkQueue &Q)
{
if(Q.front == Q.rear)
{
cout<<"空"<<endl;
exit(1);
}
Node *p = Q.front->next;
ElemType x = p->data;
Q.front->next = p->next;
if(Q.rear == p)
Q.rear = Q.front;
delete p;
return x;
}
链队列——判断队空
bool QueueEmpty(LinkQueue &Q)
{
return Q.rear == Q.front;
}
链队列——销毁
void Destroy(LinkQueue &Q)
{
while(Q.front){
Q.rear = Q.front->next;
delete Q.front;
Q.front = Q.rear;
}
}