3.1 栈

栈的定义

  • 限定只能在表的一端进行插入和删除操作的线性表。
  • 栈顶(top):允许插入和删除的一端。
  • 栈底(bottom):不允许插入和删除的另一端。
  • :不含元素的空表。

eg.( a, a2,……, an)

其中a为栈底元素,an为栈顶元素image.png

栈的特点

  • 先进后出
  • 后进先出

image.png

栈的抽象数据类型定义

  1. ADT Stack {
  2. 数据对象:D={a,l ai ElemSet, i=1,2.,n, n0 }
  3. 数据关系:R1={<a.1,4>l aj-1,4 D, i=2,..n }
  4. 基本操作:
  5. InitStack(&S) // 操作结果:构造一个空栈S。
  6. DestroyStack(&S) // 初始条件:栈S已存在。 操作结果:栈S被销毁。
  7. ClearStack(&S) // 初始条件:栈S已存在。 操作结果:将S清为空栈
  8. StackEmpty(S) // 初始条件:栈S已存在。 操作结果:若栈S为空栈,则返回TRUE,否则返回FALSE。
  9. StackLength(S) // 初始条件:栈S已存在。 操作结果:返回栈S中元素个数,即栈的长度。
  10. GetTop(S, &e) // 初始条件:栈S已存在且非空。 操作结果:用e返回S的栈顶元素。
  11. Push(&S, e) // 初始条件:栈S已存在。 操作结果:插入元素e为新的栈顶元素。
  12. Pop(&S, &e) // 初始条件:栈S已存在且非空。操作结果:删除S的栈顶元素,并用e 返回其值
  13. StackTraverse(S, visit( )) // 初始条件:栈S已存在且非空,visit()为元素的访问函数。操作结果:从栈底到栈顶依次对S的每个元素调用函数
  14. visit(),一旦visit()失败,则操作失败。
  15. }ADT Stack

栈的结构定义

利用一组地址连续的存储单元依次存放自栈底到栈顶的数据元素。

  1. #define STACK_INIT_SIZE 100; //存储空间初始分配量
  2. #define STACKINCREMENT 10; //存储空间分配增量
  3. typedef struct {
  4. SElemType *base; //存储空间基址
  5. ElemType *top; //栈顶指针
  6. int stacksize; //当前已分配的存储空间,以元素位单位
  7. }SqStack;

注意:这里的栈顶指针不是栈顶元素,它是指向栈顶元素的下一个存储空间

image.png

