0.PTA得分截图

1.本周学习总结

1.1 总结栈和队列内容

一.栈:

栈是一种只能在栈顶一端进行插入或删除操作的线性表。
栈中元素遵循先进后出的规则。元素未完全进栈时即可出栈

栈的存储结构:

1.顺序栈:

  • 顺序栈结构体定义:
typedef struct 
{  ElemType data[MaxSize]; //栈中数据元素
   int top;		//top为栈顶指针
} Stack;
typedef Stack *SqStack;

  • 顺序栈四大要素:
    1.栈空条件:top=-1
    2.栈满条件:top=MaxSize-1
    3.进栈e操作:top++; st->data[top]=e
    4.退栈操作:e=st->data[top]; top--;

  • 顺序栈基本操作

1.初始化栈:

void CreateStack(SqStack& s)
{
	s = new Stack;
	s->top = -1;
}

2.判断栈空:

int EmptyStack(SqStack s)
{
	if (s->top == -1)
		return 1;
	else
		return 0;
}

3.判断栈满:

int FullStack(SqStack s)
{
	if (s->top == MaxSize-1)
		return 1;
	else
		return 0;
}

4.元素入栈:

bool Push(SqStack& s, ElemType e)
{
	if (s->top == MaxSize - 1)
		cout << "FULLSTACK";
		return false;
	s->top++;		   //栈顶指针增1
	s->data[s->top] = e;
	return true;
}

5.元素出栈:

bool Pop(SqStack &s,ElemType &e)
{
   if (s->top==-1)	//栈空的情况
	return false;
   e=s->data[s->top];   
   s->top--;
   return true;
}

6.取栈顶:

bool GetTop(SqStack *s,ElemType &e)
{	
   if (s->top==-1)	//判断栈空 
    return false;
    e=s->data[s->top];//栈顶元素赋值为e
    return true;
}

7.销毁栈:

void ClearStack(SqStack& s)
{
	delete s;
}

8.共享栈:

*共享栈类型定义:

typedef struct
{     ElemType data[MaxSize];	//存放共享栈中元素
       int top1,top2;		//两个栈的栈顶指针
} DStack;	

*共享栈结构体定义:

struct SNode {
    ElementType *Data;
    Position Top1, Top2;
    int MaxSize;
};
typedef struct SNode *Stack;

*共享栈三要素:
1.栈1空:top1-1
2.栈2空:top2
MaxSize
3.栈满:top1+1=top2

2.链式栈(带头节点):

  • 链式栈的结构体定义:
typedef struct linknode
{  ElemType data;			//数据域
   struct linknode *next;	//指针域
} LiNode,*LiStack;

  • 链式栈四大要素:
    1.栈空条件:s->next=NULL
    2.栈满条件:不考虑
    3.进栈e操作:结点插入到头结点后,链表头插法
    4.退栈操作:取出头结点之后结点的元素并删除

  • 链式栈基本操作:

1.初始化栈:

void InitStack(LiStack &s)
{  s=new LiNode;
   s->next=NULL;
}

2.判断栈空:

int StackEmpty(LiStack s)
{
	if (s->next == NULL)
		return 1;
	else
		return 0;
}

3.元素入栈:

void Push(LiStack& s, ElemType e)
{
	LiStack p;
	p = new LiNode;
	p->data = e;		//新建节点p
	p->next = s->next;	//插入*p节点作为开始节点
	s->next = p;
}

4.元素出栈:

bool Pop(LiStack& s, ElemType& e)
{
	LiStack p;
	if (s->next == NULL)		//栈空的情况
		return false;
	p = s->next;			//p指向开始节点,从栈顶开始出栈
	e = p->data;
	s->next = p->next;		//删除*p节点
	free(p);				//释放*p节点
	return true;
}

5.取栈顶元素:

bool GetTop(LiStack s,ElemType &e)
{  if (s->next==NULL)	//栈空的情况
	return false;
   e=s->next->data;
   return true;
}

6.销毁栈:

