408线性表【跨考小白学习笔记2】——栈的应用

408线性表【跨考小白学习笔记2】——栈的应用

pre:栈数据类型ADT定义及基本操作声明

本文代码:c语言,版本c99

#define Elemtype char
#define Maxsize 50  //定义栈中元素的最大个数
typedef struct {
  Elemtype data[Maxsize];  //存放栈中元素
  int top;                 //栈项指针i,数据类型是int
} SqStack;

typedef struct Linknode {
  Elemtype data;          //数据域
  struct Linknode* next;  //指针域
} StackNode, *LinkStackPtr;

typedef struct {
  LinkStackPtr top;
  int count;
} LinkStack;

bool InitStack(SqStack* S);
bool StackEmpty(SqStack S);
int StackLength(SqStack S);
bool GetTop(SqStack S, Elemtype* e);
bool Push(SqStack* S, Elemtype e);
bool Pop(SqStack* S, Elemtype* e);
bool StackTraverse(SqStack S);

bool InitLinkStack(LinkStack* S);
bool ClearLinkStack(LinkStack* S);
bool LinkStackEmpty(LinkStack S);
int LinkStackLength(LinkStack S);
bool GetTop_LinkStack(LinkStack S, Elemtype* e);
bool Push_LinkStack(LinkStack* S, Elemtype e);
bool Pop_LinkStack(LinkStack* S, Elemtype* e);
bool LinkStackTraverse(LinkStack S);

3.3.1栈在括号匹配中的应用

bool bracketcheck(char str[], int length) {
  //形参说明:str[]需匹配的字符串;length匹配的长度
  //初始条件:栈ADT,此处使用顺序栈
  //操作结果:匹配成功返回true(输出为1)匹配失败返回false(输出为0)
  //算法设计思想:
  /*从左到右扫描一个字符串:
    1)凡出现左括弧,则进栈;
    2)凡出现右括弧,
        首先检查栈是否空
            若栈空,则表明该“右括弧”多余
            否则(栈非空)和栈顶元素比较,
                若相匹配,则“左括弧出栈”
                否则表明不匹配
    3)表达式检验结束时,
        若栈空,则表明表达式中匹配正确
        否则表明“左括弧”有余*/
  SqStack S;
  InitStack(&S);  //初始化一个栈
  for (int i = 0; i < length; i++) {
    if (str[i] == '(' || str[i] == '[' || str[i] == '{')
      Push(&S, str[i]);  //扫描到左括号,入栈
    else if (str[i] == ')' || str[i] == ']' || str[i] == '}') {
      if (StackEmpty(S))  //扫描到右括号,且当前栈空
        return false;     //匹配失败
      Elemtype 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);  //检索完全部括号后,栈空说明匹配成功
}
调用
int main() {
  // 1.括号匹配
  char a_group[50] = {'1', '+', '(', '4', '-', '5', ')', '/', '2', '0'};
  printf("%d\n",
        bracketcheck(a_group, 20));  // 1代表匹配成功,0代表false匹配失败
return 0;
}

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

1.1)将中缀表达式转成后缀表达式

bool transform(char suffix[], char exp[]) {
  //形参说明:
  /* *suffix后缀表达式字符串,传入实参&suffix[];
  exp[]即expression中缀表达式*/
  //初始条件:输入中缀表达式
  //操作结果:返回后缀表达式
  //算法设计思想:
  /*初始化一个栈,用于保存暂时还不能确定运算顺序的运算符。
从左到右处理各个元素,直到末尾。可能遇到三种情况
    ①遇到操作数。直接加入后缀表达式。
    ②遇到界限符。遇到“(”直接入栈;遇到“)”则依次弹出栈内运算符并加入后缀表达式,直到弹出“(”为止。注意:“(”不加入后缀表达式。
    ③遇到运算符。依次弹出栈中优先级高于或等于当前运算符的所有运算符,并加入后缀表达式若碰到“(”或栈空则停止。之后再把当前运算符入栈。
按上述方法处理完所有字符后,将栈中剩余运算符依次弹出,并加入后缀表达式。
  */

  SqStack S;  //用于保存暂时还不能确定运算顺序的运算符
  InitStack(&S);
  Elemtype topElem = '\0';
  int j = 0;

  for (int i = 0; i < strlen(exp); i++) {
    if (exp[i] == ')') {
      GetTop(S, &topElem);
      while (topElem != '(') {
        Pop(&S, &topElem);
        if (topElem != '(') {
          suffix[j++] = topElem;
        }
      }
    } else if (exp[i] == '(') {
      Push(&S, exp[i]);
    } else if (exp[i] == '+' || exp[i] == '-') {
      GetTop(S, &topElem);
      if (topElem == '*' || topElem == '/') {
        while (!StackEmpty(S) && topElem != '(') {  //栈顶元素优先级更高故出栈
          Pop(&S, &topElem);
          suffix[j++] = topElem;
        }
      }
      Push(&S, exp[i]);  //并将该元素压入栈
    } else if (exp[i] == '*' || exp[i] == '/') {
      Push(&S, exp[i]);  //由于乘除优先级最高,直接进栈
    } else {             //操作数直接输出
      suffix[j++] = exp[i];
    }
  }
  while (!StackEmpty(S)) {
    Pop(&S, &topElem);
    suffix[j++] = topElem;
  }
  return true;
}

