笔记_408_数据结构_03. 栈和队列

03. 栈和队列

3.1 栈

3.1.1 栈的基本概念

在这里插入图片描述

  • 栈(Stack)是只允许在表尾进行插入或删除操作的线性表
  • 【常考题型:给出进栈顺序,有哪些合法的出栈顺序?】
    • n n n个不同元素进栈,出栈元素不同排列的个数为 1 n + 1 C 2 n n \frac{1}{n+1}C^n_{2n} n+11C2nn

3.1.2 栈的顺序存储实现

在这里插入图片描述

  • 使用共享栈的好处:节省存储空间,降低发生上溢的可能
# define MaxSize 10
typedef struct{
    ElemType data[MaxSize];
    int top;
}SqStack;
// 初始化栈
bool InitStack(SqStack &S){
    S.top = -1;				// S.top = 0;
}
// 判空
bool StackEmpty(SqStack S){
    if(S.top == -1)	return true;	// S.top == 0;
    else	return false;
}
// 新元素入栈
bool Push(SqStack &S,ElemType x){
    if(S.top == MaxSize-1)		// s.top == MaxSize
        return false;
    S.data[++S.top] = x;		// S.data[S.top++] = x;
    return true;
}
// 出栈
bool Pop(SqStack &S,ElemType &e){
    if(S.top == -1)
        return false;
    x = S.data[S.top--];		// x = S.data[--S.top];
    return true;
}

3.1.3 栈的链式存储实现

在这里插入图片描述

  • 带头结点
// 头插法
bool LiStack_Head(LiStack &S){
    // 初始化
    S = (Linknode *)malloc(sizeof(Linknode));
    if(S == NULL)	return false;
    S->next = NULL;
    Linknode *p;
    int x;
    while(scanf("%d",x)){
        p = (Linknode *)malloc(sizeof(Linknode));
        p->data = x;
        p->next = S->next;
        S->next = p;
    }
    return true;
}
// push
bool PushLiStack(LiStack &S,int e){
    Linknode *p = (Linknode *)malloc(sizeof(Linknode));
    if(p == NULL)	return false;
    p->data = e;
    p->next = S->next;
    S->next = p;
    return true;
}
// pop
bool PopLiStack(LiStack &S,int &e){
    if(S->next == NULL)
        return false;
    Linknode *p = S->next;
    e = p->data;
    S->next = p->next;
    free(p);
    return true;
}
// empty
bool EmptyLiStack(LiStack S){
    if(S->next == NULL)	return true;
    else	retun false;
}

3.2 队列

3.2.1 队列的基本概念

在这里插入图片描述

  • 队列(Queue)是只允许在一端进行插入,在另一端删除的线性表

3.2.2 队列的顺序实现

在这里插入图片描述

#define MaxSzie 10
typedef struct{
    int data[MaxSize];
    int Front,Rear; 
}SqQueue;
// 初始化
void InitQueue(SqQueue &Q){
    Q.Rear = Q.Front = 0;		// ①
}
// 判空
bool QueueEmpty(SqQueue Q){
    if(Q.Rear == Q.Front)	return true;
    else	return false;
}
// 入队
bool EnQueue(SqQueue &Q,int x){
    // 判满
    if((Q.Rear + 1) % MaxSize == Q.Front) // 法一
        return false;
    Q.data[Q.Rear] = x;
    Q.Rear = (Q.Rear + 1) % MaxSize;
    return true; 
}
// 出队
bool DeQueue(SqQueue &Q,int x){
    if(Q.Rear == Q.Front)   // 判空
        return false;
    x = Q.data[Q.Front];
    Q.Front = (Q.Front + 1) % MaxSize;
    return true;
}
  • 判断队列已满 / 已空 / 元素个数

    • 法一:牺牲一个存储单元

      • (Q.Rear + 1) % MaxSize == Q.Front // 满
        
      • Q.Rear == Q.Front // 空
        
      • (Q.Rear + MaxSize - Q.Front)%MaxSize // 队列元素个数
        
    • 法二:size变量

      • Q.size == MaxSize // 满
        
      • Q.size == 0; // 空
        
      • size // 队列元素个数
        
    • 法三:tag — 每次删除成功令tag=0,插入成功令tag=1

      • Q.Front == Q.Rear && tag == 1 // 满
        
      • Q.Front == Q.Rear && tag == 0 // 空
        