void DestroyStack(LiStack &s)
{ LiStack p;
   while (s!=NULL)
   {   p=s; 	
       s=s->next;
       free(p); 
   }
 }

栈的应用:

C++模板类:stack类

1.stack s:初始化栈,参数表示元素类型
2.s.push(t):入栈元素t
3.s.top():返回栈顶元素
4.s.pop():出栈操作只是删除栈顶元素(彻底删除),并不返回该元素。
5.s1.empty(),当栈空时,返回true。
6.s1.size():访问栈中的元素个数

栈的应用

1.中缀表达式转后缀表达式(符号简化)

while (从exp读取字符ch,ch!='\0')
{       ch为数字:将后续的所有数字均依次存放到postexp中,
                     并以字符'#'标志数值串结束;
        ch为左括号'(':将此括号进栈到Optr中;
        ch为右括号')':将Optr中出栈时遇到的第一个左括号'('以前的运算符
                    依次出栈并存放到postexp中,然后将左括号'('出栈;
        ch为'+'或'-':
                   出栈运算符并存放到postexp中,直到栈空或者栈顶为'(',
                   然后将'+'或'-'进栈;
        ch为'*'或'/':
                      若 栈顶为'*'或'/',出栈,直到栈顶不是'*'或'/'
                      否则 入栈
}
若exp扫描完毕,则将Optr中所有运算符依次出栈并存放到postexp中。

2.后缀表达式求值:

while (从postexp读取字符ch,ch!='\0')
{    若ch为数字,将后续所有数字构成一个整数存放数值栈st中。
     若ch为“+”,则从数值栈st中退栈两个运数,相加后进栈st中。
     若ch为“-”,则从数值栈st中退栈两个数,相减后进栈st中。
     若ch为“*”,则从数值栈st中退栈两个数,相乘后进栈st中。
     若ch为“/”,则从数值栈st中退栈两个数,相除后进栈st中
        (若除数为零,则提示相应的错误信息)。}
若字符串postexp扫描完毕,则数值栈op中的栈顶元素就是表达式的值。

3.迷宫问题:

初始化栈st,入口方块(x1,y1)入栈
//可走方位左3右1,上0下2;  bi
//方块(x,y)->(i,j)行号和列号
while 栈不为空
	遍历取栈顶
	if 栈顶是出口
		输出栈中内容
	end if
	bi初始化为-1;
	while 未走遍相邻方块且未找到可行下一步
		bi++;
		if bi为0  向上走;
		else if bi为1 向右走;
		else if bi为2 向下走;
		else bi为3 向左走;
		end if
	end while
	if 方块可行
		记录寻找可行方块的路径存入栈中
	else 退栈返回上一个位置并将现方块列为可行方块
	end if
end while

二.队列:

队列是只允许在表的一端进行插入,而在表的另一端进行删除的线性表。
队列中元素遵循先进先出的原则

队列的存储结构:

1.顺序队列:

  • 顺序队列结构体定义:
typedef struct 
{     ElemType data[MaxSize]; 
      int front,rear;      //队首和队尾指针
}Queue;
typedef Queue *SqQueue;

  • 顺序队列四大要素:
    1.队空条件:front = rear(初始时front = rear=-1)
    2.队满条件:rear=MaxSize-1
    3.元素e进队:rear++;data[rear]=e;
    4.元素e出队:front++;e=data[front];

  • 顺序队列基本操作

1.初始化顺序队列:

void InitQueue(SqQueue &q)
{	q=new Queue;
	q->front=q->rear=-1;
}

2.判断队列是否为空:

bool QueueEmpty(SqQueue q)
{
   return(q->front==q->rear);
}

3.判断队列是否为满:

bool QueueFULL(SqQueue q)
{
   return(q->rear==MaxSize-1);
}

4.进队列:

bool enQueue(SqQueue &q,ElemType e)
{  
    if (q->rear+1==MaxSize)	   return false;
                                       //队满上溢出
	q->rear=q->rear+1;
	q->data[q->rear]=e;
	return true;
}