1.2)对后缀表达式求值

int Operate_char2int(char a, char op, char b) {
  //输出-9999即为报错
  //如果不强转,用char + char 遇到其中一个char 是 0 的时候会出错
  //输出2 + '\0'= 98  这里的'\0'显示不出来,但复制的时候是一个换行或终止符
  //  if (a == '\0')
  //    a = '0';
  //  if (b == '\0')
  //    b = '0';
  char a_str[Maxsize] = {0};
  char b_str[Maxsize] = {0};
  if (isdigit(a)) {
    a_str[0] = a;
  } else
    sprintf(a_str, "%d",a);  
    // sprintf 是转换整型为字符串!int转不成char,可以转char[]
    // 如果是char会发生上溢,overflow
  if (isdigit(b)) {
    b_str[0] = b;
  } else
    sprintf(b_str, "%d", b);
  switch (op) {
    case '+':
      return atoi(a_str) + atoi(b_str);
      break;
    case '-':
      return atoi(a_str) - atoi(b_str);
      break;
    case '*':
      return atoi(a_str) * atoi(b_str);
      break;
    case '/':
      if (b == '0')
        return -9999;  //除数不能为0,报错
      return atoi(a_str) / atoi(b_str);
      break;
  }
  return -9999;
}
int calculte_suffix(char suffix[]) {
  //形参说明:传入suffix[]后缀表达式,
  //初始条件:传入后缀表达式,建立栈ADT,operation_char2int
  //操作结果:返回结果(int型)
  //算法设计思想:
  /*用栈实现后缀表达式的计算:
    ①从左往右扫描下一个元素,直到处理完所有元素
    ②若扫描到操作数则压入栈,并回到①;否则执行③
    ③若扫描到运算符,则弹出两个栈顶元素,执行相应运算,运算结果压回栈顶,回到①
  */
  SqStack S;
  InitStack(&S);
  Elemtype topElem;
  Elemtype SecondElem;
  int result = 0;
  for (int i = 0; i < strlen(suffix); i++) {
    if (suffix[i] != '+' && suffix[i] != '-' && suffix[i] != '*' &&
        suffix[i] != '/') {
      Push(&S, suffix[i]);  //压入栈的都是十六进制
    } else {
      Pop(&S, &topElem);
      Pop(&S, &SecondElem);
      result = Operate_char2int(SecondElem, suffix[i], topElem);

      printf("%c %c %c = %d\n", SecondElem, suffix[i], topElem, result);
      //这里result是int压入的是char栈!
      //当result=0时,压入栈的被强转成为'\0',当result!=0时,压入栈者强转为十六进制。hoit()可将十六进制字符串强转为十进制整型
      Push(&S, result);
    }
  }
  return result;
}
char 与 int 强转方式

sscanf()
atoi() 最方便
Typecasting 输出是ascii码

代码示例:

const char *str = "12345";
  char c = 's';
  int x, y, z;

  sscanf(str, "%d", &x); // Using sscanf
  printf("\nThe value of x : %d", x);

  y = atoi(str); // Using atoi()
  printf("\nThe value of y : %d", y);

  z = (int)(c); // Using typecasting
  printf("\nThe value of z : %d", z);