栈的基本操作

  • 初始化栈

    1. Status InitStack (SqStack &S){ //构造一个空栈S
    2. S.base=(SElemType*)malloc(STACK_INIT_SIZE*sizeof(SElemType));//为栈底分配空间
    3. if(!S.base) exit(OVERFLOW); //存储分配失败
    4. S.top = S.base; //空栈的情况就是栈顶指针等于栈底
    5. S.stacksize = STACK_INIT_SIZE;
    6. return OK;
    7. }//InitStack

    所谓初始化就是对栈的结构定义中的所有属性进行赋值。

  • 取栈顶元素

    1. Status GetTop (SqStack S, SElemType &e){ //若栈不空,则用e返回S的栈顶元素,并返回OK
    2. if (S.top ==S.base ) return ERROR; //空栈
    3. e =*(S.top-1); //返回非空栈中栈顶元素
    4. return OK;
    5. }//GetTop
  • 进栈(重点)

    1. Status Push (SqSttack &S, SElemType{ //插入元素e为新的栈顶元素
    2. if(S.top.-S.base>=S.stacksize){ //栈满,追加存储空间
    3. S.base=(SElemType *)realloc(S.base,(S.stacksizetSTACKINC)*sizeof(SElemType));
    4. if(!S.base) exit(OVERFLOW); //存储分配失败
    5. S.top = S.base + S.stacksize;
    6. S.stacksize += STACKINCREMENT;
    7. }
    8. *(S.top++)=e; //插入新的元素
    9. return OK;
    10. }//Push
  • 出栈(重点)

    1. Status Pop (Stack &S, ElemType &e){ //栈不空,删除S的栈顶元素,用e返回其值,并返回OK;否则返回ERROR
    2. if (S.top == S.base) return ERROR; //空栈
    3. e =*(--S.top); //返回非空栈中栈顶元素
    4. return OK;
    5. }//Pop

    这里第三句话的意思是:栈顶指针先自减,在把指向的元素赋值给e返回。 上述的操作都是顺序栈(参考顺序表)的操作。

栈的链式存储

入栈算法
看图了解一下即可,算法很简单可以自己写出来。
image.png
image.png
出栈算法
image.png
image.png

详细的算法在下面的链接中。

数据结构:栈的链式实现(C语言描述)lpp0900320123的专栏-CSDN博客链式栈

顺序栈和链栈的比较

  • 时间性能:相同,都是常数时间O(1)
  • 空间性能:
    • 顺序栈:有元素个数的限制和空间浪费的问题。
    • 链栈:没有栈满的问题,只有当内存没有可用空间时才会出现栈满,但是每个元素都需要一个指针域,从而产生了结构性开销(就是所需要的存储空间变大)。

3.2 栈的应用(重点)

数制转换

  • 数制转换的规则

十进制数N和其他d进制数的转换:
N= (N div d)×d+ N mod d
计算方法如下:

  1. 求出该数对d做整除运算和求余运算的结果
  2. 判断整除运算的结果是不是0,如果不是,把该结果代入第一步中重新计算。
  3. 直到整除运算为0,停止运算。
  4. 所有求余运算的结果从后往前就是转换进制后的大小。

    (其中: div为整除运算并且取整数mod为求余运算) eg.十进制和八进制进行转换 N= (N div 8)×8+ N mod 8

举个例子:
image.png
所以十进制数1348转换为八进制为2504
代入到计算机的算法中,就是每次求出一个求余运算的结果,都把这个结果入栈,以这个上面的题目为例,
入栈的顺序就是4 0 5 2,求完之后出栈的顺序就是2 5 0 4
核心算法实现

  1. void conversion (int Num) { //对于输入的任意一个非负十进制整数,打印输出与其等值的八进制数
  2. ElemType e; SqStack S;
  3. InitStack(S); //构造空栈
  4. while (Num) {
  5. Push(S, Num % 8);Num = Num/8;
  6. }//核心算法,Num不等于0的时候,把余数入栈。
  7. while (!StackEmpty(S)){
  8. Pop(S.e);printf ("%d", e);
  9. }//逐个输出余数。
  10. printf("\n");
  11. }// conversion

完整代码

  1. #include<stdio.h>
  2. #include<stdlib.h>
  3. #include<malloc.h>
  4. #include<math.h>
  5. #define STACK_INIT_SIZE 100
  6. #define STACKINCREMENT 10
  7. #define OK 1
  8. typedef int SElemType;
  9. typedef int SElemType;
  10. typedef int Status;
  11. typedef struct {
  12. SElemType *base;
  13. SElemType *top;
  14. int stacksize;
  15. }SqStack;
  16. Status InitStack (SqStack &S){
  17. S.base=(SElemType *)malloc(STACK_INIT_SIZE*sizeof(SElemType));
  18. if(!S.base) exit(OVERFLOW);
  19. S.top= S.base;
  20. S.stacksize = STACK_INIT_SIZE;
  21. return OK;
  22. }
  23. Status Push(SqStack &S,SElemType e){
  24. if(S.top-S.base>=S.stacksize){
  25. S.base=(SElemType *)realloc(S.base,(S.stacksize+STACKINCREMENT)*sizeof(SElemType));
  26. if(!S.base) exit(-2);
  27. S.top=S.base+S.stacksize;
  28. S.stacksize+=STACKINCREMENT;
  29. }
  30. *(S.top++)=e;
  31. return 1;
  32. }
  33. Status StackEmpty(SqStack S){
  34. if(S.top==S.base)
  35. return 1;
  36. else return 0;
  37. }
  38. Status Pop (SqStack &S,SElemType &e){
  39. if (S.top == S.base)
  40. return 0;
  41. e =*(--S.top);
  42. return 1;
  43. }
  44. void conversion (int Num){
  45. SElemType e;
  46. SqStack S;
  47. InitStack(S);
  48. while (Num){
  49. Push(S, Num % 8);
  50. Num = Num/8;
  51. }
  52. while (!StackEmpty(S)){
  53. Pop(S,e);
  54. printf ("%d", e);
  55. }
  56. printf("\n");
  57. }
  58. int main(){
  59. int a;
  60. printf("请输入要转换的十进制数:");
  61. scanf("%d",&a);
  62. printf("\n转换为八进制的结果为:");
  63. conversion(a);
  64. return 0;
  65. }

检测括号匹配

比如说我们要判断下列括号字符串是否符合语法规则。
{}[][(({{}}))]
核心思想就是:

  1. 把所有括号字符依次存入栈中。
  2. 如果是左括号直接入栈
  3. 如果是右括号判断当前的**栈顶元素**是不是相应的右括号
  4. 如果不是,那么该字符串就不符合语法规则。如果是,那么,把当前栈顶元素(也就是这个右括号对应的左括号)出栈。
  5. 直到最后一个括号字符出栈/入栈,判断栈中是否还有元素,如果有,那么不符合语法规则;如果没有,符合。

    下面是自制的流程图(我知道非常的搓,凑合看看)

image.png

这里就不多bb,直接上完整代码。

  1. /*
  2. 括号匹配检测,对于一串带括号的字符
  3. 1.如果是左括号,入栈
  4. 2.如果是右括号,与栈顶元素比较,
  5. 若形成括号对,则栈顶左括号出栈;
  6. 若不能形成括号对,则括号不能匹配
  7. */
  8. # include <stdio.h>
  9. # include <stdlib.h>
  10. # define INIT_SIZE 6 //初始栈空间
  11. # define INCRE_SIZE 2 //占空间增量
  12. //栈结构
  13. typedef struct
  14. {
  15. char * base; //指向栈空间基址
  16. char * top; //指向栈顶元素的下一个位置
  17. int initsize; //栈空间大小
  18. }Stack;
  19. Stack inital(); //初始化栈
  20. void push(Stack &s, char ch); //元素入栈
  21. void pop(Stack &s, char &e); //元素出栈
  22. bool stack_empty(Stack s); //判断栈是否为空
  23. int main(void)
  24. {
  25. Stack s = inital();
  26. char ch[20];
  27. char * p;
  28. char e;
  29. printf("请输入带括号的字符串:");
  30. gets(ch); //输入字符
  31. p = ch; //指向首字符
  32. while(*p) //没有到串尾
  33. {
  34. switch(*p)
  35. {
  36. case '(':
  37. case '[':
  38. case '{':
  39. push(s, *p); //左括号入栈
  40. p ++; //读下一个字符
  41. break;
  42. case ')':
  43. case ']':
  44. case '}':
  45. pop(s, e); //读入右括号,与栈顶的左括号e匹配
  46. if( !((e == '(' && *p == ')') || (e == '[' && *p == ']') || (e == '{' && *p == '}')))
  47. {
  48. printf("左右括号不能匹配\n");
  49. exit(0);
  50. }
  51. p ++;
  52. break;
  53. default:
  54. p ++;
  55. }
  56. }
  57. if(stack_empty(s)) //栈是否为空,为空,则匹配成功
  58. {
  59. printf("括号匹配成功\n");
  60. }
  61. else
  62. {
  63. printf("括号匹配失败\n");
  64. }
  65. return 0;
  66. }
  67. //初始化栈
  68. Stack inital()
  69. {
  70. Stack s;
  71. s.base = (char *)malloc(sizeof(char) * INIT_SIZE);
  72. s.initsize = INIT_SIZE;
  73. s.top = s.base;
  74. return s;
  75. }
  76. //入栈
  77. void push(Stack &s, char ch)
  78. {
  79. if(s.top - s.base >= s.initsize) //栈满,增加栈空间
  80. {
  81. s.base = (char *)realloc(s.base, sizeof(char) * (s.initsize + INCRE_SIZE));
  82. s.initsize = s.initsize + INCRE_SIZE;
  83. }
  84. *(s.top) = ch;
  85. s.top ++;
  86. }
  87. //出栈
  88. void pop(Stack &s, char &e)
  89. {
  90. if(stack_empty(s))
  91. return;
  92. else
  93. {
  94. e = *(s.top - 1);
  95. s.top --;
  96. }
  97. }
  98. //判断栈是否为空
  99. bool stack_empty(Stack s)
  100. {
  101. if(s.base == s.top)
  102. return true;
  103. else
  104. return false;
  105. }
  106. ————————————————
  107. 版权声明:本文为CSDN博主「wjb214149306」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
  108. 原文链接:https://blog.csdn.net/wjb214149306/article/details/47299749

下面是b站视频讲解。

点击查看【bilibili】

表达式求值

  • 表达式的组成
    1. 操作数(operand):常数、变量。
    2. 运算符(operator):算术运算符、关系运算符和逻辑运算符。
    3. 界限符(delimiter):左右括弧和表达式结束符。
  • 算术运算的规则
    1. 先乘除后加减先左后右
    2. 先括弧内后括弧外
      例如:
      4+2*3-10/5
      =4+6-10/5
      =10-10/5
      =10-2

      这个好难的我也不会,所以直接上链接。(我估计也不大会考)

点击查看【bilibili】


3.3 队列

队列的基础定义

队列
只允许在一端进行插入而在另一端进行删除的线性表。
队尾:允许插入的一端。
队头:允许删除的一端。
特点:先进先出(FIFO)
image.png

队列的抽象数据类型定义(了解即可)

  1. ADT Queue {
  2. 数据对象:D={ai|aiElemSet, i=1,2....,n, n0}
  3. 数据关系:R1={<ai-1,ai>│ ai-1,aiD,i=2,....n}
  4. 基本操作:
  5. InitQueue(&Q) //操作结果:构造一个空队列Q。
  6. DestroyQueue(&Q) //初始条件:队列Q已存在。操作结果:队列Q被销毁,不再存在。
  7. ClearQueue(&Q) //初始条件:队列Q已存在。操作结果:将Q清为空队列。
  8. QueueEmpty(Q) //初始条件:队列Q已存在。操作结果:若Q为空队列,则返回TRUE,否则返回FAISE。
  9. QueueLength(Q) //初始条件:队列Q已存在。操作结果:返回Q的元素个数,即队列的长度。
  10. GetHlead(Q,&e) //初始条件:Q为非空队列。操作结果:用e 返回Q的队头元素。
  11. EnQueue(&cQ,e) //初始条件:队列Q已存在。操作结果:插入元素e为Q的新的队尾元素。
  12. DeQueue(&Q,&e) //初始条件:Q为非空队列。操作结果:删除Q的队头元素,并用e返回其值。
  13. QueueT'raverse(Q,visit()) //初始条件:队列Q已存在且非空,visit()为元素的访问函数。操作结果:依次对Q的每个元素调用函数visit( ),
  14. 一旦visit()失败则操作失败。
  15. } ADT Queue

队列的结点定义(重点)

  1. typedef struct QNode{
  2. Qelemlype data ;
  3. struct QNode *next ;
  4. }QNode, *QueuePtr;
  5. typedef struct{
  6. QueuePtr front ;//队头指针
  7. QueuePtr rear ;//队尾指针
  8. }LinkQueue;

image.png
这里的第一个Q.front指向是一个空指针,所以队头next指向的结点

不难看出,队列是基于链表的发展。

基本操作

  • 创造一个空队列Q

    1. Status InitQueue (LinkQueue &Q){//构造一个空队列Q
    2. Q.front=Q.rear=(QueuePtr)malloc(sizeof(QNode));
    3. if (!Q.front) exit(OVERFOLW);/存储分配失败
    4. Q.front->next=NULL;
    5. return OK;
    6. }

    首先使队列的头指针和尾指针指向同一个存储空间。 判断是否还存在存储空间,若不存在,则创建空列队Q失败。 令头指针的下一个指针为空指针。

  • 入队列

    1. Status EnQueue(LinkQueue &Q,QElemType e){
    2. //在当前队列的尾元素之后,插入元素e为新的队列尾元素
    3. p=(QueuePtr)malloc(sizeof(QNode));
    4. if (!p) exit(OVERFLOW); //存储分配失败
    5. p->data=e;p->next = NULL;
    6. Q.rear->next=p; //修改尾结点的指针
    7. Q.rear=p; //移动队尾指针
    8. }

    如下图所示
    image.png
    image.png
    image.png

  • 出队列

    1. Status DeQueue(LinkQueue &Q,QElem.Type &e){
    2. //若队列不空,则删除队列Q的队头元素,用e返回其值,并返回OK;
    3. //否则返回ERROR
    4. if(Q.front==Q.rean) return ERROR; //链队列空
    5. p=Q.front->next;
    6. e= p->data; //返回被删元素的值
    7. Q.front->next=p->next; //修改队头结点指针
    8. if(Q.rear == p) Q.rear=Q.front;
    9. free(p);
    10. //释放被删结点
    11. return OK;
    12. }

    过程如下图所示
    image.png
    image.png

队列存在的问题

问题:假溢出
什么是假溢出,我们举个例子,假设一个队列的存储空间为6
image.png
经过上述入队和出队操作后,rear指针不断被提高,会导致后面J4,J5,J6入队之后,不能再入队J7(已经栈满了),然而实际情况是队列仍然存在存储空间。
所以下面引入了循环队列。

循环队列

  • 基本思想:把队列设想成环形,让sq[0]接在sq[M-1]之后,若rear+1==M,则令rear=0;
  • 入队: sq[rear]=x; rear=(rear+1)%M;
  • 出队: x=sq[front]; front=(front+1)%M;

image.png

  • 结构定义

    1. 循环队列的结构定义
    2. #define MAXQSIZE 100//最大队列长度
    3. typedef struct {
    4. QElemType *base //初始化的动态分配存储空间
    5. int rear //队尾指针,指向队尾元素
    6. int front //队头指针,指向队头元素的前一个位置
    7. }SqQueue
  • 创建一个空队列

    1. Status InitQueue (SqQueue &Q){//构造一个空队列Q
    2. Q.base = (QElemType *)malloc(MAXQSIZE*sizeof(QElemType));//为循环队列分配存储空间
    3. if (!Q.base) exit(OVERFLOW); //存储分配失败
    4. Q.front = Q.rear= 0;
    5. return OK;
    6. }// InitQueue
  • 求队列的长度

    1. int QueueLength (SqQueue Q){
    2. //返回队列Q中元素个数,即队列的长度
    3. return ((Q.rear-Q.fron+MAXQSIZE)%MAXQSIZE);
    4. }
  • 入队操作

    1. Status EnQueue (SqQueue &Q,QElemType e){
    2. //插入元素e为新的队列尾元素
    3. if((Q.rear+1)%MAXQSIZE==Q.front ) return ERROR; //队列满
    4. Q.base[Q.rear] = e;
    5. Q.rear = (Q.rear+1) % MAXQSIZE;
    6. return OK;
    7. }
  • 出队操作

    1. Status DeQueue (SqQueue &Q,QElemType &e){
    2. //若队列不空,则删除当前队列Q中的头元素,用e返回其值,并返回OK
    3. if (Q.front== Q.rear) return ERROR;
    4. e = Q.base[Q.front];
    5. Q.front =(Q.front+1) % MAXQSIZE;
    6. return OK;
    7. }