栈和队列(自己复习用)

图片来自C语言中文网和《大话数据结构》

栈和队列

栈和队列是两种特殊的线性表。 它们的基本操作大致与线性表相同,同时也具有顺序结构和链式结构两种。而其特殊性就体现在两者的不同上。
栈和队列的区别:

  • 栈遵循先进后出,队列遵循先进先出。
  • 栈只允许表尾进行插入和删除,队列只允许表尾进行插入,表头进行删除。
  • 栈常用于实现深度优先遍历,队列常用于实现广度优先遍历。

堆,栈,队列

  • 堆,又称为优先队列,遵循队列的原则,但本质是棵二叉树。
  • 栈,又称堆栈。
  • 队列。

栈的应用

  • 递归
  • 数值转换
  • 四则运算表达式求值
  • 括号匹配问题

队列的应用

  • 舞伴问题
  • 银行排队模拟

定义

栈是限定仅在表尾进行插入和删除操作的线性表,遵循"先进后出(LIFO)"的原则。
栈储存结构示意图
栈的开口端被称为栈顶;相应地,封口端被称为栈底
栈顶和栈底

基本概念

进栈和出栈

  • 向栈中添加元素,此过程被称为"进栈"(入栈或压栈);
  • 从栈中提取出指定元素,此过程被称为"出栈"(或弹栈);
栈的具体实现

栈是一种 “特殊” 的线性存储结构,因此栈的具体实现有以下两种方式:

  • 顺序栈:采用顺序存储结构实现栈存储结构;
  • 链栈:采用链式存储结构实现栈结构;

两种实现方式的区别,仅限于数据元素在实际物理空间上存放的相对位置。

顺序栈
顺序栈结构体定义:
#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0
#define MAXSIZE 20 
#define MAXSIZE 20 
typedef int SElemType;
typedef int Status; 
typedef struct
{
	SElemType data[MAXSIZE];
    int top; /* 用于栈顶指针 */
}SqStack;

空栈的结构体

初始化栈
Status InitStack(SqStack *S)
{ 
    S->top = -1;
    return OK;
}
空栈判断
Status StackEmpty(SqStack S)
{ 
    if (S.top == -1)
        return TRUE;
    else
        return FALSE;
}
进栈操作
Status Push(SqStack *S,SElemType e)
{
    /* 栈满 */
    if(S->top == MAXSIZE -1) 
    {
    	return ERROR;
    }
    /* 栈顶指针增加一 */
    S->top++;		
    /* 将新插入元素赋值给栈顶空间 */		
    S->data[S->top] = e;  
    return OK;
}
出栈操作
Status Pop(SqStack *S,SElemType *e)
{ 
    if(S->top == -1)
        return ERROR;
    /* 将要删除的栈顶元素赋值给e */
    *e = S->data[S->top];	
    /* 栈顶指针减一 */
    S->top--;				
    return OK;
}
获得栈顶元素
Status GetTop(SqStack S,SElemType *e)
{
    if (S.top == -1)
        return ERROR;
    else
        *e=S.data[S.top];
    return OK;
}
链栈

在这里插入图片描述

以下链栈实现是不含头节点的。

链栈结构体定义:
/* 链栈结构 */
typedef struct StackNode
{
    SElemType data;
    struct StackNode *next;
}StackNode,*LinkStackPtr;

typedef struct
{
    LinkStackPtr top;
    int count;
}LinkStack;
初始化栈
Status InitStack(LinkStack *S)
{ 
    S->top = (LinkStackPtr)malloc(sizeof(StackNode));
    if(!S->top)
        return ERROR;
    S->top = NULL;
    S->count = 0;
    return OK;
}
空栈判断
Status StackEmpty(LinkStack S)
{ 
    if (S.count == 0)
        return TRUE;
    else
        return FALSE;
}
进栈操作

有头插法和尾插法两种,我们采用头插法。

在这里插入图片描述

Status Push(LinkStack *S,SElemType e)
{
	LinkStackPtr s = (LinkStackPtr)malloc(sizeof(StackNode)); 
    s->data = e; 
    /* 把当前的栈顶元素赋值给新结点的直接后继*/
    s->next = S->top;	
    /* 将新的结点s赋值给栈顶指*/
    S->top = s;        
    S->count++;
    return OK;
}

在这里插入图片描述

出栈操作

在这里插入图片描述

Status Pop(LinkStack *S,SElemType *e)
{ 
    LinkStackPtr p;
    if(StackEmpty(*S))
            return ERROR;
    *e = S->top->data;
    /* 将栈顶结点赋值给p,见图中③ */
    p = S->top;
    /* 使得栈顶指针下移一位,指向后一结点,见图中④ */					
    S->top = S->top->next;    
    /* 释放结点p */
    free(p);                            
    S->count--;
    return OK;
}

在这里插入图片描述

获得栈顶元素
Status GetTop(LinkStack S,SElemType *e)
{
    if (S.top == NULL)
        return ERROR;
    else
        *e = S.top->data;
    return OK;
}
栈的应用

队列

定义:队列是只允许在一端进行插入操作,另一端进行删除操作的线性表,遵循 “先进先出” 原则。

在这里插入图片描述

队列的实现

队列存储结构的实现有以下两种方式:

  • 顺序队列:在顺序表的基础上实现的队列结构;
  • 链队列:在链表的基础上实现的队列结构;
顺序队列