c++中,对于signed
char,将其转化为int时,最高位为符号位,那么扩展时,就会对符号位进行扩展,即将整型比字符多出来的位全部设置成与符号位相同。
比如0x80,变成int就会变成0xffffff80;而0x70就会变成0x00000070。
而unsigned char就不会进行扩展,因此想表示byte的时候,最好使用unsigned
char

————————————————
版权声明:该引用段落为CSDN博主「木凡辰」的原创文章,遵循CC 4.0
BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_44844115/article/details/102801953

2)计算中缀表达式

从下面代码也可以看出,直接计算中缀表达式繁琐且易出错

int calculte_exp(char exp[]) {
  //形参说明:传入exp中缀表达式
  //初始条件:栈ADT,func Operate_char2int
  //操作结果:返回计算结果;如果计算失败,返回-9999
  //算法设计思想:
  /*初始化两个栈,操作数栈和运算符栈
若扫描到操作数,压入操作数栈
若扫描到运算符或界限符,则按照“中缀转后缀”相同的逻辑压入运算符栈
(期间也会弹出运算符每当弹出一个运算符时,就需要再弹出两个操作数栈的栈顶元素并执行相应运算,运算结果再压回操作数栈)
  */
  int result = 0;
  SqStack S_nums;  //操作数栈
  SqStack S_op;    //运算符栈
  InitStack(&S_nums);
  InitStack(&S_op);
  Elemtype topElem;
  Elemtype SecondElem;
  Elemtype Operation;
  for (int i = 0; i < strlen(exp); i++) {
    if (exp[i] == ')') {
      GetTop(S_op, &Operation);
      while (Operation != '(') {
        Pop(&S_op, &Operation);
        if (Operation != '(') {
          Pop(&S_nums, &topElem);
          Pop(&S_nums, &SecondElem);
          result = Operate_char2int(SecondElem, Operation, topElem);
          printf("现在执行的是 %c %c %c\n", SecondElem, Operation, topElem);

          Push(&S_nums, result);
        }
      }
    } else if (exp[i] == '(') {
      Push(&S_op, exp[i]);
    } else if (exp[i] == '+' || exp[i] == '-') {
      GetTop(S_op, &Operation);
      if (Operation == '*' || Operation == '/') {
        while (!StackEmpty(S_op) &&
              Operation != '(') {  //栈顶元素优先级更高故出栈
          Pop(&S_op, &Operation);
          Pop(&S_nums, &topElem);
          Pop(&S_nums, &SecondElem);
          result = Operate_char2int(SecondElem, Operation, topElem);
          printf("现在执行的是 %c %c %c\n", SecondElem, Operation, topElem);

          Push(&S_nums, result);
        }
      }
      Push(&S_op, exp[i]);  //并将该元素压入栈
    } else if (exp[i] == '*' || exp[i] == '/') {
      Push(&S_op, exp[i]);  //由于乘除优先级最高,直接进栈
    } else {                //操作数压入操作数栈
      Push(&S_nums, exp[i]);
    }
  }
  while (!StackEmpty(S_op)) {
    Pop(&S_op, &Operation);
    Pop(&S_nums, &topElem);
    Pop(&S_nums, &SecondElem);
    result = Operate_char2int(SecondElem, Operation, topElem);
    printf("现在执行的是 %c %c %c\n", SecondElem, Operation, topElem);

    Push(&S_nums, result);
    //      Push(&S_nums,result);//?result是2,这里压入栈的是\x02
    //      int压入char栈会强转为十六进制
  }
  Pop(&S_nums, &topElem);
  if (StackEmpty(S_nums))
    return result;
  else
    return -9999;
}

调用

int main(){
    // 2.表达式求值
  char suffix[Maxsize] = {0};
  //中缀表达式:a * b + (c - d / e) * f
  //后缀式:  a b * c d e / - f * +
  //  char exp[Maxsize] = {'a', '*', 'b', '+', '(', 'c', '-','d','/', 'e',
  //  ')',
  //  '*', 'f'};
  char exp[Maxsize] = {'2', '+', '(', '5', '-', '3', ')', '/', '2'};
  transform(suffix, exp);
  puts(suffix);
  printf("%s\n", suffix);
  printf("%s计算结果是%d \n", exp, calculte_suffix(suffix));
  printf("%s计算结果是%d \n", exp, calculte_exp(exp));
    return 0 ;
}

