• 第三章 栈和队列【24王道数据结构笔记】


    1.栈

    1.1 栈的基本概念

    • 只允许在一端(栈顶top)进行插入或删除操作的受限的线性表。
    • 后进先出(Last In First Out)LIFO。或者说先进后出FILO。

      

    进栈顺序:a1 > a2 > a3 > a4 > a5
    出栈顺序:a5 > a4 > a3 > a2 > a1 

     1.2 栈的基本操作


    InitStack(&S):初始化栈。构造一个空栈 S,分配内存空间。
    DestroyStack(&S):销毁栈。销毁并释放栈 S 所占用的内存空间。
    Push(&S, x):进栈。若栈 S 未满,则将 x 加入使其成为新的栈顶元素。
    Pop(&S, &x):出栈。若栈 S 非空,则弹出(删除)栈顶元素,并用 x 返回。
    GetTop(S, &x):读取栈顶元素。若栈 S 非空,则用 x 返回栈顶元素。
    StackEmpty(S):判空。断一个栈 S 是否为空,若 S 为空,则返回 true,否则返回 false。

    1.2.1 栈的顺序存储实现

     【顺序栈的定义】

    1. #define MaxSize 10 //定义栈中元素的最大个数
    2. typedef struct{
    3. ElemType data[MaxSize]; //静态数组存放栈中元素
    4. int top; //栈顶元素
    5. }SqStack;
    6. void testStack(){
    7. SqStack S; //声明一个顺序栈(分配空间)
    8. //连续的存储空间大小为 MaxSize*sizeof(ElemType)
    9. }

     【顺序栈的初始化

    1. #define MaxSize 10
    2. typedef struct{
    3. ElemType data[MaxSize];
    4. int top;
    5. }SqStack;
    6. // 初始化栈顶为-1,栈顶指针指向栈顶
    7. void InitStack(SqStack &S){
    8. S.top = -1; //初始化栈顶指针
    9. }
    10. // 判断栈是否为空
    11. bool StackEmpty(SqStack S){
    12. if(S.top == -1)
    13. return true;
    14. else
    15. return false;
    16. }
    17. // 初始化栈顶为0,栈顶指针指向栈顶的下一个空位
    18. void InitStack(SqStack &S){
    19. S.top = 0; //初始化栈顶指针
    20. }
    21. // 判断栈是否为空
    22. bool StackEmpty(SqStack S){
    23. if(S.top == 0)
    24. return true;
    25. else
    26. return false;
    27. }

     【顺序栈的入栈出栈】

    1. 初始化为-1
    2. // 新元素进栈
    3. bool Push(SqStack &S, ElemType x){
    4. if(S.top == MaxSize - 1) // 判断栈是否已满
    5. return false;
    6. S.data[++S.top] = x;
    7. return true;
    8. }
    9. // 出栈
    10. bool Pop(SqStack &x, ElemType &x){
    11. if(S.top == -1) // 判断栈是否为空
    12. return false;
    13. x = S.data[S.top--];
    14. return true;
    15. }
    16. 初始化为0
    17. // 新元素进栈
    18. bool Push(SqStack &S, ElemType x){
    19. if(S.top == MaxSize) // 判断栈是否已满
    20. return false;
    21. S.data[S.top++] = x;
    22. return true;
    23. }
    24. // 出栈
    25. bool Pop(SqStack &x, ElemType &x){
    26. if(S.top == 0) // 判断栈是否为空
    27. return false;
    28. x = S.data[--S.top];
    29. return true;
    30. }

    【读取栈顶元素】 

    1. // 读栈顶元素
    2. 初始化为-1
    3. bool GetTop(SqStack S, ElemType &x){
    4. if(S.top == -1) 先判空,非空读取才有意义
    5. return false;
    6. x = S.data[S.top];
    7. return true;
    8. }
    9. 初始化为-1
    10. bool GetTop(SqStack S, ElemType &x){
    11. if(S.top == 0)
    12. return false;
    13. x = S.data[S.top-1];
    14. return true;
    15. }

     【读取栈的长度】 

    1. // 获取当前栈长
    2. 当初始化为-1
    3. int GetSize(SqStack S){
    4. return S.top + 1;
    5. }
    6. 当初始化为0
    7. int GetSize(SqStack S){
    8. return S.top;
    9. }

    共享栈(两个栈共享同一片空间)】

    • 共享栈--特殊的顺序栈
    • 将栈底设计在共享空间的两端,栈顶向中间靠拢
    1. #define MaxSize 10 //定义栈中元素的最大个数
    2. typedef struct{
    3. ElemType data[MaxSize]; //静态数组存放栈中元素
    4. int top0; //0号栈栈顶指针
    5. int top1; //1号栈栈顶指针
    6. }ShStack;
    7. //初始化栈
    8. void InitSqStack(ShStack &S){
    9. S.top0 = -1; //初始化栈顶指针
    10. S.top1 = MaxSize;
    11. }

    1.2.2 栈的链式存储

    【链栈的定义】

    • 定义:采用链式存储的栈称为链栈。
    • 优点:链栈的优点是便于多个栈共享存储空间和提高其效率,且不存在栈满上溢的情况。
    • 特点:进栈和出栈都只能在栈顶一端进行(链头作为栈顶)

    链表的头部作为栈顶,意味着:

    • 1. 在实现数据"入栈"操作时,需要将数据从链表的头部插入;
    • 2. 在实现数据"出栈"操作时,需要删除链表头部的首元节点;

    因此,链栈实际上就是一个只能采用头插法插入或删除数据的链表;
    栈的链式存储结构可描述为:

    【链栈的定义】

    1. typedef struct Linknode{
    2. ElemType data; //数据域
    3. Linknode *next; //指针域
    4. }Linknode,*LiStack;
    5. void testStack(){
    6. LiStack L; //声明一个链栈
    7. }

    【链栈的初始化】

    1. typedef struct Linknode{
    2. ElemType data;
    3. Linknode *next;
    4. }Linknode,*LiStack;
    5. // 初始化栈
    6. bool InitStack(LiStack &L){ // 生成虚拟头节点,并将其next指针置空
    7. L = (Linknode *)malloc(sizeof(Linknode));
    8. if(L == NULL)
    9. return false;
    10. L->next = NULL;
    11. return true;
    12. }
    13. // 判断栈是否为空
    14. bool isEmpty(LiStack &L){
    15. if(L->next == NULL)
    16. return true;
    17. else
    18. return false;
    19. }

    【入栈出栈】

    1. // 新元素入栈
    2. bool pushStack(LiStack &L,ElemType x){
    3. Linknode *s = (Linknode *)malloc(sizeof(Linknode));
    4. if(s == NULL)
    5. return false;
    6. s->data = x;
    7. // 头插法
    8. s->next = L->next;
    9. L->next = s;
    10. return true;
    11. }
    12. // 出栈
    13. bool popStack(LiStack &L, int &x){
    14. // 栈空不能出栈
    15. if(L->next == NULL)
    16. return false;
    17. Linknode *s = L->next;
    18. x = s->data;
    19. L->next = s->next;
    20. free(s);
    21. s = NULL;
    22. return true;
    23. }

     2. 队列

    2.1 队列的基本概念

    • 只允许在表的一端(队尾)插入,表的另一端(队头)进行删除操作的受限的线性表。
    • 特点:先进先出(先入队的元素先出队)、FIFO(First In First Out),后入后出LILO。
     
    

    2.2 队列的基本操作


    InitQueue(&Q):初始化队列,构造一个空队列Q。
    QueueEmpty(Q):判队列空,若队列Q为空返回true,否则返回false。
    EnQueue(&Qx):入队,若队列Q未满,则将x加入使之成为新的队尾。
    DeQueue(&Q&x):出队,若队列Q非空,则删除队头元素,并用x返回。
    GetHead(Q&x):读队头元素,若队列Q非空则用x返回队头元素。
    ClearQueue(&Q):销毁队列,并释放队列Q占用的内存空间。
    【队列的顺序存储实现 】

    队头指针:指向队头元素
    队尾指针:指向队尾元素或者队尾的下一个位置

    【顺序队列的定义】

    1. #define MaxSize 10; //定义队列中元素的最大个数
    2. typedef struct{
    3. ElemType data[MaxSize]; //用静态数组存放队列元素
    4. int front, rear; //队头指针和队尾指针
    5. }SqQueue;
    6. void test{
    7. SqQueue Q; //声明一个队列
    8. }

    顺序队列的初始化】

    1. #define MaxSize 10;
    2. typedef struct{
    3. ElemType data[MaxSize];
    4. int front, rear;
    5. }SqQueue;
    6. // 初始化队列
    7. void InitQueue(SqQueue &Q){
    8. // 初始化时,队头、队尾指针指向0
    9. // 队尾指针指向的是即将插入数据的数组下标
    10. // 队头指针指向的是队头元素的数组下标
    11. Q.rear = Q.front = 0;
    12. }
    13. // 判断队列是否为空
    14. bool QueueEmpty(SqQueue Q){
    15. if(Q.rear == Q.front)
    16. return true;
    17. else
    18. return false;
    19. }

    【入队出队(循环队列)】

    1. // 新元素入队
    2. bool EnQueue(SqQueue &Q, ElemType x){
    3. // 如果队列已满直接返回
    4. if((Q.rear+1)%MaxSize == Q.front) //牺牲一个单元区分队空和队满
    5. return false;
    6. Q.data[Q.rear] = x;
    7. Q.rear = (Q.rear+1)%MaxSize;
    8. return true;
    9. }
    10. // 出队
    11. bool DeQueue(SqQueue &Q, ElemType &x){
    12. // 如果队列为空直接返回
    13. if(Q.rear == Q.front)
    14. return false;
    15. x = Q.data[Q.front];
    16. Q.front = (Q.front+1)%MaxSize;
    17. return true;
    18. }
    • 循环队列不能直接使用Q.rear == Q.front作为判空的条件,因为当队列已满时也符合该条件,会与判空发生冲突!

    解决方法一:牺牲一个单元来区分队空和队满,即将(Q.rear+1)%MaxSize == Q.front作为判断队列是否已满的条件。(主流方法)
    解决方法二:设置 size 变量记录队列长度。

    1. #define MaxSize 10;
    2. typedef struct{
    3. ElemType data[MaxSize];
    4. int front, rear;
    5. int size;
    6. }SqQueue;
    7. // 初始化队列
    8. void InitQueue(SqQueue &Q){
    9. Q.rear = Q.front = 0;
    10. Q.size = 0;
    11. }
    12. // 判断队列是否为空
    13. bool QueueEmpty(SqQueue 0){
    14. if(Q.size == 0)
    15. return true;
    16. else
    17. return false;
    18. }
    19. // 新元素入队
    20. bool EnQueue(SqQueue &Q, ElemType x){
    21. if(Q.size == MaxSize)
    22. return false;
    23. Q.size++;
    24. Q.data[Q.rear] = x;
    25. Q.rear = (Q.rear+1)%MaxSize;
    26. return true;
    27. }
    28. // 出队
    29. bool DeQueue(SqQueue &Q, ElemType &x){
    30. if(Q.size == 0)
    31. return false;
    32. Q.size--;
    33. x = Q.data[Q.front];
    34. Q.front = (Q.front+1)%MaxSize;
    35. return true;
    36. }

    解决方法三:设置 tag 变量记录队列最近的操作。(tag=0:最近进行的是删除操作;tag=1 :最近进行的是插入操作)

    1. #define MaxSize 10;
    2. typedef struct{
    3. ElemType data[MaxSize];
    4. int front, rear;
    5. int tag;
    6. }SqQueue;
    7. // 初始化队列
    8. void InitQueue(SqQueue &Q){
    9. Q.rear = Q.front = 0;
    10. Q.tag = 0;
    11. }
    12. // 判断队列是否为空,只有tag==0即初始化或者出队后才可能为空
    13. bool QueueEmpty(SqQueue 0){
    14. if(Q.front == Q.rear && Q.tag == 0)
    15. return true;
    16. else
    17. return false;
    18. }
    19. // 新元素入队 判断队列是否满,只有tag==1即入队后才可能满
    20. bool EnQueue(SqQueue &Q, ElemType x){
    21. if(Q.rear == Q.front && tag == 1)
    22. return false;
    23. Q.data[Q.rear] = x;
    24. Q.rear = (Q.rear+1)%MaxSize;
    25. Q.tag = 1;
    26. return true;
    27. }
    28. // 出队
    29. bool DeQueue(SqQueue &Q, ElemType &x){
    30. if(Q.rear == Q.front && tag == 0)
    31. return false;
    32. x = Q.data[Q.front];
    33. Q.front = (Q.front+1)%MaxSize;
    34. Q.tag = 0;
    35. return true;
    36. }

    获得队头元素】

    1. // 获取队头元素并存入x
    2. bool GetHead(SqQueue &Q, ElemType &x){
    3. if(Q.rear == Q.front)
    4. return false;
    5. x = Q.data[Q.front];
    6. return true;
    7. }
    队列的链式存储实现

    【链队列的定义】

    1. // 链式队列结点
    2. typedef struct LinkNode{
    3. ElemType data;
    4. struct LinkNode *next;
    5. }
    6. // 链式队列
    7. typedef struct{
    8. // 头指针和尾指针
    9. LinkNode *front, *rear;
    10. }LinkQueue;

    【 链队列的初始化(带头结点)】

    1. typedef struct LinkNode{
    2. ElemType data;
    3. struct LinkNode *next;
    4. }LinkNode;
    5. typedef struct{
    6. LinkNode *front, *rear;
    7. }LinkQueue;
    8. // 初始化队列
    9. void InitQueue(LinkQueue &Q){
    10. // 初始化时,front、rear都指向头结点
    11. Q.front = Q.rear = (LinkNode *)malloc(sizeof(LinkNode));
    12. Q.front -> next = NULL;
    13. }
    14. // 判断队列是否为空
    15. bool IsEmpty(LinkQueue Q){
    16. if(Q.front == Q.rear)
    17. return true;
    18. else
    19. return false;
    20. }

    入队出队(带头结点)】

    1. // 新元素入队
    2. void EnQueue(LinkQueue &Q, ElemType x){ // 不存在满的情况
    3. LinkNode *s = (LinkNode *)malloc(sizeof(LinkNode));
    4. s->data = x;
    5. s->next = NULL;
    6. Q.rear->next = s;
    7. Q.rear = s;
    8. }
    9. // 队头元素出队
    10. bool DeQueue(LinkQueue &Q, ElemType &x){
    11. if(Q.front == Q.rear) // 判空
    12. return false;
    13. LinkNode *p = Q.front->next;
    14. x = p->data;
    15. Q.front->next = p->next;
    16. // 如果p是最后一个结点,此时Q.rear已经要被删除了,则将队尾指针也指向队首指针
    17. if(Q.rear == p)
    18. Q.rear = Q.front;
    19. free(p);
    20. p = NULL;
    21. return true;
    22. }

    【不带头结点的链队列操作

    1. typedef struct LinkNode{
    2. ElemType data;
    3. struct LinkNode *next;
    4. }LinkNode;
    5. typedef struct{
    6. LinkNode *front, *rear;
    7. }LinkQueue;
    8. // 初始化队列
    9. void InitQueue(LinkQueue &Q){
    10. // 不带头结点的链队列初始化,头指针和尾指针都指向NULL
    11. Q.front = NULL;
    12. Q.rear = NULL;
    13. }
    14. // 判断队列是否为空
    15. bool IsEmpty(LinkQueue Q){
    16. if(Q.front == NULL)
    17. return true;
    18. else
    19. return false;
    20. }
    21. // 新元素入队
    22. void EnQueue(LinkQueue &Q, ElemType x){
    23. LinkNode *s = (LinkNode *)malloc(sizeof(LinkNode));
    24. s->data = x;
    25. s->next = NULL;
    26. // 第一个元素入队时需要特别处理
    27. if(Q.front == NULL){
    28. Q.front = s;
    29. Q.rear = s;
    30. }else{
    31. Q.rear->next = s;
    32. Q.rear = s;
    33. }
    34. }
    35. //队头元素出队
    36. bool DeQueue(LinkQueue &Q, ElemType &x){
    37. if(Q.front == NULL)
    38. return false;
    39. LinkNode *s = Q.front;
    40. x = s->data;
    41. if(Q.front == Q.rear){
    42. Q.front = Q.rear = NULL;
    43. }else{
    44. Q.front = Q.front->next;
    45. }
    46. free(s);
    47. return true;
    48. }
    双端队列

    双端队列定义 

    • 双端队列是允许从两端插入、两端删除的线性表。
    • 如果只使用其中一端的插入、删除操作,则等同于栈。
    • 输入受限的双端队列:允许一端插入,两端删除的线性表。
    • 输出受限的双端队列:允许两端插入,一端删除的线性表。

    双端队列考点:判断输出序列的合法化

    • 例:数据元素输入序列为 1,2,3,4,判断 4! = 24 个输出序列的合法性
             输入受限的双端队列:只有 4213 和 4231 不合法
             输出受限的双端队列:只有 4132 和 4231 不合法

    3. 栈与队列的应用

    3.1栈在括号匹配中的应用

    • 用栈实现括号匹配:
      1. 最后出现的左括号最先被匹配 (栈的特性——LIFO)。
      2. 遇到左括号就入栈(可以把对应右括号入栈,这样遇到右括号只需要判断是否和栈顶相等。
      3. 遇到右括号,就“消耗”一个左括号(出栈)。
    • 匹配失败情况:
      1. 扫描到右括号且栈空,则该右括号单身。
      2. 扫描完所有括号后,栈非空,则该左括号单身。
      3. 左右括号不匹配。

    1. #define MaxSize 10
    2. typedef struct{
    3. char data[MaxSize];
    4. int top;
    5. }SqStack;
    6. void InitStack(SqStack &S);
    7. bool StackEmpty(SqStack &S);
    8. bool Push(SqStack &S, char x);
    9. bool Pop(SqStack &S, char &x);
    10. // 判断长度为length的字符串str中的括号是否匹配
    11. bool bracketCheck(char str[], int length){
    12. SqStack S;
    13. InitStack(S);
    14. // 遍历str
    15. for(int i=0; i
    16. // 扫描到左括号,入栈
    17. if(str[i] == '(' || str[i] == '[' || str[i] == '{'){
    18. Push(S, str[i]);
    19. }else{
    20. // 扫描到右括号且栈空直接返回
    21. if(StackEmpty(S))
    22. return false;
    23. char topElem;
    24. // 用topElem接收栈顶元素
    25. Pop(S, topElem);
    26. // 括号不匹配
    27. if(str[i] == ')' && topElem != '(' )
    28. return false;
    29. if(str[i] == ']' && topElem != '[' )
    30. return false;
    31. if(str[i] == '}' && topElem != '{' )
    32. return false; }
    33. }
    34. // 扫描完毕若栈空则说明字符串str中括号匹配
    35. return StackEmpty(S);
    36. }

    3.2栈在表达式求值中的应用 


    中缀表达式:中缀表达式是一种通用的算术或逻辑公式表示方法,运算符以中缀形式处于操作数的中间。对于计算机来说中缀表达式是很复杂的,因此计算表达式的值时,通常需要先将中缀表达式转换为前缀或后缀表达式,然后再进行求值。
    前缀表达式(波兰表达式):前缀表达式的运算符位于两个操作数之前。
    后缀表达式(逆波兰表达式):后缀表达式的运算符位于两个操作数之后。

    中缀表达式转后缀表达式-手算
    步骤1: 确定中缀表达式中各个运算符的运算顺序
    步骤2: 选择下一个运算符,按照[左操作数 右操作数 运算符]的方式组合成一个新的操作数
    步骤3: 如果还有运算符没被处理,继续步骤2

    “左优先”原则: 只要左边的运算符能先计算,就优先算左边的 (保证运算顺序唯一);
     

    1. 中缀:A + B - C * D / E + F
    2. ① ④ ② ③ ⑤
    3. 后缀:A B + C D * E / - F +

    后缀表达式转中缀的计算—手算:
    从左往右扫描,每遇到一个运算符,就让运算符前面最近的两个操作数执行对应的运算,合体为一个操作数

    后缀表达式的计算—机算
    用栈实现后缀表达式的计算(栈用来存放当前暂时不能确定运算次序的操作数)
    步骤1: 从左往后扫描下一个元素,直到处理完所有元素;
    步骤2: 若扫描到操作数,则压入栈,并回到步骤1;否则执行步骤3;
    步骤3: 若扫描到运算符,则弹出两个栈顶元素,执行相应的运算,运算结果压回栈顶,回到步骤1;
     

    中缀表达式转后缀表达式(机算) 
    初始化一个栈,用于保存暂时还不能确定运算顺序的运算符从左到右处理各个元素,直到末尾。可能遇到三种情况:
    1.遇到操作数:直接加入后缀表达式。
    2.遇到界限符:遇到“(”直接入栈;遇到“)”则依次弹出栈内运算符并加入后缀表达式,直到 弹出“(”为止。注意:“(”不加入后缀表达式。
    3.遇到运算符:依次弹出栈中优先级高于或等于当前运算符的所有运算符,并加入后缀表达式, 若碰到“(” 或栈空则停止。之后再把当前运算符入栈。
     

    1. #define MaxSize 40
    2. typedef struct{
    3. char data[MaxSize];
    4. int top;
    5. }SqStack;
    6. typedef struct{
    7. char data[MaxSize];
    8. int front,rear;
    9. }SqQueue;
    10. void InitStack(SqStack &S);
    11. bool StackEmpty(SqStack S);
    12. bool Push(SqStack &S, char x);
    13. bool Pop(SqStack &S, char &x);
    14. void InitQueue(SqQueue &Q);
    15. bool EnQueue(LQueue &Q, char x);
    16. bool DeQueue(LQueue &Q, char &x);
    17. bool QueueEmpty(SqQueue Q);
    18. // 判断元素ch是否入栈
    19. int JudgeEnStack(SqStack &S, char ch){
    20. char tp = S.data[S->top];
    21. // 如果ch是a~z则返回-1
    22. if(ch >= 'a' && ch <= 'z')
    23. return -1;
    24. // 如果ch是+、-、*、/且栈顶元素优先级大于等于ch则返回0
    25. else if(ch == '+' && (tp == '+' || tp == '-' || tp == '*' || tp == '/'))
    26. return 0;
    27. else if(ch == '-' && (tp == '+' || tp == '-' || tp == '*' || tp == '/'))
    28. return 0;
    29. else if(ch == '*' && (tp == '*' || tp == '/'))
    30. return 0;
    31. else if(ch == '/' && (tp == '*' || tp == '/'))
    32. return 0;
    33. // 如果ch是右括号则返回2
    34. else if(ch == ')')
    35. return 2;
    36. // 其他情况ch入栈,返回1
    37. else return 1;
    38. }
    39. // 中缀表达式转后缀表达式
    40. int main(int argc, char const *argv[]) {
    41. SqStack S;
    42. SqQueue Q;
    43. InitStack(S);
    44. InitQueue(Q);
    45. char ch;
    46. printf("请输入表达式,以“#”结束:");
    47. scanf("%c", &ch);
    48. while (ch != '#'){
    49. // 当栈为空时
    50. if(StackEmpty(&S)){
    51. // 如果输入的是数即a~z,直接入队
    52. if(ch >= 'a' && ch <= 'z')
    53. EnQueue(Q, ch);
    54. // 如果输入的是运算符,直接入栈
    55. else
    56. Puch(S, ch);
    57. }else{
    58. // 当栈非空时,判断ch是否需要入栈
    59. int n = JudgeEnStack(S, ch);
    60. // 当输入是数字时直接入队
    61. if(n == -1){
    62. EnQueue(Q, ch);
    63. }else if(n == 0){
    64. // 当输入是运算符且运算符优先级不高于栈顶元素时
    65. while (1){
    66. // 取栈顶元素入队
    67. char tp;
    68. Pop(S, tp);
    69. EnQueue(Q, tp);
    70. // 再次判断是否需要入栈
    71. n = JudgeEnStack(S, ch);
    72. // 当栈头优先级低于输入运算符或者栈头为‘)’时,入栈并跳出循环
    73. if(n != 0){
    74. EnStack(S, ch);
    75. break;
    76. }
    77. }
    78. }else if(n == 2){
    79. // 当出现‘)’时 将()中间的运算符全部出栈入队
    80. while(1){
    81. char tp;
    82. Pop(S, tp);
    83. if(tp == '(')
    84. break;
    85. else
    86. EnQueue(Q, tp);
    87. }
    88. }else{
    89. // 当运算符优先级高于栈顶元素或出现‘(’时直接入栈
    90. Push(S, ch);
    91. }
    92. }
    93. scanf("%c", &ch);
    94. }
    95. // 将最后栈中剩余的运算符出栈入队
    96. while (!StackEmpty(S)){
    97. char tp;
    98. Pop(S, tp);
    99. EnQueue(Q, tp);
    100. }
    101. // 输出队中元素
    102. while (!QueueEmpety(Q)){
    103. printf("%c ", DeQueue(Q));
    104. }
    105. return 0;
    106. }

    用栈实现中缀表达式的计算:
         1.初始化两个栈,操作数栈和运算符栈;
         2.若扫描到操作数,压入操作数栈;
         3.若扫描到运算符或界限符,则按照“中缀转后缀”相同的逻辑压入运算符栈(期间也会弹出运算符,每当弹出一个运算符时,就需要再弹出两个操作数栈的栈顶元素并执行相应运算,运算结果再压回操作数栈) 
     

    3.3栈在递归中的应用


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

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

    • 调用返回地址
    • 实参
    • 局部变量

    递归调用时,函数调用栈称为 “递归工作栈”:

    每进入一层递归,就将递归调用所需信息压入栈顶;
    每退出一层递归,就从栈顶弹出相应信息;
    缺点:太多层递归可能回导致栈溢出;适合用“递归”算法解决:可以把原始问题转换为属性相同,但规模较小的问题
     

     3.4队列的应用

    1. 队列应用:树的层次遍历
    2. 队列应用:图的广度优先遍历
    3. 队列应用:操作系统中多个进程争抢着使用有限的系统资源时,先来先服务算法(First Come First Service)是是一种常用策略。

    3.5 特殊矩阵的压缩存储 

     

    3.5.1 数组的存储 

    一维数组的存储:各数组元素大小相同,且物理上连续存放。设起始地址为LOC,则数组元素a[i]的存放地址 = LOC + i * sizeof(ElemType) (0≤i<10) 

    二维数组的存储 :
            1. M行N列的二维数组b[M][N]中,设起始地址为LOC,若按行优先存储,则b[i][j]的存储地址 =LOC+(i∗N+j)∗sizeof(ElemType)

            2. M行N列的二维数组b[M][N]中,设起始地址为 LOC,若按列优先存储,则b[i][j]
    的存储地址 = LOC+(i+j*M)∗sizeof(ElemType)

     

    3.5.2对称矩阵的压缩存储

             对称矩阵的压缩存储:若n阶方阵中任意一个元素a_{i,j},都有a_{i,j}=a_{j,i}则该矩阵为对称矩阵,对于对称矩阵,只需存储主对角线+下三角区。若按照行优先原则将各元素存入一维数组中,即a_{i,j}存入到数组B[k]中,那么数组B[k]共有\frac{n(n+1)}{2}个元素。对于k,有:

    k = \left\{\begin{matrix} \frac{i(i-1)}{2} + j - 1 &i >= j \\ \frac{j(j-1)}{2} + i - 1 &i < j \end{matrix}\right.

    3.5.3三角矩阵的压缩存储

    1. 下三角矩阵:除了主对角线和下三角区,其余的元素都相同。

    2. 上三角矩阵:除了主对角线和上三角区,其余的元素都相同。

    3. 压缩存储策略:按行优先原则将主对角线+下三角区存入一维数组中,并在最后一个位置存储常量。即a_{i,j}存入到数组B[k]中,那么数组B[k]共有\frac{n(n+1)}{2} + 1个元素。对于k,有:

     k = \left\{\begin{matrix} \frac{i(i-1)}{2} + j - 1 &i >= j \\ \frac{n(n+1)}{2} &i < j \end{matrix}\right.

    按行优先原则将主对角线+上三角区存入一维数组中,并在最后一个位置存储常量。即a_{i,j}存入到数组B[k]中,那么数组B[k]共有\frac{n(n+1)}{2} + 1个元素。对于k,有: 

    k = \left\{\begin{matrix} \frac{(i-1)(2n-i+2)}{2} + j - i &i <= j \\ \frac{n(n+1)}{2} &i > j \end{matrix}\right.

     推导:k= n+ (n-1) + ...+ (n - i + 2) + (j -i+1)-1= \frac{(i-1)(2n-i+2)}{2} + j - i

    3.5.4三对角矩阵的压缩存储

    三对角矩阵,又称带状矩阵: 当|i-j|>1时,有a_{i,j} =0(1\leqslant i,j\leqslant n)。对于三对角矩阵,按行优先原则,只存储带状部分,即a_{i,j}存入到数组B[k]中,那么k =3i+j- 3。若已知数组下标k,则i=\left \lfloor (k+1)/3+1) \right \rfloor , j=k-2i+3

     

    k值推导:前i-1行有2 + (i- 2)* 3个元素,第i行j-i + 2个元素,共有2*i+j- 2个元素,数组中下标为2*i+j- 3。

    稀疏矩阵的压缩存储

    稀疏矩阵的非零元素远远少于矩阵元素的个数。压缩存储策略:

    • 顺序存储:三元组 <行,列,值>为了运算方便,矩阵的行数,列数,非零元素的个数也同时存储。

     

    • 链式存储:十字链表法 

  • 相关阅读:
    python3调用阿里云openapi脚本 - 生产环境
    【节能学院】智能操控装置在高压开关柜的应用
    Oracle 19c RAC异机恢复到单机19c(包含PDB)
    必备的团队任务协同看板工具及共享思维导图软件
    数据结构与算法训练:第二十九弹
    Es的核心概念
    体重秤智能电子秤方案开发设计
    C++ 实现基于时序公平的读写锁
    可编程 USB 转串口适配器开发板专用工具 S2STool 介绍
    SpringBoot缓存使用方式@EnableCaching、@Cacheable
  • 原文地址:https://blog.csdn.net/zhendong825/article/details/134432302