3.2.3 队列的链式实现

在这里插入图片描述

  • 带头结点
// 初始化(带头结点)
void InitQueue(LinkQueue &Q){
    // front 和 rear 都指向头结点
    Q.Front = Q.Rear = (LinkNode *)malloc(sizeof(LinkNode));
    Q.Front->next = NULL;
}
// 判空
bool IsEmpty(LinkQueue Q){
    if(Q.Front == Q.Rear)	return true;
    else	return false;
}
// 入队
void EnQueue(LinkQueue &Q,ElemType x){
    LinkNode *s = (LinkNode *)malloc(sizeof(LinkNode));
    s->data = x;
    s->next = NULL;
    Q.Rear->next = s;
    Q.Rear = s;
}
// 出队
bool DeQueue(LinkQueue &Q,ElemType &x){
    if(Q.Front == Q.Rear)
        return false;
    LinkNode *p = Q.Front->next;
    x = p->data;
    Q.Front->next = p->next;
    if(Q.Rear == p){		// 若是最后一个结点出队
        Q.Rear = Q.Front;
    }
    free(p);
    return true;
}
  • 不带头结点
// 初始化
void InitQueue(LinkQueue &Q){
    // front 和 rear 都指向NULL
    Q.Front = NULL;
    Q.Rear = NULL;
}
// 判空
bool IsEmpty(LinkQueue Q){
    if(Q.Front == NULL)		return true;
    elsec	return false;
}
// 入队
void EnQueue(LinkQueue &Q,ElemType x){
    LinkNode *s = (LinkNode *)malloc(sizeof(LinkNode));
    s->data = x;
    s->next = NULL;
    if(Q.Front == NULL){
        Q.Front = s;
        Q.Rear = s;
    }else{
        Q.Rear->next = s;
    	Q.Rear = s;
    }
}
// 出队
bool DeQueue(LinkQueue &Q,ElemType &x){
    if(Q.Front == NULL)
        return false;
    LinkNode *p = Q.Front;	// p指向此次出队的元素
    x = p->data;
    Q.Front = p->next;
    if(Q.Rear == p){		// 若是最后一个结点出队
        Q.Rear = NULL;
        Q.Front = NULL;
    }
    free(p);
    return true;
}

3.2.4 双端队列

在这里插入图片描述

给输入序列判断输出序列是否合法

  • 输入受限的双端队列(只有一端可以输入):队列中填输入顺序,看受否存在对应的输出序列
  • 输出受限的双端队列(只有一端可以输出):队列中填输出顺序,看是否存在正确的输入顺序

3.3 应用

3.3.1 栈 - 括号匹配

用栈实现括号匹配:依次扫描所有字符,遇到左括号入栈,遇到右括号则弹出栈顶元素检查是否匹配。

  • 匹配失败情况:
    • 左括号单身
    • 右括号单身
    • 左右括号不匹配
# define MaxSize 10
typedef struct{
    ElemType data[MaxSize];
    int top;
}SqStack;
// 初始化栈
void InitStack(SqStack &S);
// 判空
void StackEmpty(SqStack S);
// 入栈
bool Push(SqStack &S, char x);
// 出栈
bool Pop(SqStack &S,char &x);
// 括号匹配
bool bracketCheck(char str[],int length){
    SqStack S;
    InitStack(S);
    for(int i=0;i<length;i++){
        if(str[i] == '('||str[i] == '['||str[i] == '{'){
            Push(S,str[i]);
        }else{
            if(StackEmpty(S))
                return false;
            char topElem;
            Pop(S,topElem);
            if(str[i] == ']' && topElem != '[')
                return false;
            if(str[i] == ')' && topElem != '(')
                return false;
            if(str[i] == '}' && topElem != '{')
                return false;
        }
    }
    return StackEmpty(S);
}