5.出队列:

bool deQueue(SqQueue &q,ElemType &e)
{	
         if (q->front==q->rear)  //队空下溢出
		return false;
	q->front=q->front+1;
	e=q->data[q->front];
	return true;
}

6.销毁队列:

void DestroyQueue(SqQueue &q)
{
  delete q;
}

  • 为了解决队列元素假溢出的问题,我们引进循环队列的做法:
循环队列:

把数组的前端和后端连接起来,形成一个环形的顺序表,即把存储队列元素的表从逻辑上看成一个环,称为环形队列或循环队列。

  • 循环队列结构体定义
typedef struct 
{	
  ElemType data[MaxSize];
  int front,rear;	
} Queue;
typedef Queue *SqQueue;
  • 循环队列两要素:
    1.队空:frontrear
    2.队满:(rear+1)%M
    front

  • 循环队列基本操作

1.初始化队列:

void InitQueue(SqQueue &q)
{   q=new Queue;
    q->front=q->rear=0;
}

2.判断队列是否为空:

bool QueueEmpty(SqQueue q)
{
	return(q->front==q->rear);
}

3.判断队列是否为满:

bool QueueFULL(SqQueue q)
{
	return((q->rear+1)%MaxSize==q->front);
}

4.入队列:

bool enQueue(SqQueue &q,ElemType e)
{	if ((q->rear+1)%MaxSize==q->front)	//队满上溢出
		return false;
	q->rear=(q->rear+1)%MaxSize;
	q->data[q->rear]=e;
	return true;
}

5.出队列:

bool deQueue(SqQueue &q,ElemType &e)
{	if (q->front==q->rear)		//队空下溢出
		return false;
	q->front=(q->front+1)%MaxSize;
	e=q->data[q->front];
	return true;
}

6.销毁队列:

void DestroyQueue(SqQueue &q)
{
	delete q;
}

7.求循环队列长度:

int  QueueLength (SqQueue Q)
{
     return (Q.rear-Q.front+MAXQSIZE)%MAXQSIZE;                   
}

链式队列:

  • 链式队列结构体定义:
typedef struct QNode{
   QElemType   data;
   struct Qnode  *next;
}Qnode, *QueuePtr;//定义节点类型
typedef struct {
   QueuePtr  front;            //队头指针   
   QueuePtr  rear;             //队尾指针
}LinkQueue;  //定义队列类型

  • 链队列四要素:
    1.队空条件:front=rear
    2.队满条件:不考虑
    3.进队e操作:将包含e的节点插入到单链表表尾
    4.出队操作:删除单链表首数据节点

  • 链队列基本操作:

1.初始化队列:

Status InitQueue (LinkQueue &Q)
{
   Q.front=Q.rear=new QNode; 
    if(!Q.front) exit(OVERFLOW);
    Q.front->next=NULL;
     return OK;
}

2.判断队列是否为空:

Status QueueEmpty(LinkQueue Q)
{
	return (Q.front == Q.rear);
}

3.入队:

Status EnQueue(LinkQueue& Q, QElemType e)
{
	p = (QueuePtr)malloc(sizeof(QNode));
	if (!p) exit(OVERFLOW);
	p->data = e; p->next = NULL;
	Q.rear->next = p;
	Q.rear = p;
	return OK;
}


4.出队:

Status DeQueue(LinkQueue& Q, QElemType& e)
{
	if (Q.front == Q.rear) return ERROR;
	p = Q.front->next;
	e = p->data;
	Q.front->next = p->next;
	if (Q.rear == p) Q.rear = Q.front;
	delete p;
	return OK;
}

5.取链队列的队头元素:

Status GetHead(LinkQueue Q, QElemType& e) 
{
	if (Q.front == Q.rear) return ERROR;
	e = Q.front->next->data;
	return OK;
}

6.销毁队列:

