线性表 —— 栈和队列

本文详细介绍了栈和队列这两种重要的数据结构,包括它们的定义、特点及实际应用。栈遵循后进先出的原则,常用于括号匹配、表达式求值等问题;队列遵循先进先出原则,适用于处理类似排队的问题。同时,文章讨论了顺序栈、链式栈、循环队列和链队的操作,并阐述了栈在递归调用中的作用。此外,还提到了栈和队列在进制转换、舞伴问题等场景中的应用。
摘要由CSDN通过智能技术生成

栈和队列

栈和队列的定义与特点

栈和队列是限定插入和删除只能在表的“端点”进行的线性表
在这里插入图片描述
栈:先进后出,后进先出,eg:数值转换、表达式求值、括号匹配检验、八皇后问题、行编辑程序、函数调用、迷宫求解、递归调用的实现
队列:先进先出,处理程序设计中类似排对问题的有用工具
在这里插入图片描述

案例引入

  1. 进制转换
    转换法则:除以d倒取余
  2. 括号匹配
    在这里插入图片描述
    检验:①可以利用一个栈结构保存每个出现的左括号,当遇到右括号时,从栈中弹出左括号,检查匹配情况
    ②检验过程中,得出不匹配结论的情况有:
    1)当遇到一个右括号时,栈已空,说明右括号多于左括号
    2)从栈中弹出的左括号与当前检验的右括号类型不同,说明出现了括号交叉情况
    3)算术表达式输入完毕,但栈中还有没有匹配的括号,说明左括号多于右括号
  3. 表达式求值
    算法之一:由运算符的优先级确定运算顺序对表达式求值的算法,——算符优先算法
    在这里插入图片描述
    在这里插入图片描述
  4. 舞伴问题

栈S

仅在一端通常是表尾进行插入和删除的线性表,又称为后进先出(Last in First out)的线性表,简称LIFO结构
表尾称为栈顶Top,表头称为栈底Base/Bottom
Push:插入到表尾:入栈 Pop:删除:出栈
在这里插入图片描述
不可能出现cab的情况:因为若c先出栈,证明a、b已经在栈中
可能出现abc:a入栈,a出栈,b入栈,b出栈,c入栈,c出栈

  1. 逻辑结构:与线性表相同仍为一对一关系
  2. 存储结构:顺序栈或者链式栈,但以顺序栈更常见
  3. 运算规则:只能在栈顶进行插入删除

在这里插入图片描述

基本操作
  1. IntiStack(&S) //初始化操作:构造一个空栈S
  2. DestroyStack(&S) //销毁栈操作:S已存在→S被销毁
  3. StackEmpty(S) //判空操作:S已存在→返回对应结果
  4. StackLength(S) //求长度:S已存在→返回长度
  5. GetTop(S,&e) //取栈顶元素:S已存在且非空→用e返回栈顶元素
  6. ClearStack(&S) //栈置空操作:S已存在→将S清空为空栈
  7. Push(&S,e) //入栈操作:S已存在→插入元素e为新的栈顶元素
  8. Pop(&S,&e) //出栈操作:S已存在且非空→删除栈顶元素,并用e返回其值
顺序栈



使用数组作为顺序栈的特点:简单、方便,但容易产生溢出(数组大小固定)

  1. 定义
    typedef struct {
    SElemType *base; //栈底指针
    SElemType *top;//栈顶指针
    int stacksize;//栈可用最大容量
    }SqStack;
  2. 计算栈中的元素个数:top - base;
  3. IntiStack(&S):
S.base  = (SElemType*)malloc(sizeof(SElemType)*MAXSIZE);
if (!S.base) return OVERFLOW;
S.top = S.base;
S.stacksize = MAXSIZE;
  1. StackEmpty(S): return S.base == S.top?TRUE:FALSE;
  2. StackLength(S):return S.top-S.base
  3. ClearStack(&S):不管内存中里面包含了什么,默认为空:if (S.base) S.top = S.base;
  4. DestroyStack(&S):将所分配的内存都释放了
if (S.base) {
	free S.base;
	S.stacksize = 0;
	S.base = S.top = NULL; //因为释放后仍然可以读取出S.base内所存放的地址,再次进行操作会影响结果
}
return OK;
  1. Push(&S,e)
    算法:①判断是否栈满,若满则出错,上溢
    ②将e压入栈顶
    ③栈顶指针+1
if (S.top - S.base == S.stacksize) return ERROR;
*S.top++ = e; //*S.top = e;S.top++;
return OK;
  1. Pop(&S, &e)
    算法:①判断是否栈空,若空则出错,下溢
    ②获取栈顶元素e
    ③栈顶指针-1 //默认不用删除数据,将S.top-1即可表示已经删除元素
if (S.top == S.base) return ERROR;
e = *S.top--; //e = *S.top; S.top--;
return OK;
链式栈

链栈是运算受限的单链表,只能在链表头部进行操作

  1. 定义
    typedef struct StackNode{
    SElemType data;
    struct StackNode * next;
    }StackNode, *LinkStack;
    LinkStack S;
    在这里插入图片描述
  2. IntiStack(&S)
S = NULL;
return OK;
  1. StackEmpty(S):if (S == NULL) /if(!S)
  2. Push(&S,e)