3.3.2 栈 - 表达式

在这里插入图片描述
在这里插入图片描述

  • 中缀表达式转后缀表达式

    • 手算

      1. 确定中缀表达式中各个运算符的运算顺序
      2. 选择下一个运算符,按照**「左操作数 右操作数 运算符」**的方式组合成一个新的操作数
      3. 如果还有运算符没被处理,就继续②

      【“左优先”原则】:只要左边的运算符能先计算,就优先算左边的

    • 机算(栈)

      1. 初始化一个栈,用于保存暂时还不能确定运算顺序的运算符

      2. 从左到右处理各个元素,直到末尾。可能遇到三种情况:

        ① 遇到操作数直接加入后缀表达式。

        ② 遇到界限符

        • 遇到 “(” 直接入栈;

        • 遇到 “)” 则依次弹出栈内运算符并加入后缀表达式,直到弹出 “(” 为止。

          【注意】“(” 不加入后缀表达式。

        ③ 遇到运算符。依次弹出栈中优先级高于或等于当前运算符的所有运算符,并加入后缀表达式,若碰到 “(” 或栈空则停止。之后再把当前运算符入栈

      3. 按上述方法处理完所有字符后,将栈中剩余运算符依次弹出,并加入后缀表达式。

  • 后缀表达式的计算:

    • 手算

      从左往右扫描,每遇到一个运算符,就让运算符前面最近的两个操作数执行对应运算,合体为一个操作数

    • 机算(栈)

      1. 从左往右扫描下一个元素,直到处理完所有元素
      2. 若扫描到操作数则压入栈,并回到①;否则执行③
      3. 若扫描到运算符,则弹出两个栈顶元素,执行相应运算,运算结果压回栈顶,回到①
  • 中缀表达式转前缀表达式

    • 手算

      1. 确定中缀表达式中各个运算符的运算顺序
      2. 选择下一个运算符,按照**「运算符 左操作数 右操作数」**的方式组合成一个新的操作数
      3. 如果还有运算符没被处理,就继续②

      【“右优先”原则】:只要右边的运算符能先计算,就优先算右边的

  • 前缀表达式的计算:

    • 机算
      1. 从右往左扫描下一个元素,直到处理完所有元素
      2. 若扫描到操作数则压入栈,并回到①;否则执行③
      3. 若扫描到运算符,则弹出两个栈顶元素,执行相应运算,运算结果压回栈顶,回到①
  • 中缀表达式的计算(机算)

    初始化两个栈,操作数栈和运算符栈(中缀转后缀+后缀表达式求值)

    ① 若扫描到操作数,压入操作数栈

    ② 若扫描到运算符或界限符,则按照“中缀转后缀”相同的逻辑压入运算符栈(期间也会弹出运算符,每当弹出一个运算符时,就需要再弹出两个操作数栈的栈顶元素并执行相应运算,运算结果再压回操作数栈)

3.3.3 栈在递归调用中的应用

  • 函数调用的特点:最后被调用的函数最先执行结束(LIFO)

  • 函数调用时,需要用一个“函数调用栈”存储:

    1. 调用返回地址
    2. 实参
    3. 局部变量
  • 递归调用时,函数调用栈可称为**“递归工作栈”**

    • 每进入一层递归,就将递归调用所需信息压入栈顶
    • 每退出一层递归,就从栈顶弹出相应信息
  • 缺点:效率低,太多层递归可能会导致栈溢出;可能包含很多重复计算

  • 可以自定义栈将递归算法改造成非递归算法

3.3.4 队列的应用

  • 树的层次遍历
  • 图的广度优先遍历
  • 在OS中的应用
    • 先来先服务

3.4 特殊矩阵的压缩存储

在这里插入图片描述
在这里插入图片描述

题目未特别说明:矩阵下标默认从 1 开始,数组下标默认从 0 开始

1. 对称矩阵

  • 压缩存储策略

    • 只存储主对角线+下三角区(或主对角线+上三角区)
    • 行优先 / 列优先原则将各元素存入一维数组中
  • 矩阵下标 a i j a_{ij} aij → 一维数组下标(行优先)
    k = { i ( i − 1 ) 2 + j − 1 ,   i > = j ( 下三角区和主对角线元素 ) j ( j − 1 ) 2 + i − 1 ,   i < j ( 上三角区元素 a i j = a j i ) k= \begin{cases} \frac{i(i-1)}{2}+j-1,\ i>=j(下三角区和主对角线元素) \\ \frac{j(j-1)}{2}+i-1,\ i<j (上三角区元素a_{ij}=a_{ji})\\ \end{cases} k={2i(i1)+j1, i>=j(下三角区和主对角线元素)2j(j1)+i1, i<j(上三角区元素aij=aji)

2. 三角矩阵

在这里插入图片描述

下三角矩阵
  • 压缩存储策略:按行优先 / 列优先原则将橙色区元素存入一维数组中。并在最后一个位置存储常量c

  • 矩阵下标 a i j a_{ij} aij -> 一维数组下标(行优先)

k = { i ( i − 1 ) 2 + j − 1 ,   i > = j ( 下三角区和主对角线元素 ) n ( n + 1 ) 2 ,   i < j ( 上三角区元素 ) k= \begin{cases} \frac{i(i-1)}{2}+j-1,\ i>=j(下三角区和主对角线元素) \\ \frac{n(n+1)}{2},\ i<j (上三角区元素)\\ \end{cases} k={2i(i1)+j1, i>=j(下三角区和主对角线元素)2n(n+1), i<j(上三角区元素)

上三角矩阵
  • 压缩存储策略:按行优先 / 列优先原则将绿色区元素存入一维数组中。并在最后一个位置存储常量c

  • 矩阵下标 a i j a_{ij} aij -> 一维数组下标(行优先)

k = { ( i − 1 ) ( 2 n − i + 2 ) 2 + ( j − i ) ,   i < = j ( 下三角区和主对角线元素 ) n ( n + 1 ) 2 ,   i < j ( 上三角区元素 ) k= \begin{cases} \frac{(i-1)(2n-i+2)}{2}+(j-i),\ i<=j(下三角区和主对角线元素) \\ \frac{n(n+1)}{2},\ i<j (上三角区元素)\\ \end{cases} k={2(i1)(2ni+2)+(ji), i<=j(下三角区和主对角线元素)2n(n+1), i<j(上三角区元素)

3. 三对角矩阵 / 带状矩阵

在这里插入图片描述

  • ∣ i − j ∣ > 1 |i-j|>1 ij>1 时,有 a i j = 0 ( 1 < = i , j < = n ) a_{ij}=0(1<=i,j<=n) aij=0(1<=i,j<=n)

  • 压缩存储策略:按行优先(或列优先)原则,只存储带状部分

  • 矩阵下标 a i j a_{ij} aij -> 一维数组下标(行优先)

    • i − 1 i-1 i1 行共 3 ( i − 1 ) − 1 3(i-1)-1 3(i1)1 个元素
    • a i j a_{ij} aij i i i 行第 j − i + 2 j-i+2 ji+2 个元素
    • a i j a_{ij} aij 是第 2 i + j − 2 2i+j-2 2i+j2 个元素
      k = 2 i + j − 3 k = 2i+j-3 k=2i+j3
  • 一维数组下标-> 矩阵下标 a i j a_{ij} aij (行优先)
    i = ⌊ ( k + 1 ) / 3 + 1 ⌋ j = k − 2 i + 3 i = \lfloor(k+1)/3+1\rfloor\\ j=k-2i+3 i=⌊(k+1)/3+1j=k2i+3

4. 稀疏矩阵
  • 压缩存储策略一:顺序存储——三元组<行,列,值>
  • 压缩存储策略二:链式存储——十字链表法
    在这里插入图片描述
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ll._.ll

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值