Status DestroyQueue(LinkQueue& Q)
{
	while (Q.front)
	{
		Q.rear = Q.front->next;
		free(Q.front);
		Q.front = Q.rear;
	}
	return OK;
}

队列的应用:

C++容器:queue

q1.push(x): 将x接到队列的末端。
q1.pop():弹出队列的第一个元素,并不会返回被弹出元素的值。(front后移时队列中元素并未完全消失)
q1.front():即最早被压入队列的元素。
q1.back():即最后被压入队列的元素。
q1.empty():当队列空时,返回true。
q1.size():访问队列中的元素个数

队列的应用:

1.舞伴问题:


int QueueLen(SqQueue Q)//队列长度 
{
	因为是顺序队列,所以队列长度为队尾减队首
	返回Q->rear - Q->front;
}
int EnQueue(SqQueue& Q, Person e)//加入队列 
{
	if 队满
	返回0
	else
	元素e入队,队尾后移
}
int QueueEmpty(SqQueue& Q)//队列是否为空 
{
	直接返回队首队尾相等比较的结果
	return Q->front == Q->rear;
}
int DeQueue(SqQueue& Q, Person& e)//出队列 
{
	if 队空
	返回0
	else
	元素e出队,队首后移
}
void DancePartner(Person dancer[], int num) //配对舞伴 
{
	int k;
	Person e;
	for k = 0 to k=num-1
		if 字符数组元素为男
			元素入Fdancers队
		else 字符数组元素为女
			元素入Mdancers队
		end if
	end for
	while 男队女队都不空
		输出男女配对舞伴
	end while
}

2.迷宫问题:

#include <stdio.h>
#define MaxSize 100
#define M 8
#define N 8
int mg[M+2][N+2]=
{	
	{1,1,1,1,1,1,1,1,1,1},
	{1,0,0,1,0,0,0,1,0,1},
	{1,0,0,1,0,0,0,1,0,1},
	{1,0,0,0,0,1,1,0,0,1},
	{1,0,1,1,1,0,0,0,0,1},
	{1,0,0,0,1,0,0,0,0,1},
	{1,0,1,0,0,0,1,0,0,1},
	{1,0,1,1,1,0,1,1,0,1},
	{1,1,0,0,0,0,0,0,0,1},
	{1,1,1,1,1,1,1,1,1,1}
};
typedef struct 
{	int i,j;			//方块的位置
	int pre;			//本路径中上一方块在队列中的下标
} Box;					//方块类型
typedef struct
{
	Box data[MaxSize];
	int front,rear;		//队头指针和队尾指针
} QuType;				//定义顺序队类型
void print(QuType qu,int front)	//从队列qu中输出路径
{
	int k=front,j,ns=0;
	printf("\n");
	do				//反向找到最短路径,将该路径上的方块的pre成员设置成-1
	{	j=k;
		k=qu.data[k].pre;
		qu.data[j].pre=-1;
	} while (k!=0);
	printf("迷宫路径如下:\n");
	k=0;
	while (k<MaxSize)  //正向搜索到pre为-1的方块,即构成正向的路径
	{	if (qu.data[k].pre==-1)
		{	ns++;
			printf("\t(%d,%d)",qu.data[k].i,qu.data[k].j);
			if (ns%5==0) printf("\n");	//每输出每5个方块后换一行
		}
		k++;
	}
	printf("\n");
}
int mgpath(int xi,int yi,int xe,int ye)					//搜索路径为:(xi,yi)->(xe,ye)
{
	int i,j,find=0,di;
	QuType qu;						//定义顺序队
	qu.front=qu.rear=-1;
	qu.rear++;
	qu.data[qu.rear].i=xi; qu.data[qu.rear].j=yi;	//(xi,yi)进队
	qu.data[qu.rear].pre=-1;	
	mg[xi][yi]=-1;					//将其赋值-1,以避免回过来重复搜索
	while (qu.front!=qu.rear && !find)	//队列不为空且未找到路径时循环
	{	
		qu.front++;					//出队,由于不是环形队列,该出队元素仍在队列中
		i=qu.data[qu.front].i; j=qu.data[qu.front].j;
		if (i==xe && j==ye)			//找到了出口,输出路径
		{	
			find=1;				
			print(qu,qu.front);			//调用print函数输出路径
			return(1);				//找到一条路径时返回1
		}
		for (di=0;di<4;di++)		//循环扫描每个方位,把每个可走的方块插入队列中
		{	
			switch(di)
			{
			case 0:i=qu.data[qu.front].i-1; j=qu.data[qu.front].j;break;
			case 1:i=qu.data[qu.front].i; j=qu.data[qu.front].j+1;break;
			case 2:i=qu.data[qu.front].i+1; j=qu.data[qu.front].j;break;
			case 3:i=qu.data[qu.front].i, j=qu.data[qu.front].j-1;break;
			}
			if (mg[i][j]==0)
			{	qu.rear++;				//将该相邻方块插入到队列中
				qu.data[qu.rear].i=i; qu.data[qu.rear].j=j;
				qu.data[qu.rear].pre=qu.front; //指向路径中上一个方块的下标
				mg[i][j]=-1;		//将其赋值-1,以避免回过来重复搜索
			}
		}
     }
     return(0);						//未找到一条路径时返回1
}
int main()
{
	mgpath(1,1,M,N);
	return 1;
}