3.3.2_经验总结

1.char之间不能直接运算,故需写一个switch语句即func Operation_char2int


2.当int 压入 char栈时,强转十六进制,但类型不是char 而是int


3.当栈中的char 或int
%d和%x均存在时,统一存储格式较麻烦
可在operation处运用isdigit(char c)函数 判断传过来的实参是
(1)char里包含的int,还是
(2)本身是个int,


----isxdigit判断字符串中是否是十六进制字符,还包括A~F,易与英文字符弄混,故这里选用isdigit判断更为合适和便捷


4.数组直接传入的是首地址无需使用引用等,os操作数组是按首地址+i更改地址上的data的
可参见本链接

char ↔ \leftrightarrow int 在c99中的函数分析 :

1)hoit( )函数将十六进制字符串转整型,但该函数并非c标准库


2)atoi( )函数 是c标准库,作用为将字符串强转整型


3)itoa( )函数 头文件stdlib.h可能不包含该函数(将整型转字符串)


4)用sprintf(str[ ],"%d",int)输出的str[ ]或str无法压入栈,因为这里是数组(或数组指针),和char不符


5)sscanf( )函数用于从字符串中读取指定格式的数据,

sscanf可代替atoi,且比atoi适用范围更广 参考链接


本文遇到的问题是char 和 int 混在一起,sscanf接受输入的参数必须是字符串

  sscanf(str,"%d %[a-z]", &num,lowercase);

这里lowercase[]是数组

3.3.3栈在递归中的应用

汉诺塔(未验证)

void hanoi(int n, char A, char B, char C) {
  // 将塔座A上按直径由小到大且至上而下编号为1至n的n个圆盘按规则搬到塔座C上,B用作辅助塔座。
  if (n == 1)
    printf(" move %c to  %c ", A, C);
  else {
    hanoi(n - 1, A, C, B);  // 将A上编号为1至n-1的圆盘移到B,  C作辅助塔
    printf(" move %c to  %c ", A, C);
    hanoi(n - 1, B, A, C);  // 将B上编号为1至n-1的圆盘移到C,  A作辅助塔
  }
}

反向打印数组A[n]的值

void recfunc(int A[], int n) {
  //传入的n是A[]的长度,在调用时需n-1
  if (n == 0)
    return;
  else {
    printf("%d ", A[n - 1]);
    //    cout << A[n] << "  ";//c++
    n--;
    recfunc(A, n);
  }
}

cout是c++独有,与printf区别在于cout无需控制变量类型格式化

可参见本链接

搜索链表最后一个结点并打印其数值(见单链表)

代码略

递归算法求阶乘

//计算正整数n!
int factorial(int n) {
  if (n == 0 || n == 1)
    return 1;
  else
    return n * factorial(n - 1);
}

递归算法求斐波那契数列

int Fib(int n) {
  int result = 0;
  if (n == 0)
    result = 0;
  else if (n == 1)
    result = 1;
  else
    result = Fib(n - 1) + Fib(n - 2);
  printf("现在是n=%d函数在运行,取得的结果是%d\n", n, result);
  return result;
}

更简洁的

int Fbi(int i) {
  if (i < 2)
    return i == 0 ? 0 : 1;
  return Fbi(i - 1) + Fbi(i - 2);
}/* 这里Fbi就是函数自己,等于在调用自己 */

调用

int main(){
    int x = factorial(10);
  printf("%d\n", x);
  int y = Fib(11);  //斐波那契数列第一项n=0,故此处输出的是第n+1项
  printf("%d\n", y);
  printf("%d\n", Fbi(10));
  int A[] = {3, 6, 2, 8, 8, 0, 3};
  recfunc(A, sizeof(A) / sizeof(int));
    return 0;
}

改为非递归过程的目的是提高效率

单向递归和尾递归可直接用迭代实现其非递归过程,其他情形必须借助栈实现非递归过程。
单向递归 是指 递归过程执行时 虽然可能有 多个分支 ,但可以 保存前面计算的结果 以供后面的语句使用。
尾递归 指程序中只有一个递归语句 ,且在程序最后,这时递归栈保存的内容无需再利用