StackNode *p = (StackNode*)malloc(sizeof(StackNode));
p->data = e;
p->next = S;
S = p;
return OK;
  1. Pop(&S,&e)
if (S == NULL) return ERROR;
e = S->data;
p = S; //便于释放内存
S = S->next;
free p;
return OK;
  1. GetTop(S) if (S!=NULL) return S->data;

队列Q

在表尾插入,在表头删除的线性表,先进先出(First in First out),FIFO结构,头删尾插

  1. 逻辑结构:与线性表相同,仍为一对一的关系
  2. 存储结构:顺序队或链队,以循环队列更常见
  3. 运算规则:只能在队首和队尾运算
  4. 定义
    typedef struct {
    QElemType *base;
    int front; //头指针,但并非指针类型
    int rear; //尾指针,但并非指针类型
    }SqQueue;
  5. 解决假上溢方法:利用模运算(%)
    插入元素:Q.base[Q.rear] = x; Q.rear = (Q.rear+1)%MAXQSIZE;
    删除元素:x = Q.base[Q.front]; Q.front = (Q.front+1) % MAXQSIZE;
    循环队列:循环使用为队列分配的存储空间
  6. 判断队空和队满:①另设一个标志以区别队空、队满 ②另设一个变量,记录元素个数 ③少用一个元素的空间
  7. 队空:Q.front = Q.rear 队满:(Q.rear+1) % MAXQSIZE == Q.front
循环队列
  1. IntiQueue(&Q)
Q.base = (QElemType*)malloc(sizeof(QElemType) * MAXQSIZE);
if (!Q.base) exit OVERFLOW;
Q.front  = Q.rear = 0;
return OK;
  1. QueueLength(Q) return (Q.rear-Q.front+MAXQSIZE)%MAXSIZE;
  2. Push(&Q, e)
if ((Q.rear+1)%MAXQSIZE == Q.front) return ERROR;
Q.base[Q.rear] = x; 
Q.rear = (Q.rear+1)%MAXQSIZE;
return OK;
  1. Pop(&Q, &e)
if (Q.rear == Q.front) return ERROR;
e = Q.base[Q.front];
Q.front = (Q.front+1)%MAXQSIZE;
return OK;
  1. GetHead(Q)
if (Q.rear == Q.front) return ERROR;
return Q.base[Q.front];
链队

若用户无法估计所用队列的长度,则宜采用链队列
其中Q.front指向一个空的头结点,Q.rear指向最后一个元素

  1. 定义
    typedef struct Qnode {
    QElemType data;
    struct Qnode * next;
    }QNode, *QueuePtr;
    typedef struct {
    QueuePtr front;
    QueuePtr rear;
    }LinkQueue;
  2. 各个操作
    在这里插入图片描述
  3. IntiQueue(&Q)
Q.front = Q.rear = (QueuePtr*)malloc(sizeof(QNode));
if (!Q.front) exit OVERFLOW;
Q.front->next = NULL;
  1. DestroyQueue(&Q)
while (Q.front) {
p = Q.frot->next; //或者直接利用Q.rear这个指针作为变量p...
free (Q.front);
Q.front = p;
}
return OK;
  1. Push(&Q, e)
p = (QueuePtr)malloc(sizeof (QNode));
if (!p) exit OVERFLOW;
p->data = e; p->next = NULL;
Q.rear->next = p;
Q.rear = p;
return OK;
  1. Pop(&Q, &e)
if (Q.front == Q.rear) return ERROR;
p = Q.front->next;
e = p->data;
Q.front->next = p->next;
if (Q.rear == p) Q.rear = Q.front; //考虑若是删除结点p恰好是最后一个元素
free (p);
return OK;
  1. GetHead(Q, e)
if (Q.rear == Q.front) return ERROR;
e = Q.front->next->data;
return OK;

栈与递归

  1. 递归:若一个对象部分地包含它自己或者它自己给自己定义,称这个对象是递归的
  2. 若一个过程直接或间接地调用自己,则称这个过程是递归的过程,
  3. 常常运用递归的方法
    ①递归定义的数学函数:递归求n的阶乘,斐波那契序列
    ②具有递归特性的数据结构:二叉树、广义表
    ③可递归求解的问题:迷宫问题、Hanoi塔问题
  4. 递归问题——用分治法求解
    分治法:对于一个较为复杂的问题,能够分解成几个相对简单的且解法相同或类似的子问题来求解
    必备的三个条件:
    1)能将一个问题转变成一个新问题下,而新问题与原问题的解法相同或类同,不同的是处理的对象,且这些处理对象的变化是有规律的
    2)可以通过上述转化而使问题简化
    3)必须有一个明确的递归出口,或称递归的边界
  5. 分治法求解递归问题算法的一般形式:
    在这里插入图片描述
  6. 多个函数构成嵌套调用时
    int main(void) {…y = f(3)…} double f(int n) {…z = p(3.5, 2)…} double (double x, int y) {…}
    遵循后调用的先返回
    在这里插入图片描述
    递归:优点:结构清晰,程序易读 缺点:每次调用要生成工作记录,保存状态信息,入栈;返回时要出栈,回复状态信息,时间开销大
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值