3.报数问题:

数据结构:
结构体定义:
int型在队首元素前一位的front;
int型队尾rear;
整型数组Data[最大队列容量MaxSize];
顺序队列Queue;
创建新队列,数据入队
while 队列不为空
//此时front在-1的位置
	while 遍历队列
		//取出输出队首,front后移一位
		cout << Queue->data[front + 1] << " ";
		front++;
		//取出偶数位置的数字再入队放在最后
		e = Queue->data[front + 1];
		rear++;
		Queue->data[rear]=e;
	end while
end while

1.2.谈谈你对栈和队列的认识及学习体会。

队列
先进后出 先进先出
出栈元素在栈中完全删除 出队元素在队中不完全删除
只能在一端插入删除 在队头删除队尾插入
线性结构 线性结构
插入删除时间复杂度O(1) 插入删除时间复杂度O(1)

栈和队列虽然原理不同,但都是线性结构存储数据的一种特殊计算方法,为表达式转换等问题提供了简便的做法。在学习栈和队列的过程中,用C++容器真的带给了我巨大的便利,在做题的时候感觉理解题意很难,有挺多测试点过不去,还是要拜托对搜索引擎的依赖,反思自己为什么不会做相对缜密的题。

2.PTA实验作业

2.1.题目1:7-7 银行业务队列简单模拟

2.1.1代码截图



2.1.2本题PTA提交列表说明。

Q1:在队列A判断完又进行了一次flag初始化
A1:在开始flag初始化一次即可
Q2:队列B没有判断空格的问题,忘记了顾客编号都为偶数的情况
A2:在队列B中也进行数字后空格的判断,防止有顾客编号都为偶数的情况出现。

2.2.题目2:6-5 jmu-ds-舞伴问题

2.2.1代码截图



2.2.2本题PTA提交列表说明。

Q1:计算队列长度时把队列当成可变的了
A1:用固定队列队尾减队首就可以求出队列长度了
Q2:在最后输出配对舞伴时两人之间的空格数搞错了
A2:把输出一个空格改为输出两个空格

3.阅读代码

3.1 题目及解题代码

题目:来源:力扣(LeetCode)

解题代码:

public class Solution {
    public boolean find132pattern(int[] nums) {
        if (nums.length < 3)
            return false;
        Stack < Integer > stack = new Stack < > ();
        int[] min = new int[nums.length];
        min[0] = nums[0];
        for (int i = 1; i < nums.length; i++)
            min[i] = Math.min(min[i - 1], nums[i]);
        for (int j = nums.length - 1; j >= 0; j--) {
            if (nums[j] > min[j]) {
                while (!stack.isEmpty() && stack.peek() <= min[j])
                    stack.pop();
                if (!stack.isEmpty() && stack.peek() < nums[j])
                    return true;
                stack.push(nums[j]);
            }
        }
        return false;
    }
}