附录:栈的基本操作

/* 顺序栈 */
/*  构造一个空栈S */
bool InitStack(SqStack* S) {
  S->top = -1;
  return true;
}

bool StackEmpty(SqStack S) {
  if (S.top == -1)
    return true;
  return false;
}

/* 返回S的元素个数,即栈的长度 */
int StackLength(SqStack S) {
  return S.top + 1;
}

/* 若栈不空,则用e返回S的栈顶元素,并返回OK;否则返回ERROR */
bool GetTop(SqStack S, Elemtype* e) {
  if (S.top == -1)
    return false;
  else
    *e = S.data[S.top];
  return true;
}

/* 插入元素e为新的栈顶元素 */
bool Push(SqStack* S, Elemtype e) {
  if (S->top == Maxsize - 1) /* 栈满 */
    return false;
  S->top++;            /* 栈顶指针增加一 */
  S->data[S->top] = e; /* 将新插入元素赋值给栈顶空间 */
  return true;
}

/* 若栈不空,则删除S的栈顶元素,用e返回其值,并返回OK;否则返回ERROR */
bool Pop(SqStack* S, Elemtype* e) {
  if (S->top == -1)
    return false;
  *e = S->data[S->top]; /* 将要删除的栈顶元素赋值给e */
  S->top--;             /* 栈顶指针减一 */
  return true;
}

bool visit(Elemtype c) {
  printf("%d ", c);
  return true;
}

/* 从栈底到栈顶依次对栈中每个元素显示 */
bool StackTraverse(SqStack S) {
  int i;
  i = 0;
  while (i <= S.top) {
    visit(S.data[i++]);
  }
  printf("\n");
  return true;
}

/*链栈*/
/*  构造一个空栈S */
bool InitLinkStack(LinkStack* S) {
  S->top = (LinkStackPtr)malloc(sizeof(StackNode));
  if (!S->top)
    return false;
  S->top = NULL;
  S->count = 0;
  return true;
}

/* 把S置为空栈 */
bool ClearLinkStack(LinkStack* S) {
  LinkStackPtr p, q;
  p = S->top;
  while (p) {
    q = p;
    p = p->next;
    free(q);  //为什么不可以直接free p呢?
  }
  S->count = 0;
  return true;
}

/* 若栈S为空栈,则返回TRUE,否则返回FALSE */
bool LinkStackEmpty(LinkStack S) {
  if (S.count == 0)
    return true;
  else
    return false;
}

/* 返回S的元素个数,即栈的长度 */
int LinkStackLength(LinkStack S) {
  return S.count;
}

/* 若栈不空,则用e返回S的栈顶元素,并返回OK;否则返回ERROR */
bool GetTop_LinkStack(LinkStack S, Elemtype* e) {
  if (S.top == NULL)
    return false;
  else
    *e = S.top->data;
  return true;
}

/* 插入元素e为新的栈顶元素 */
bool Push_LinkStack(LinkStack* S, Elemtype e) {
  LinkStackPtr s = (LinkStackPtr)malloc(sizeof(StackNode));
  s->data = e;
  s->next = S->top; /* 把当前的栈顶元素赋值给新结点的直接后继,见图中① */
  S->top = s; /* 将新的结点s赋值给栈顶指针,见图中② */
  S->count++;
  return true;
}

/* 若栈不空,则删除S的栈顶元素,用e返回其值,并返回OK;否则返回ERROR */
bool Pop_LinkStack(LinkStack* S, Elemtype* e) {
  LinkStackPtr p;
  if (LinkStackEmpty(*S))
    return false;
  *e = S->top->data;
  p = S->top; /* 将栈顶结点赋值给p,见图中③ */
  S->top = S->top->next; /* 使得栈顶指针下移一位,指向后一结点,见图中④ */
  free(p); /* 释放结点p */
  S->count--;
  return true;
}

bool LinkStackTraverse(LinkStack S) {
  LinkStackPtr p;
  p = S.top;
  while (p) {
    visit(p->data);
    p = p->next;
  }
  printf("\n");
  return true;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值