为了满足顺序队列中数据从队尾进,队头出且先进先出的要求,我们还需要定义两个指针(toprear)分别用于指向顺序队列中的队头元素和队尾元素。
在这里插入图片描述
当有数据元素入队时,对应的实现操作是将其存储在指针 rear 指向的数组位置,然后 rear+1;当需要队头元素出队时,仅需做 top+1 操作。

在这里插入图片描述
在这里插入图片描述
顺序队列整体后移造成的影响是:

  • 顺序队列之前的数组存储空间将无法再被使用,造成了空间浪费;
  • 如果顺序表申请的空间不足够大,则直接造成程序中数组 a 溢出,产生溢出错误;

所以,就有了顺序队列另一种实现方法:

循环队列

在这里插入图片描述
注意:顺序队列的存储状态不同,但是判断条件相同。为了对其进行区分,最简单的解决办法是:牺牲掉数组中的一个存储空间,判断数组满员的条件是:尾指针的下一个位置和头指针相遇,就说明数组满了。

  • 队列长度:(rear - front + max)%max
  • 队列空的标志:队列的头指针等于队列的尾指针(front == rear);
  • 队列满的判断:队列的头指针等于队列的尾指针((rear+1)%max==front)
顺序队列结构定义:
#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0
#define MAXSIZE 20
typedef struct
{
	QElemType data[MAXSIZE];
	int front;    	/* 头指针 */
	int rear;		/* 尾指针,若队列不空,指向队列尾元素的下一个位置 */
}SqQueue;
初始化队列
Status InitQueue(SqQueue *Q)
{
	Q->front=0;
	Q->rear=0;
	return  OK;
}
求队列长度
int QueueLength(SqQueue Q)
{
	return  (Q.rear-Q.front+MAXSIZE)%MAXSIZE;
}
入队操作
Status EnQueue(SqQueue *Q,QElemType e)
{
	if ((Q->rear+1)%MAXSIZE == Q->front)	/* 队列满的判断 */
		return ERROR;
	Q->data[Q->rear]=e;			/* 将元素e赋值给队尾 */
	Q->rear=(Q->rear+1)%MAXSIZE;/* rear指针向后移一位置, */
								/* 若到最后则转到数组头部 */
	return  OK;
}
出队操作
Status DeQueue(SqQueue *Q,QElemType *e)
{
	if (Q->front == Q->rear)			/* 队列空的判断 */
		return ERROR;
	*e=Q->data[Q->front];				/* 将队头元素赋值给e */
	Q->front=(Q->front+1)%MAXSIZE;	/* front指针向后移一位置, */
									/* 若到最后则转到数组头部 */
	return  OK;
}
获取队头元素
Status GetHead(SqQueue Q,QElemType *e)
{
	if(Q.front == Q.rear) /* 队列空 */
		return ERROR;
	*e = Q.data[Q.front];
	return OK;
}
栈式队列

以下代码实现是含有头节点的。

栈式队列的结构定义:
typedef struct QNode	/* 结点结构 */
{
   QElemType data;
   struct QNode *next;
}QNode,*QueuePtr;

typedef struct			/* 队列的链表结构 */
{
   QueuePtr front,rear; /* 队头、队尾指针 */
}LinkQueue;

链式队列结构

初始化链式队列

空队列

Status InitQueue(LinkQueue *Q)
{ 
	Q->front = Q->rear = (QueuePtr)malloc(sizeof(QNode));
	if(!Q->front)
		exit(OVERFLOW);
	Q->front->next = NULL;
	return OK;
}
链式队列入队操作

链队队列中,当有新的数据元素入队,只需进行以下 3 步操作:

  • 将该数据元素用节点包裹,例如新节点名称为 elem;
  • 与 rear 指针指向的节点建立逻辑关系,即执行 rear->next = elem;
  • 最后移动 rear 指针指向该新节点,即 rear = elem;

在这里插入图片描述

Status EnQueue(LinkQueue *Q,QElemType e)
{ 
	QueuePtr s = (QueuePtr)malloc(sizeof(QNode));
	if(!s) /* 存储分配失败 */
		exit(OVERFLOW);
	s->data = e;
	s->next = NULL;
	/* 把拥有元素e的新结点s赋值给原队尾结点的后继*/
	Q->rear->next = s;	
	/* 把当前的s设置为队尾结点,rear指向s */
	Q->rear = s;		
	return OK;
}
链式队列出队操作

链式队列中队头元素出队,需要做以下 3 步操作:

  • 通过 front 指针直接找到队头节点,创建一个新指针 p 指向此即将出队的节点;
  • 将 p 节点(即要出队的队头节点)从链表中摘除;
  • 释放节点 p,回收其所占的内存空间;

在这里插入图片描述

Status DeQueue(LinkQueue *Q,QElemType *e)
{
	QueuePtr p;
	if(Q->front == Q->rear)
		return ERROR;
	/* 将欲删除的队头结点暂存给p,见图中① */
	p = Q->front->next;		
	/* 将欲删除的队头结点的值赋值给e */
	*e = p->data;				
	/* 将原队头结点的后继p->next赋值给头结点后继,见图中② */
	Q->front->next = p->next;
	/* 若队头就是队尾,则删除后将rear指向头结点,见图中③ */
	if(Q->rear == p)		
		Q->rear = Q->front;
	free(p);
	return OK;
}
  • 11
    点赞
  • 40
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值