栈与队列
第二弹~栈与队列来也!
文章目录
一.栈
栈必须按照“后进先出” 规则进行操作
- 栈是限定仅在表尾进行插入或删除的线性表
- 允许进行插入或删除的一端称为栈顶(top),它将随栈中元素增减而浮动
- 另一端称为栈底(bottom),其指针是固定的,不随栈中元素增减而移动
顺序栈
把自栈底到栈顶的元素按照逻辑次序依次存放在一组地址连续的存储单元里的方式称为栈的顺序存储结构
采用这种存储结构的栈称为顺序栈,同时附设指针 top 指示栈顶元素在数组中的位置。
结构体定义
#define STACK_INIT_SIZE 100 //顺序栈的初始分配量
#define STACK_INCREMENT 10 //顺序栈的分配增补量
typedef struct
{ ElemType *elem; //栈存储空间基地址
int top; //栈顶指针,非空栈指向栈顶元素
int stacksize; //当前分配的存储量
} SqStack;
初始化操作
//时间复杂度为O(1)
void InitStack_Sq(SqStack &S)
{
S.elem=new ElemType[STACK_INIT_SIZE];
if(!S.elem)//该栈已满
Error(“Overflow”);
S.top=-1;
S.stacksize=STACK_INIT_SIZE;
}
销毁操作
//时间复杂度为O(1)
void DestroyStack_Sq(SqStack S)
{ delete []S.elem;
S.top=-1;
S.stacksize=0;
}
清空操作:S.top = -1;
求栈长度操作:return (S.top+1);
取栈顶元素
//时间复杂度为O(1)
void GetTop_sq(SqStack S,ElemType &e)
{
//若顺序栈为空,则退出运行;否则用e返回栈顶元素值
if(S.top==-1)
Error(“stack empty!”);
e=S.elem[S.top];
}
入栈操作
① 判断当前存储空间是否已满,如果已满,则需要系统增加空间
② 先将栈顶指针加1,再将新数据元素入栈顶
//在正常情况下(不发生上溢),算法的时间复杂度为O(1);在非正常情况下(发生上溢),算法的时间复杂度为O(n)。
void Push_Sq(SqStack &S, ElemType e)
{
//插入元素e,作为新的栈顶元素
if(S.top==(S.stacksize-1))
//若当前存储空间已满,则增加空间
Increment(L);
S.elem[++S.top]=e;
// 栈顶指针先加1,再将e压入栈顶
}
void Increment(SqStack &S)
{
//为顺序栈S扩充STACK_INCREMENT个数据元素的空间
newstack=new ElemType[S.stacksize+STACK_INCREMENT];
// 增加STACK_INCREMENT个存储分配
if(!newstack)
Error("Overflow!"); // 存储分配失败
for(i=0;i<=S.top;i++)
newstack[i]=S.elem[i]; // 腾挪原空间中的数据到newstack中
delete []S.elem; // 释放元素所占用的原空间S.elem
S.elem=newstack; // 移交空间首地址
S.stacksize+=STACK_INCREMENT; // 扩充后的顺序栈的最大空间
}
出栈操作
① 判断当前顺序栈是否为空,如果为空,则给出相应信息并退出运行
② 先用取出栈顶数据元素值,再将栈顶指针减1
//算法的时间复杂度为O(1)
void Pop_Sq(SqStack &S,ElemType &e)
{
//若顺序栈S为空,给出相应信息并退出运行;否则用e返回栈顶元素值并修改栈顶指针
if(S.top==-1)
Error("Stack Empty!"); // 栈空,给出相应信息并退出运行
e=S.elem[S.top--];
}
共享栈
相同数据类型的两个栈,栈空间需求相反
链栈
空栈:
链栈可以不用头结点,栈顶指针作为链表的头指针
结构体形式
typedef struct StackNode
{
ElemType data;//结点数据域
struct StackNode *next;//结点指针域
}StackNode, *LinkStack;
LinkStack S;
链栈进栈
//算法的时间复杂度为O(1)
void Push(LinkStack &S,SElemType &e)
{
//栈顶插入元素e
p =new StackNode; //生成新结点
p->data=e; //将新结点数据置为e
p->next=S; //将新结点插入栈顶
S=p; //修改栈顶指针S值为p
}
链栈出栈
//算法的时间复杂度为O(1)
void Pop(LinkStack &S,SElemType &e)
{
// 删除栈顶元素,用e返回
if(S==NULL)
return Error(“Stack Empty!”); //栈空
e=S->data; //将栈顶元素赋给e
p=S; //临时保存栈顶元素空间,以备释放
S=S->next; //修改栈顶指针
delete p;
}
常见例题:
- 回文字符串
- 汉诺塔问题
- 八皇后问题和迷宫问题
二.队列
队列必须按照“先进先出” 规则进行操作
- 队列是限定只允许在表的一端进行插入,而在表另一端进行删除的线性表
- 允许插入的一端称为队尾,队尾将随着队列中元素的增加而浮动
- 允许删除的一端称为队头,队头将随着队列中元素的减少而浮动
顺序队列
在队列的顺序存储结构中,除了用一组地址连续的存储单元依次存放队列头到队列尾的数据元素之外,还需要附设两个指针front和rear分别指示队列头元素和队列尾元素的位置
由于队列头和尾都是动态的,因此需设置队头和队尾两个指针,且在初始化空队列时,令 front = rear = 0
当入队时,尾指针加1;当出队时,头指针加1
在非空队列中,头指针front指向队头元素,尾指针rear指向队尾元素的下一个位置
因为顺序队列有“假溢出”现象,因此产生了循环队列
循环队列
设存储循环队列的数组长度为Queue_Size,则:
循环队列长度 QueueLength = (rear - front + Queue_Size ) % Queue_Size
队尾指针修改:rear = (rear + 1) % Queue_Size
队头指针修改:front = (front + 1) % Queue_Size
循环队列空的条件:front == rear
循环队列满的条件:(rear + 1) % Queue_Size == front
例:
结构体形式
#define QUEUE_MAX_SIZE 100; // 循环队列大小
typedef struct {
ElemType *elem; // 存储空间基地址
int front; // 头指针,队列非空指向队头元素
int rear;
// 尾指针,队列非空指向队尾元素下一位置
} SqQueue;
初始化
//时间复杂度为O(1)
void InitQueue_Sq(SqQueue &Q)
{
//构造一个空循环队列Q
Q.elem=new ElemType[QUEUE_MAX_SIZE];
if(!Q.elem) Error(" Overflow!"); // 存储分配失败
Q.front=Q.rear=0;
}
销毁操作
//时间复杂度为O(1)
void DestroyQueue_Sq(SqQueue &Q)
{
//释放循环队列Q所占用的存储空间
delete []Q.elem;
Q.front=Q.rear=0;
}
清空操作
//时间复杂度为O(1)
void ClearQueue_Sq(SqStack &Q)
{
//重置循环队列Q为空队列
Q.front=Q.rear=0;
}
求队列长操作
//时间复杂度为O(1)
int QueueLength_Sq(SqQueue Q)
{
//返回循环队列Q的数据元素个数
length=(Q.rear-Q.front+QUEUE_MAX_SIZE)%QUEUE_MAX_SIZE;
return length;
}
取队列头元素值操作
//时间复杂度为O(1)
void GetHead_Sq(SqQueue Q,ElemType &e)
{
//若循环队列Q为空,则给出相应信息并退出运行;否则用返回队头元素值
if(Q.front==Q.rear) Error("Queue Empty!");
e=Q.elem[Q.front];
}
入队操作
① 判断当前存储空间是否已满,如果已满,则给出相应信息并退出运行
② 先将数据元素插入队尾,再修改队尾指针
//时间复杂度为O(1)
void EnQueue_Sq(SqQueue &Q,ElemType e)
{
//若循环队列Q已满,则给出相应信息退出运行;否则将元素e插入队尾,并修改队尾指针
if(((Q.rear+1)%QUEUE_MAX_SIZE)==Q.front)
Error("Queue Overflow!")
Q.elem[Q.rear]=e;
Q.rear=(Q.rear+1)%Queue_Size;
}
出队操作
① 判断循环队列是否为空:如果为空,则给出相应信息并退出运行
② 先取出队头数据元素值,再修改队头指针
//时间复杂度为O(1)
void DeQueue_Sq(SqQueue &Q,ElemType &e) {
// 若循环队列Q为空,则给出相应信息并退出运行;否则用e返回队头元素,并修改队头指针
if(Q.front==Q.rear) Error("Queue Empty!");
e=Q.elem[Q.front];
Q.front=(Q.front+1)%QUEUE_MAX_SIZE;
}
链队列
用链表表示的队列称为链队列。
通常用单链表表示,并设置队头指针指向其头结点,队尾指针指向其最后一个结点。
空链队列:
front 和 rear 都指向头结点
结构体形式
typedef struct QNode
{
ElemType data ; // 数据域
struct QNode *next ; // 指针域
}QNode,*QueuePtr;
typedef struct {
QueuePtr front; // 头指针,指向链队列头结点
QueuePtr rear; // 尾指针,指向链队列最后结点
} LinkQueue;
初始化操作
void InitQueue_L(LinkQueue &Q)
{
//构造一个空链队列Q
Q.front=Q.rear=new QNode;
Q.front->next=NULL;
}
销毁操作
//时间复杂度为O(n)
void DestroyQueue_L(LinkQueue &Q)
{
// 释放链队列Q所占用的存储空间
while(Q.front)
{
Q.rear=Q.front->next;
delete Q.front;
Q.front=Q.rear;
}
}
清空操作
//时间复杂度为O(n)
void ClearQueue_L(LinkQueue &Q)
{
//清空链队列L,释放所有结点空间
p=Q.front->next;
while(p)
{
q=p;
p=p->next;
delete q;
}
Q.front->next=NULL;
Q.rear=Q.front;
}
求队列长
//时间复杂度为O(n)
int QueueLength_L(LinkQueue Q) {// 返回链队列Q的长度
p=Q.front; // 设置指针,初始指向链队列头结点
length=0; // 设置计数器length初始为0
while(p->next) {
length++;
p=p->next; }
return length;
}
取队列头元素操作
//时间复杂度为O(1)
void GetHead_L(LinkQueue Q,ElemType &e)
{
//若链队列Q为空,则给出相应信息并退出运行;否则用e返回队头结点数据域的值
if(Q.front->next==NULL) Error("Queue Empty!");
e=Q.front->next->data;
}
入队操作
链队列的入队列操作只需要处理队尾结点,而不需要考虑其他结点
void EnQueue_L(LinkQueue &Q,ElemType e)
{
//插入一个数据域值为e的结点到链队列Q中,成为新的队尾结点
QueuePtr p=new QNode;
p->data=e; // 生成数据域值为e的新结点
p->next=NULL;
Q.rear->next=p; // 将新结点插到队尾
Q.rear=p; // 修改队尾指针,令其指向新结点
}
出队操作
链队列的出队操作只需要处理队头结点,而不需要考虑其他结点。链队列的出队操作需要考虑两种情况:
① 当队列长度大于1时,用e返回链队列队头结点数据域的值,并释放其存储空间
② 当队列长度等于1时,即删除的既是队头结点又是队尾结点,还需要修改队尾指针
void DeQueue_L(LinkQueue &Q,ElemType &e)
{
// 若链队列Q为空,则退出运行;否则用e返回队头结点数据域的值
if(Q.front->next==NULL) Error("Queue Empty!");
QNode* p=Q.front->next; // 指针p指向链队列Q队头结点
e=p->data;
Q.front->next=p->next; // 修改队头指针
if(Q.rear==p)
Q.rear=Q.front; // 若队列长度等于1,则修改尾指针
delete p; // 释放队头结点所占的存储空间
}
下一弹:数组与广义表