3.1.1 该题的设计思路










时间复杂度:O(N)
空间复杂度:O(N)

3.1.2 该题的伪代码

public class Solution {
	public boolean find132pattern(int[] nums) {
		if 数组元素少于3个
			return false;
		end if
		Stack < Integer > stack = new Stack < >();//定义栈
		定义最小数数组min[]来储存啊a[i]
		min[0] = nums[0];//min数组初始化
		for i = 1 to nums.length-1
			min[i] = Math.min(min[i - 1], nums[i]);//求j点左侧最小的a[i]
		end for
		for j = nums.length - 1 to 0
			if a[j]大于最小a[i]
				while 栈非空且栈顶元素小于a[i]
					栈顶出栈
				end while
				if 栈非空且栈顶元素小于a[j]
					return true;
				end if
				a[j]入栈
			end if
		end for
		return false;
	}
}

3.1.3 运行结果

3.1.4 该题目解题优势及难点。

*优势:
分部进行比较,先比较a[i]<a[j],先假设定义一个j,寻找j左侧最小的a[i];寻找到最优a[i]后从队尾到a[j]寻找a[k],将所有a[k]放入栈中让它成栈底最大栈顶最小的倒序,a[k]<a[j]的栈顶不合理,pop出栈,寻找到a[k]>a[i],再去比较a[k]<a[j],如果可以匹配就说明有“132”模式的子序列。

*难点:
a[j]的比较寻找,因为j是不确定的,只能边移边找,分部比较也挺难理解;

3.2 题目及解题代码

题目:来源:力扣(LeetCode)

解题代码:

class CQueue {
    Stack<Integer> stack1;
    Stack<Integer> stack2;
    int size;

    public CQueue() {
        stack1 = new Stack<Integer>();
        stack2 = new Stack<Integer>();
        size = 0;
    }
    
    public void appendTail(int value) {
        while (!stack1.isEmpty()) {
            stack2.push(stack1.pop());
        }
        stack1.push(value);
        while (!stack2.isEmpty()) {
            stack1.push(stack2.pop());
        }
        size++;
    }
    
    public int deleteHead() {
        if (size == 0) {
            return -1;
        }
        size--;
        return stack1.pop();
    }
}

3.2.1 该题的设计思路

插入元素:
时间复杂度:O(n)。插入元素时,对于已有元素,每个元素都要弹出栈两次,压入栈两次,因此是线性时间复杂度。
空间复杂度:O(n)。需要使用额外的空间存储已有元素。

删除元素:
时间复杂度:O(1)。判断元素个数和删除队列头部元素都使用常数时间。
空间复杂度:O(1)。从第一个栈弹出一个元素,使用常数空间。

3.2.2 该题的伪代码

class CQueue {
	Stack<Integer> stack1;//第一个栈
	Stack<Integer> stack2;//辅助栈
	int size;//队列元素数

	public CQueue() {
		stack1初始化 
		stack2初始化
		size = 0;//队列元素数开始为0
	}

	public void appendTail(int value) {
		while stack1不为空
			将stack1中新插入的元素push进stack2中
		end while
		新元素value入栈stack1
		while stack2非空
			将stack2中的元素再全部返回stack1中
		end while
		size++;
	}

	public int deleteHead() {
		if 队列为空 
			return -1;
		end if
		队列元素-1
		return stack1.pop();
	}
}

3.2.3 运行结果

3.2.4 该题目解题优势及难点

*优势:
它是用第一个栈来储存新插入的元素,然后将第一个栈中的元素放入第二个栈中,最后把第二个栈中的元素全部返回到第一个栈中,使新插入的元素成为栈底,其他的元素顺序和输入的时候相同,符合队列的规则。

*难点:
第二个栈给第一个栈做辅助栈,两个栈之间的元素倒换很麻烦。