| 这个作业属于哪个班级 | 数据结构-网络20 |
| ---- | ---- | ---- |
| 这个作业的地址 | DS博客作业02--栈和队列 |
| 这个作业的目标 | 学习栈和队列的结构设计及运算操作 |
| 姓名 | 余智康 |

目录

0. 展示PTA总分

1. 本章学习总结

2. PTA实验作业

3. 阅读代码



0.PTA得分截图



1.本周学习总结

1.1 栈

画一个栈的图形,介绍如下内容。

  1. 顺序栈的结构:
    • 顺序栈是使用数组来存放栈内元素,需要一个整型变量top来记录目前栈顶的位置。
    • 由于顺序栈需要考虑数组的容量,需要一个maxsize来记录最大能存放的数据个数
      则,顺序栈的结构体定义如下:
  typedef struct {
    int data [MAXSIZE];  //以整型数据为例,若栈需要存放其他类型的数据,修改data[]的类型,如:char data[MAXSIZE]
                         //MAXSIZE 可以用 #define 来定义,这样便于修改。也可以直接用整型数代替,如:int data[12]
    int top;        //栈空时,top的值为-1
    int maxSize = MAXSIZE;  //存放最大数据个数,便于判断栈满情况
  }SqStack, * stack;

  • 顺序栈的操作函数
  • 判断栈空:
  bool stack_empty(stack sta)    //如果栈空,返回 1.否则返回 0
  {
      if(sta -> top == -1)    return true;
      else  return false;  
  }
  • 判断栈满:
  bool stack_full(stack sta)    //如果栈满,返回 1.否则返回 0
  {
      if(sta -> top == sta -> maxSize - 1)  return true;
      else return false;
  }

  • 进栈:
  bool stack_push(stack sta, int x)  //进栈成功,返回 1, 失败返回 0
  {
      if(sta -> top == sta -> maxSize - 1)  //栈满情况,入栈失败
        return false;
      
      sta -> data [ ++ sta -> top] = x;
      return true;
  }

  • 出栈:
  bool stack_pop(stack sta, int &e)  //出栈成功,将出栈的元素保存在e中, 返回 1, 失败返回 0
  {
      if(sta -> top == -1)    //栈空情况,出栈失败
        return false;

      e = sta -> data[ sta -> top --];
      return true;
  }
  • 共享栈:
  • 介绍:
  • 顺序栈需要考虑栈的容量,而栈的容量又不总会恰恰好与所填入的数据元素的个数相同,从而会产生空间的不足或是浪费
  • 为增大空间的利用率,有了顺序栈的一种新的形态——“共享栈”

借用《大话数据结构》作者的比喻,普通的顺序栈就像是单身公寓,独立的卧室、厨卫、客厅。而共享栈好比双人套房,各自独立的卧室,共享的客厅、厨卫。

  • 特点:

  • 共享栈的两个栈顶 top1 和 top2 分别从 -1 和 MAXSIZ (栈的最大容量)开始

  • 当 top1 + 1 == top2 时,栈满

  • 共享栈的结构体定义中有两个整型的top变量,top1,top2

  • 在进栈和入栈的函数中,需要输入一个用来判断入栈1还是栈2的变量

  • 图像:

    //代码, 部分细节与顺序表相同

/*入栈*/ bool sta_push(stack sta, int x, int sta_select)
        {
            if (sta->top1 + 1 == sta->top2)    
                return false;               //栈满

            if (sta_select == 1)
                sta->data[++sta->top1] = x;

            else if (sta_select == 2)
                sta->data[--sta->top2] = x;

            return true
        }

