Data structure advanced training course notes and algorithm exercises

Source Code: https://github.com/MysticalGuest/DataStructure/tree/master/AdvancedJuniorTraining


题目1

假设算术表达式只包含“+”、“-”、“*”、“/”,正整数和括号的合法数学表达式。根据算符优先关系,
  - 将算术表达式的中缀表示法转换为后缀表示法。
  - 对得到的后缀表达式进行求值

1.1 算法设计思想

1.1.1 转后缀表达式:

  • 从左到右扫描每一个字符。如果扫描到的字符是操作数(如a、b等),就直接输出这些操作数。
  • 如果扫描到的字符是一个操作符,分三种情况:
    (1)如果堆栈是空的,直接将操作符存储到堆栈中(pushCStack it)
    (2)如果该操作符的优先级大于堆栈出口的操作符,就直接将操作符存储到堆栈中(pushCStack it)
    (3)如果该操作符的优先级低于堆栈出口的操作符,就将堆栈出口的操作符导出(popCStack it),直到该操作符的优先级大于堆栈顶端的操作符。将扫描到的操作符导入到堆栈中(pushCStack)
  • 如果遇到的操作符是左括号"(”,就直接将该操作符输出到堆栈当中。
    该操作符只有在遇到右括号“ )”的时候移除。这是一个特殊符号该特殊处理。
  • 如果扫描到的操作符是右括号“ ”,将堆栈中的操作符导出(popCStack)到output中输出,直到遇见左括号“(”。将堆栈中的左括号移出堆栈(popCStack )。继续扫描下一个字符。
  • 如果输入的中缀表达式已经扫描完了,但是堆栈中仍然存在操作符的时候,我们应该讲堆栈中的操作符导出并输入到output 当中。

1.1.3 求值

后缀表达式求值的算法是:
遍历后缀表达式,如果遇到运算数,那么运算数入栈
如果遇到运算符,那么弹出栈里面两个元素,先弹出的是右运算数,后弹出的是左运算数,计算运算结果,然后将结果入栈
最后遍历到后缀表达式末尾,当结果只有一个元素时,就是答案

1.2 源代码

#define StackSize 50
#define INFINITY 32768

// 定义运算符栈
typedef struct{
  char elem[StackSize];
  int top;
}SeqCStack;

void InitCStack(SeqCStack *S){
  S->top=-1;
}

void pushCStack(SeqCStack *S, char operator){
  if(S->top == StackSize - 1){   // 栈满
    return ;
  }
  else{
    S->top++;
    S->elem[S->top] = operator;
    return ;
  }
}

void popCStack(SeqCStack *S, char *e){
  if(S->top == -1){   // 栈空
    return ;
  }
  else{
    *e = S->elem[S->top];
    S->top--;
    return ;
  }
}

char getCStackTop(SeqCStack S){
  if(S.top == -1){   // 栈空
    return '#';
  }
  else{
    return S.elem[S.top];
  }
}

void traverse(SeqCStack S){
  int i=0;
  while(i <= S.top){
    printf("%c ", S.elem[i++]);
  }
  printf("\n");
}

// 定义运算数栈
typedef struct{
  int data[StackSize];
  int top;
}SeqNStack;

void InitNStack(SeqNStack *S){
  S->top=-1;
}

void pushNStack(SeqNStack *S, int num){
  if(S->top == StackSize - 1){   // 栈满
    return ;
  }
  else{
    S->top++;
    S->data[S->top] = num;
    return ;
  }
}

void popNStack(SeqNStack *S, int *e){
  if(S->top == -1){   // 栈空
    return ;
  }
  else{
    *e = S->data[S->top];
    S->top--;
    return ;
  }
}

int getNStackTop(SeqNStack S){
  if(S.top == -1){   // 栈空
    return INFINITY;
  }
  else{
    return S.data[S.top];
  }
}

char compare(char operator, char top){
  if(top == '#')   // 空栈,操作符直接进栈
    return '>';
  else if(operator==')' && top=='(')
    return '=';
  else if(top=='(')
    return '>';
  else if(operator=='+')    // 如果操作符是'+', 无论栈顶元素是什么, '+'优先级都小
    return '<';
  else if(operator=='-')
    return '<';
  else if(operator=='*' && top=='+')
    return '>';
  else if(operator=='*' && top=='-')
    return '>';
  else if(operator=='*' && top=='*')
    return '<';
  else if(operator=='*' && top=='/')
    return '<';
  else if(operator=='*' && top=='(')
    return '<';
  else if(operator=='/' && top=='+')
    return '>';
  else if(operator=='/' && top=='-')
    return '>';
  else if(operator=='/' && top=='*')
    return '<';
  else if(operator=='/' && top=='(')
    return '<';
  else if(operator=='(')
    return '>';
  else if(operator==')')
    return '<';
}

int caculate(int left, int right, char c){
  int re = 0;
  switch (c){
  case '+':
    re = left + right;
    break;
  case '-':
    re = left - right;
    break;
  case '*':
    re = left * right;
    break;
  case '/':
    re = left / right;
    break;
  default:
    break;
  }
  return re;
}

void main(){
  SeqCStack OS, SuffixExp;
  SeqNStack NS;
  /* 初始化运算符栈 */
  InitCStack(&OS);
  /* 初始化运算数栈 */
  InitNStack(&NS);
  /* 初始后缀表达式栈 */
  InitCStack(&SuffixExp);

  char exp[] = {'5', '+', '2', '*', '(', '1', '+', '6', ')', '-', '8', '/', '2', '\0'};
  printf("Infix expression is: %s\n", exp);
  char suffixstr[50], temp;
  int i = 0, tempNum;
  while (exp[i]!='\0'){
    if(isdigit(exp[i])){    // 如果是数字直接进后缀表达式栈
      pushCStack(&SuffixExp, exp[i]);
      // printf("num------%c\n", exp[i]);
      i++;
    }
    else{
      // printf("char------\n");
      // printf("compare----%c\n", compare(exp[i], getCStackTop(OS)));
      switch(compare(exp[i], getCStackTop(OS))){
        case '>': 
          pushCStack(&OS, exp[i]);
          // printf("case1 >---%c\n", exp[i]);
          i++;
          break;
        case '=': 
          popCStack(&OS, &temp);   // 脱括号
          // printf("case2 =---%c\n", temp);
          i++;
          break;
        case '<':
          while(compare(exp[i], getCStackTop(OS))=='<'){
            // printf("case3 <---%c\n", exp[i]);
            // printf("case3 getCStackTop %c\n", getCStackTop(OS));
            popCStack(&OS, &temp);
            // printf("case3 after getCStackTop %c\n", getCStackTop(OS));
            pushCStack(&SuffixExp, temp);
          }
          // if(exp[i]!=')'){i++;}
          break;
      }
    }
  }
  /* 最后把栈中剩余的运算符依次弹栈打印 */
  while(getCStackTop(OS)!='#'){
    popCStack(&OS, &temp);
    pushCStack(&SuffixExp, temp);
  }
  traverse(SuffixExp);
  for(i=SuffixExp.top; i>=0; i--){
    popCStack(&SuffixExp, &temp);
    suffixstr[i] = temp;
  }
  printf("Infix expression to suffix expression is: %s\n", suffixstr);
  /* 后缀表达式求值的算法是: 遍历后缀表达式, 如果遇到运算数,那么运算数入栈 如果遇到运算符,那么弹出栈里面两个元素,先弹出的是右运算数,后弹出的是左运算数, 计算运算结果,然后将结果入栈。 最后遍历到后缀表达式末尾,当结果只有一个元素时,就是答案 */
  char *p=suffixstr;
  while (*p != '\0'){
    if (isdigit(*p)){
      pushNStack(&NS, *p-'0');
    }
    else{
      popNStack(&NS, &tempNum);
      int rightNum = tempNum;
      // printf("rightNum:::%d\n", rightNum);
      // free(temp);
      popNStack(&NS, &tempNum);
      int leftNum = tempNum;
      // free(temp);
      
      int result = caculate(leftNum, rightNum, *p);
      // printf("caculate result----%d\n", result);
      pushNStack(&NS, result);
    }
    p++;
  }
  printf("result: %d\n", getNStackTop(NS));

  system("pause");
}

1.3 运行情况截图



题目2

设L为带头结点的单链表,实现从尾到头反向输出链表中每个结点的值。(递归思想)

2.1 算法设计思想

递归语句在打印之前就可以了

2.2 源代码

void printReversely(LinkList L){
  if(L->next!=NULL){
    printReversely(L->next);
    printf("%c ", L->next->data);
  }
}

2.3 运行情况截图



题目3

一只青蛙一次可以跳上1级台阶,也可以跳上2级。
编写代码求青蛙跳上一个n级的台阶,总共有多少种跳法?
  - 若条件改为:
  一只青蛙一次可以跳上1级台阶,也可以跳上2级,也可以跳上3级,...,也可以跳上n级。
  编写代码求青蛙跳上一个n级的台阶,总共有多少种跳法?

3.1 算法设计思想

Q:

一只青蛙一次可以跳上1级台阶,也可以跳上2级……它也可以跳上n级。
求该青蛙跳上一个n级的台阶总共有多少种跳法。

A:

f(n) = f(n-1)+f(n-2)+…+f(1)
f(n-1) = f(n-2)+ f(n-3)…+f(1)
两式相减,得到f(n) = 2*f(n-1)

3.2 源代码

int Jump(int i, int n) {
  //表示当前台阶数大于总台阶数,很显然这种情况不符合,走不通,记为 0
  if (i > n) {
      return 0;
  }
  //表示当前台阶数正好等于总的台阶数,那么这种情况符合,记为 1
  if (i == n) {
      return 1;
  }
  return Jump(i + 1, n) + Jump(i + 2, n);
}

int JumpN(int num){
  if (num == 1){
    return 1;
  }
  else{
    return 2*JumpN(num-1);
  }
}

3.3 运行情况截图



题目4

用一个2X1的小矩形横着或竖着去覆盖更大的矩形。如下图
  - 具体:用8个2X1小矩形横着或竖着去覆盖2X8的大矩形,覆盖方法有多少种?
  - 编写代码求用2X1小矩形横着或竖着去覆盖2Xn的大矩形。输出总共有多少种覆盖方法

4.1 算法设计思想

当n=1时,覆盖方法有1种;
当n=2时,覆盖方法有2种;
当n=3时,覆盖方法有2+1=3种;
当n=4时,覆盖方法有3+2=5种;
按照规律就转化成了斐波那契数列问题

4.2 源代码

int Cover(int n){
  if(n<=0){
    return 0;
  }
  else if(n==1||n==2){
    return n;
  }
  else{
    return Cover(n-1) + Cover(n-2);
  }
}

4.3 运行情况截图



题目5

借助自定义栈以非递归形式求解汉诺塔问题(n,a,b,c);
即将n个盘子从起始塔座a通过辅助塔座b移动到目标塔座c,
并保证每个移动符合汉诺塔问题的要求

5.1 算法设计思想

利用递归的思想,用栈来处理;
比如n=3时,转化的问题是:
先要移动A塔座上面2个盘子到B塔座,这个操作进栈后续处理;
然后移动A塔座上面最后一个大盘子到C塔座,这个操作进栈后续处理;
最后再移动B塔座上最后两个盘子到C塔座;
一直访问栈,如果栈顶处理的盘子数不是1,就在把操作细分,进栈;
直到盘子数为1,移动盘子;
直到栈为空

5.2 源代码

// 定义汉诺塔数据
typedef struct{
  char A;
  char B;
  char C;
  int n;
}HanoiData;

// 定义栈
typedef struct{
  HanoiData elem[StackSize];
  int top;
}SeqStack;

void InitStack(SeqStack *S){
  S->top=-1;
}

void push(SeqStack *S, HanoiData hd){
  if(S->top == StackSize - 1){   // 栈满
    return ;
  }
  else{
    S->top++;
    S->elem[S->top] = hd;
    return ;
  }
}

void pop(SeqStack *S, HanoiData *e){
  if(S->top == -1){   // 栈空
    return ;
  }
  else{
    *e = S->elem[S->top];
    S->top--;
    return ;
  }
}

// HanoiData getTop(SeqStack S){
// if(S.top == -1){ // 栈空
// return ;
// }
// else{
// return S.elem[S.top];
// }
// }

void move1(int n,char A,char B,char C){
  if(n==1){
    printf("%c-->%c\n",A,C);	
  }
  else{
    move1(n-1,A,C,B);
    move1(1,A,B,C);
    move1(n-1,B,A,C);
  }	
}

void hanoi(int n){
  SeqStack S;
  InitStack(&S);
  HanoiData h = {'A', 'B', 'C', n};
  push(&S,h);//初始栈 
  // hanoi_data x;//用来保存出栈的n,A,B,C 

  while(S.top!=-1){
    pop(&S, &h);//出栈并用x带回 
    if(h.n==1){
      printf("%c-->%c\n",h.A,h.C);//打印出移动方案 
    }
    else{
      HanoiData h1 = {h.B, h.A, h.C, h.n-1};
      push(&S,h1);
      HanoiData h2 = {h.A, h.B, h.C, 1};
      push(&S,h2);
      HanoiData h3 = {h.A, h.C, h.B, h.n-1};
      push(&S,h3);
    }
  }
}

5.3 运行情况截图