/*出栈*/ bool sta_pop(stack sta, int &e, int sta_select)
         {
             switch (sta_select)
             {
              case 1:
                  if (sta->top1 == -1) 
                      return false;         //栈1空
                  e = sta->data[sta->top1--];
                  return true;

              case 2:
                  ...
             }
         }

  1. 链栈的结构:

  • 介绍;

    • 链栈顾名思义是用链表来存放数据元素的栈,数据元素的入栈和出栈即是对链表的插入和删除
    • 与顺序栈相比,链栈不需要考虑栈满的状态
    • 为满足先进后出的原则,链栈一般使用尾插法实现入栈
  • 链栈的结构体:

  typedef struct linkNode{
      int data;    //以整型数据为例,若栈需要存放其他类型的数据,修改data的类型,如:char data
      struct linkNode* next;
  }LiNode, *LiStack;
  • 链栈的操作与单链表类似:
  • 链栈的初始化:
 void InitStack(LiStack &s)
 {
    s = new LiNode;
    s -> next = NULL;
 }
  • 链栈的销毁:
  void DestroyStack(LiStack &s)
  {
      LiStack node;
      while ( s! = NULL)
      {
          node = s;
          s = s -> next;
          delete node;
      }
  }
  • 链栈的空:
  bool StackEmpty(LiStack s)
  {
      if(s == NULL)
          return true;
      else
          return false;
  }
  • 链栈的进栈:
  void StackPush( LiStack &s, int x)  // x 进栈
  {
      LiStack temp;
      temp = new LiNode;

      temp -> data = x;
      temp -> next = s -> next;
      s -> next = temp;  
  }

  • 链栈的出栈:
  bool StackPop( LiStack &s, int &e)  //出栈,并将栈顶元素赋值给e
  {
       LiStack temp = s -> next;      
        
       if (s -> next == NULL)
          return false;        //需要判断空栈的情况,一开始我忘记了判断链表为空

       e = s -> next -> data;
       s -> next = s -> next -> next;

       delete temp;
       return true;
  }

1.2 栈的应用

  • 后缀表达式

  • 大致思路:

    • 给每个符号设定优先级
    • 遇到符号时,判断符号与栈顶符号的优先级,决定将符号入栈,还是将出栈
    • 当符号优先级高于栈顶符号的优先级时,该符号入栈。
    • 若符号优先级低于栈顶符号优先级时,出栈,直到该符号的优先级高于栈顶符号
    • 未避免空栈的影响,可以将'#'的优先级设为最低,并将'#'放置在栈底
  • 具体思路:

    • 需要考虑负数的情况(类似于上学期的切分表达式):负数会出现在第一个数或者是括号后面,可以使用标志(flag)来处理
    • 需要考虑小数、以及十位数及以上的情况:可以在处理数字字符的if分支中使用continue
    • 需要考虑符号和栈顶符号相同时优先度的判断:每个优先级可以设置间隔为2,栈外面的符号优先级减1
    • 需要考虑括号的情况:可以将括号的优先度设为仅比'#'高,当遇到左括号'('时,不进行优先度判断,直接入栈
    • 需要考虑优先级的设置:创建数组存放(level['#'] = -4;)(一开始我想使用动态分配7个位置存放优先度的,节省点空间。只是DEV和VS可以通过,但PTA会出现段错误,就改成直接分配128个位置给level[]了
  • 主要代码:

for (count = 0; count < formula_mid.size(); count++)
	{
		if (flag)			//负号的处理
		{
			flag = 0;
			if (formula_mid[count] == '+')
			{
				continue;
			}
			else if (formula_mid[count] == '-')
			{
				formula_back += formula_mid[count];
				continue;
			}
		}
		if (formula_mid[count] == '(')	//括号的处理
		{
			flag = 1;
			op_sta.push('('); 
			continue;
		}

		if ((formula_mid[count] >= '0' && formula_mid[count] <= '9') || formula_mid[count] == '.')	// 小数和多位数的处理
		{
			formula_back += formula_mid[count];
			continue;
		}
		if(!((formula_mid[count] >= '0' && formula_mid[count] <= '9') || formula_mid[count] == '.'))   //其他符号处理
		{
			while (level[formula_mid[count]] - 1 < level[op_sta.top()])		//小于栈顶优先度,出栈
			{
				if (op_sta.top() == '(')
				{
					op_sta.pop();		// 括号不入后缀表达式
					continue;
				}

				formula_back += ' ';
				formula_back += op_sta.top();
				op_sta.pop();
			}

			if (level[formula_mid[count]] - 1 > level[op_sta.top()])	//大于,除了')'外,入栈
			{
				if (formula_mid[count] == ')')
					continue;
				op_sta.push(formula_mid[count]);
			}
		}

		formula_back += ' ';
	}
	//栈内剩余元素
	while (!op_sta.empty() && op_sta.top() != '#')
	{
		formula_back += ' ';
		formula_back += op_sta.top();
		op_sta.pop();
	}

	cout << formula_back;
	return 0;
}

1.3 队列(

  1. 顺序队列
  • 介绍:

    • 顺序队列:front位置为队头元素,rear的位置为队尾元素的后一位
    • 队空条件:front == rear
    • 队满条件:rear == MAXSIZE - 1
  • 顺序队列的结构:

    typedef struct queue{
       int data[MAXSIZE];
       int front,rear;
}Queue;
  • 顺序队列初始化:
  void InitQueue(SqQueue &q)
  {
      q = new Queue;
      q -> front = q -> rear = -1;
  }
  • 顺序队列入队:
  bool PushQueue(SqQueue &q, int x)
  {
      if(q -> rear == MAXSIZE -1)
          return false;
      q -> data[q->rear++] = x;
      return true;
  }
  • 顺序队列出队:
  bool PopQueue(SqQueue &q, int &e)
  {
      if(q -> rear == q -> front)
          return false;
      e = q -> data[q->front++];
      return true;
  }
  1. 环形队列
  • 介绍:

    • 环形,空间利用率大,解决的顺序队列存在的“假溢出”的问题
    • 队空条件:front == rear
    • 队满条件:(rear + 1) % MAXSIZE == front
  • 环形队列列初始化:

  void InitQueue(SqQueue &q)
  {
      q = new Queue;
      q -> front = q -> rear = 0;
  }
  • 环形队列入队:
  bool PushQueue(SqQueue &q, int x)
  {
      if((q > rear + 1) % MAXSIZE == q -> front)
          return false;
      q -> rear = (q -> rear + 1) % MAXSIZE;
      q -> data[q->rear] = x;
      return true;
  }
  • 环形队列出队:
  bool PopQueue(SqQueue &q, int &e)
  {
      if(q -> front == q-> rear)
          return false;
      e = q -> data[q->front];
      q -> front = (q->front + 1) % MAXSIZE;
      return true;
  }
  1. 链队列
  • 介绍:

    • 入队使用尾插法

    • 队空条件:front == rear == NULL

    • 队满条件:不考虑

  • 链队的结构:

//数据结点
    typedef struct qNode{
        int data;
        struct qNode *next;
    }QNode;

//头尾指针
    typedef struct{
        QNode *front;
        QNode *rear;
}LinkQueue;

  • 链队的入队:
  void PushQueue(LinkQueue &Q, int x)
  {
      QNode* temp = new QNode;
      temp -> data = x;
      
      Q.rear => next = temp;
      Q,rear = Q.rear -> next;
      Q.rear -> next = NULL;
  }
  • 链队的出队:
  bool PopQueue(LinkQueue &Q, int &e)
  {
      QNode* temp = Q.front -> next;
      Q -> front -> next = temp => next;
      
      e = temp -> data;
      delete temp;
  }

1.4 队列的应用(

  • 报数游戏
  • 思路:
    1. 建立队列1,里面存放编号1~N的人,
    2. 开始报数,删除的人放入del_thing[]数组中,未被删除的出队列1,进入队列2
    3. 返回队列2,并把队列2赋值给队列1,删除队列2,新建队列2
    4. 循环步骤2、3,直到所有人都被删除
  • 主要代码:
	if (game.del_num >= game.maxSize)    //超出,输出错误,结束程序
	{
		cout << "error!" << endl;
		return 0;
	}

	while (game.del_count < game.maxSize)  //步骤4,即循环步骤2、3
	{
		queue <int> que_empty;
		que = *PushQueue(game, que, que_empty);

		while (!que_empty.empty())
		{
			que_empty.pop();
		}
	}


 // 主要函数, 将人放在que中
    queue <int>* PushQueue(GAME& game, queue<int> que_1, queue<int>& que_2)
    {
	while (!que_1.empty())
	{
		game.count_num++;
		if (game.count_num % game.del_num == 0)
		{
			game.del_thing[game.del_count++] = que_1.front();
			que_1.pop();

			continue;
		}

		que_2.push(que_1.front());
		que_1.pop();
	}

	return &que_2;
    }



2.PTA实验作业

2.1 符号配对

https://gitee.com/welcome_to_tommrow/pta-code/blob/master/7-3 符号配对.cpp

2.1.1 解题思路及伪代码

  • 解题思路:
    • 将输入放入string字符串中,再进行遍历
    • 遍历时,发现左半符号,则左半符号入栈。
    • 遍历判断符号时,需要判断当前位置和下一个位置的符号,以便发现注释符(/**/)
    • 对于注释符,为了方便操作,可以使用其它非题中的符号代为入栈。当然,直接入栈也是可以的,相差不多
    • 遇到右边符号时,先检查栈内是否为空,若为空,则缺少左符号,返回false
    • 反之,再与栈顶符号比对。若符合,则出栈,不符合,输出缺少右符号,返回false
    • 当遍历完成之后,检查栈内是否还有元素,若仍有元素,输出缺少右符号,返回false
  • 伪代码:
 for count = 0 to procedure.size()

 // push stack
	// 1)
	if procedure[count] == '/' && procedure[count+1] == '*'
		push stack
                push stack
                continue
	end if

	// 2)
	else if sign_left[1].find(procedure[count]) < 3 && >= 0
		push stack
                continue
	end else if

 // pop stack, (need to check whether stack is empty or not?)
 // situation 1: short of left_sign

	// 1)

	if procedure[count] == '*' && procedure[count+1] == '\'

		if stack.empty() || !(stack.top == '*')
			cout <<"NO\n" << "?-*/\n"
			return false
		end if

                else if !(stack.top == '*')
                        cout << "NO\n" << sta_sign.top() << "-?\n"
                        return false;
                end else

		else if(stack.top == '*')
			stack.pop
			stack.pop
                        count++
                        continue
		end else

	end if

	// 2)
	if (position = sige_right[1].find(procedure[count])) < 3 && >=0

		if stack.empty() 
			shour of stack_right
			return false
		end if

                else if  !(stack.top == sign_left[1][position])  
                        if(sta_sign.top() == '*')
				cout << "NO\n" << "/*-?\n"
				
			else
				cout << "NO\n" << sta_sign.top() << "-?\n"
			return false;

		else if(stack.top == sign_left[1][position])
			stack.pop
		end else
	end if

 end for

 // situation 2: shor of left_sign

 if !stack.empty()
	// 1)
	if stack.top == '*'
		short of "/*"
		return false
	end if

	else
		position = sign_left[1].find(stack.top)
		short of sign_right[1][position]
		return false
	end if

 end if

2.1.2 总结解题所用的知识点

  • C++中字符串的使用,以及输入一串带空格的字符串,放入变量line中
    string line;
    getline(cin, line);

    line.empty();
    line.find('A');
    line.size();
  • 入栈、出栈,以及栈空的判断
  • 如何进行配对

2.2 银行业务队列简单模拟

https://gitee.com/welcome_to_tommrow/pta-code/blob/master/7-6 银行业务队列简单模拟.cpp

2.2.1 解题思路及伪代码

  • 解题思路: 依编号分配A、B,然后A处理两只,B处理一只,直到某个为空,剩余处理
  • 伪代码:
  // PushQue 将数据由字符型改为整型,放入队列A 和队列B中
  for i = 0 to line.size()
      num = 0

      if line[i] >= '0' && line[i] <= '9'
          while line[i] >= '0' && line[i] <= '9'  do
              num = num = line[count++] - '0' + num * 10
          end while

          if num % 2 == 0
              bank.que_B.push(num);    //偶数编号存放入B中
          else
              bank.que_A.push(num);    //奇数编号存放入A中
          end if
      end if
  end for
  //将处理完的,放入list数组中
  while !bank.que_A.empty() && !bank.que_B.empty()  do
      time = 0
  
       while  !bank.que_A.empty() && time < 2
            bank.list[count++] = bank.que_A.front();
	    bank.que_A.pop();
	    time++;
       end while

       bank.list[count++] = bank.que_B.front();
       bank.que_B.pop();
  end while

  //若队列A不为空
  while !bank.que_A.empty()  do
      bank.list[count++] = bank.que_A.front();
      bank.que_A.pop();
  end while

  //若队列B不为空
  while !bank.que_B.empty()  do
      bank.list[count++] = bank.que_B.front();
      bank.que_B.pop();
  end while

2.2.2 总结解题所用的知识点


  • 将字符型的数字转化为整型
    while (line[count] >= '0' && line[count] <= '9')
    {
	num = line[count++] - '0' + num * 10;
    }
  • string类型字符串的遍历
    for (count = 0; count < line.size(); count++)
    {
          ...
    }

  • 两组数据的以不同的速度合并
  while (!que_A.empty() && !que_B.empty())
  {
	time = 0;
	while (!que_A.empty() && time < 2)
	{
	      ...
	      time++;
        }

        if (!bank.que_B.empty())
	{
	      ...
	}
  }

3.阅读代码

3.1 题目及解题代码

  • 题目: 接雨水

给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。
输入:height = [0,1,0,2,1,0,1,3,2,1,2,1]
输出:6
解释:上面是由数组 [0,1,0,2,1,0,1,3,2,1,2,1] 表示的高度图,在这种情况下,可以接 6 个单位的雨水(蓝色部分表示雨水)

  • 代码:

int trap(int* height, int heightSize) {
    int n = heightSize;
    if (n == 0) {
        return 0;
    }
    int ans = 0;
    int stk[n], top = 0;
    for (int i = 0; i < n; ++i) {
        while (top && height[i] > height[stk[top - 1]]) {
            int stk_top = stk[--top];
            if (!top) {
                break;
            }
            int left = stk[top - 1];
            int currWidth = i - left - 1;
            int currHeight = fmin(height[left], height[i]) - height[stk_top];
            ans += currWidth * currHeight;
        }
        stk[top++] = i;
    }
    return ans;
}

//作者:LeetCode-Solution
//链接:https://leetcode-cn.com/problems/trapping-rain-water/solution/jie-yu-shui-by-leetcode-solution-tuvc/
//来源:力扣(LeetCode)
//著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

3.2 该题的设计思路及伪代码

  • 设计思路:

    • 遍历数组height[],在栈内至少有两个元素的情况下,记栈顶元素下面的一个元素为left
    • 当height[i] 小于等于 栈顶元素时入栈
    • 当height[i] 大于栈顶元素height[top]时,得到雨水区域
    • 宽度:i - left -1
    • 高度:min(height[left],height[i]) - height[top]
    • 即可根据高度和宽度得到雨水量
    • 计算结束后,出栈,left成为新的栈顶,继续操作,直到栈为空,或是height[i] 小于等于 栈顶元素
  • 伪代码:

    int n = heightSize  //最大宽度
    if (n == 0)  then
        return 0
    end if
    
    int ans = 0
    int stk[n], top = 0

    for i = 0 to n-1
        while  top && height[i] > height[stk[top - 1]]    do
            int stk_top = stk[-top]
            if(!top)
                break
            end if

            int left = stk[top - 1]
            int currWidth = i - left -1      //宽度
            int currHeight = fmin(height[left], height[i]) - height[stk_top];                             //高度
            ans += currWidth * currHeight;
        end while
    stk[top++] = i

    end for
    return ans
        

3.3 分析该题目解题优势及难点。

  • 优势:
    • 条理清晰,便于理解
    • 巧妙地利用了栈解决了雨水量的问题
  • 难点:
    • 如何利用栈完成雨水的测量
    • 入栈的条件、出栈的条件,结束的条件