凉城有梦

导航

 

0.PTA得分截图

1.本周学习总结

1.1 总结栈和队列内容

1.栈的存储结构及操作

1.栈

1.栈(stack):是限定仅在表尾进行插入和删除操作的线性表。其中,允许插入和删除的一端被称为栈顶(top),另一端被称为栈底(bottom),不含任何数据元素的栈被称为空栈。栈又被称为后进先出(Last In First Out)的线性表,简称LIFO结构。
栈的插入操作为进栈,栈的删除操作为出栈。

2.栈的抽象数据类型
ADT 栈(stack)
Data
同线性表。元素具有相同类型,相邻元素具有前驱和后继关系。
Operation
InitStack(S):初始化操作,建立一个空栈S。
DestoryStack(
S):若栈存在,则销毁它。
ClearStack(S):将栈清空。
StackEmpty(S):若栈为空,返回true,否则返回false。
GetTop(S,
e):若栈存在且非空,用e返回S的栈顶元素。
Push(S,e):若栈S存在,插入新元素e到栈S中并称为栈顶元素
Pop(
S,*e):删除栈S中栈顶元素,并用e返回其值
StackLength(S):返回栈S的元素个数
endADT

2.栈的存储结构

(1)栈顺序结构定义

#define OK 1
#define ERROR 0
typedef int SElemType;    //SElemType类型根据实际情况而定,这里假设为int
typedef struct
{
    SElemType data[MAXSIZE];    //栈存储空间大小MAXSIZE
    int top;    //用于栈顶指针
}SqStack;

(2)共享栈

3.栈的顺序存储结构
栈类型SqStack:

typedef struct 
{  ElemType data[MaxSize]; 
   int top;		//栈顶指针
} Stack;
typedef Stack *SqStack;



顺序栈4要素:
栈空条件:top=-1
栈满条件:top=MaxSize-1
进栈e操作:top++; st->data[top]=e
退栈操作:e=st->data[top]; top--;
顺序栈中实现栈的基本运算算法
(1)初始化栈initStack(&s)

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

(2)销毁栈ClearStack(&s)

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

(3)判断栈是否为空StackEmpty(s)

bool StackEmpty(SqStack s)
{
  return(s->top==-1);
}

(4)进栈操作(从栈顶插入一个元素)
算法思路:
a.判定是否栈满;
b.栈顶加1;
c.将元素插入到
实现:插入元素e为新的栈顶元素

Status Push(SqStack *S,SElemType e)
{
    if(S->top==MAXSIZE-1)    //栈顶=MAXSIZE-1,即数组的最后一个存储位置,说明栈满
    {
        return ERROR;
    }
    S->top++;    //栈顶加1
    S->data[S->top]=e;    //将元素入栈
    return OK;
}

(5)出栈操作(从栈顶删除一个元素)
算法思路:
a.判定栈是否为空;
b.将栈顶元素保存到指针变量e所指向的存储位置中;
c.栈顶减1.
实现:若栈不为空,则删除S的栈顶元素,用e返回其值,并返回OK;否则返回ERROR

Status Pop(SqStack *S,SElemType *e)
{
    if(S->top==-1)    //空栈
    {
        return ERROR;
    }
    *e=S->data[S->top];
    S->top--;
    return OK;
}

(6)取栈顶元素GetTop(s)
在栈不为空的条件下,将栈顶元素赋给e。

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

2.栈的应用

------------恢复内容开始------------

0.PTA得分截图

1.本周学习总结

1.1 总结栈和队列内容

1.栈的存储结构及操作

1.栈

1.栈(stack):是限定仅在表尾进行插入和删除操作的线性表。其中,允许插入和删除的一端被称为栈顶(top),另一端被称为栈底(bottom),不含任何数据元素的栈被称为空栈。栈又被称为后进先出(Last In First Out)的线性表,简称LIFO结构。
栈的插入操作为进栈,栈的删除操作为出栈。

2.栈的抽象数据类型
ADT 栈(stack)
Data
同线性表。元素具有相同类型,相邻元素具有前驱和后继关系。
Operation
InitStack(S):初始化操作,建立一个空栈S。
DestoryStack(
S):若栈存在,则销毁它。
ClearStack(S):将栈清空。
StackEmpty(S):若栈为空,返回true,否则返回false。
GetTop(S,
e):若栈存在且非空,用e返回S的栈顶元素。
Push(S,e):若栈S存在,插入新元素e到栈S中并称为栈顶元素
Pop(
S,*e):删除栈S中栈顶元素,并用e返回其值
StackLength(S):返回栈S的元素个数
endADT

2.栈的存储结构

1.栈的顺序存储结构
栈类型SqStack:

typedef struct 
{  ElemType data[MaxSize]; 
   int top;		//栈顶指针
} Stack;
typedef Stack *SqStack;



顺序栈4要素:
栈空条件:top=-1
栈满条件:top=MaxSize-1
进栈e操作:top++; st->data[top]=e
退栈操作:e=st->data[top]; top--;
顺序栈中实现栈的基本运算算法
(1)初始化栈initStack(&s)

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

(2)销毁栈ClearStack(&s)

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

(3)判断栈是否为空StackEmpty(s)

bool StackEmpty(SqStack s)
{
  return(s->top==-1);
}

(4)进栈操作(从栈顶插入一个元素)
算法思路:
a.判定是否栈满;
b.栈顶加1;
c.将元素插入到
实现:插入元素e为新的栈顶元素

Status Push(SqStack *S,SElemType e)
{
    if(S->top==MAXSIZE-1)    //栈顶=MAXSIZE-1,即数组的最后一个存储位置,说明栈满
    {
        return ERROR;
    }
    S->top++;    //栈顶加1
    S->data[S->top]=e;    //将元素入栈
    return OK;
}

(5)出栈操作(从栈顶删除一个元素)
算法思路:
a.判定栈是否为空;
b.将栈顶元素保存到指针变量e所指向的存储位置中;
c.栈顶减1.
实现:若栈不为空,则删除S的栈顶元素,用e返回其值,并返回OK;否则返回ERROR

Status Pop(SqStack *S,SElemType *e)
{
    if(S->top==-1)    //空栈
    {
        return ERROR;
    }
    *e=S->data[S->top];
    S->top--;
    return OK;
}

(6)取栈顶元素GetTop(s)
在栈不为空的条件下,将栈顶元素赋给e。

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

2.栈链式存储结构
定义:

typedef int ElemType;
typedef struct linknode
{  ElemType data;			//数据域
   struct linknode *next;	//指针域
} LiNode,*LiStack;

链栈中实现栈的基本运算算法:
(1)初始化栈initStack(&s)

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


(2)销毁栈ClearStack(&s)
释放栈s占用的全部存储空间。同链表删除:

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

3)判断栈是否为空StackEmpty(s)
栈S为空的条件是s->next==NULL,同空链表

bool StackEmpty(LiStack s)
{
   return(s->next==NULL);
}

(4)进栈Push(&s,e)
将新数据节点插入到头节点之后。对应算法如下:

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

(5)出栈Pop(&s,&e)
在栈不为空的条件下,将头节点后继数据节点的数据域赋给e

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;
}

6)取栈顶元素GetTop(s,e)
在栈不为空的条件下,将头节点后继数据节点的数据域赋给e。

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

stack容器:

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

2.栈的应用

表达式求值
中缀转后缀表达式
中缀表达式:运算符号位于两个运算数之间。如 ,a + b *c - d / e
后缀表达式:运算符号位于两个运算数之后。如, a b c *+ d e /- 
exp:字符数组
a + b * c - d / e * f \0
postexp:后缀表达式数组
a b c d e f
1.优先级比栈顶运算符高入栈
2.低或相等,出栈,写入后缀表达式
例如:对于表达式“(56-20)/(4+2)”,其转换成后缀表达式的过程 如下:



表达式“(56-20)/(4+2)”
后缀表达式:
56#20#-4#2#+/
对后缀表达式postexp求值

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

2.队列

1.定义及特点

定义:只允许在表的一端进行插入,而在表的另一端进行删除的线性表。
队尾(rear)——允许插入的一端
队头(front)——允许删除的一端
特点:先进先出(FIFO)

2.队列的顺序存储结构及其基本运算

顺序队类型SqQueue定义如下:
typedef struct 
{     ElemType data[MaxSize]; 
      int front,rear;      //队首和队尾指针
}   Queue;
typedef Queue *SqQueue;

队列的基本运算如下:

    InitQueue(&q):初始化队列。构造一个空队列q。
    DestroyQueue(&q):销毁队列。释放队列q占用的存储空间。
    QueueEmpty(q):判断队列是否为空。若队列q为空,则返回真;否则返回假。
    enQueue(&q,e):进队列。将元素e进队作为队尾元素。
    deQueue(&q,&e):出队列。从队列q中出队一个元素,并将其值赋给e。


顺序队的四要素(初始时front=rear=-1):
队空条件:front = rear
队满条件:rear=MaxSize-1
元素e进队:rear++;data[rear]=e;
元素e出队:front++;e=data[front];

  1. 顺序队中实现队列的基本运算
    (1)初始化队列InitQueue(q)
      构造一个空队列q。将front和rear指针均设置成初始状态即-1值。
void InitQueue(SqQueue &q)
{	q=new Queue;
	q->front=q->rear=-1;
}

(2)销毁队列DestroyQueue(q)
释放队列q占用的存储空间。

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

3)判断队列是否为空QueueEmpty(q)
若队列q满足q->front==q->rear条件,则返回true;否则返回false。

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

(4)进队列enQueue(q,e)
在队列不满的条件下,先将队尾指针rear循环增1,然后将元素添加到该位置。

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)出队列deQueue(q,e)
在队列q不为空的条件下,将队首指针front循环增1,并将该位置的元素值赋给e。

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;
}

2、环形队列(或循环队列)中实现队列的基本运算
把数组的前端和后端连接起来,形成一个环形的顺序表,即把存储队列元素的表从逻辑上看成一个环,称为环形队列或循环队列。

队空:frontrear
队满:front
rear
front指向队头元素问题,解决方案:
(1).另外设一个标志以区别队空、队满
(2).少用一个元素空间,front指队头前一个位置
队空:frontrear
队满:(rear+1)%M
front
(3).顺序队列没这个问题。
初始化队列:front=rear=0
队满条件:(rear+1)%MaxSize=front
队空条件:front=rear
入队操作:rear=(rear+1)%MaxSize
data[rear]=e;
出队操作:front=(front+1)%MaxSize
data[front]=e;
基本运算:
(1)初始化队列InitQueue(q)
构造一个空队列q。将front和rear指针均设置成初始状态即0值。

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

(2) 销毁队列

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

(3) 判断队列是否为空

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

(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) 求循环队列的长度

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

queue容器

#include<queue>
q1.push(x): 将x接到队列的末端。
q1.pop():弹出队列的第一个元素
注意,并不会返回被弹出元素的值。
q1.front():即最早被压入队列的元素。
q1.back():即最后被压入队列的元素。
q1.empty():当队列空时,返回true。
q1.size():访问队列中的元素个数

3.链队列

typedef struct QNode{
   QElemType   data;
   struct Qnode  *next;
}Qnode, *QueuePtr;
typedef struct {
   QueuePtr  front;            //队头指针   
   QueuePtr  rear;             //队尾指针
}LinkQueue;  

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

元素x入队列:

元素y入队列:

元素x出队列:

基本运算:
(1) 链队列初始化

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

(2) 销毁链队列

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

(3) 判断链队列是否为空

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

(4) 求链队列的队头元素

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

(5) 链队列入队

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;
}


(6) 链队列出队

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;
}


4.双端队列
(1)两端都可以进队和出队操作的队列。
(2)队列的两端分别称为前端和后端,两端都可以入队和出队。
(3)其元素的逻辑结构仍是线性结构。

3.队列应用

操作系统、售票系统、打印机、手机短信发送
例子:迷宫问题
记录走过的方块:

typedef struct 
{      int i,j;		//方块的位置
       int pre		//本路径中上一方块在队列中的下标
}  Box;			//方块类型
typedef struct
{     Box data[MaxSize];
       int front,rear;	//队头指针和队尾指针
}  QuType;		//定义顺序队类型

求一条迷宫路径的算法:

bool mgpath1(int xi,int yi,int xe,int ye)	//搜索路径为:(xi,yi)->(xe,ye)
{      Box e;
       int i, j, di, i1, j1;
       QuType *qu;		//定义顺序队指针qu
       InitQueue(qu);		//初始化队列qu
       e.i=xi;  e.j=yi;   e.pre=-1;
       enQueue(qu,e);		//(xi,yi)进队
       mg[xi][yi]=-1;		//将其赋值-1,以避免回过来重复搜索
 while (!QueueEmpty(qu))		//队不空循环
       {	deQueue(qu,e);			//出队方块e
	i=e.i;   j=e.j;
	if (i==xe && j==ye)		//找到了出口,输出路径
	{       print(qu,qu->front);	//调用print函数输出路径
	        DestroyQueue(qu);		//销毁队列
	        return true;			//找到一条路径时返回真
	}
 for (di=0;di<4;di++)		//循环扫描每个方位
        {	switch(di)
	{
	case 0:i1=i-1; j1=j;   break;
	case 1:i1=i;   j1=j+1; break;
	case 2:i1=i+1; j1=j;   break;
	case 3:i1=i;   j1=j-1; break;
	}
	if (mg[i1][j1]==0)
	{     e.i=i1;  e.j=j1; 
	      e.pre=qu->front;	
	      enQueue(qu,e);	//(i1,j1)方块进队
	      mg[i1][j1]=-1;	//将其赋值-1
	}
         }
      }
      DestroyQueue(qu);		//销毁队列
      return false;
}

队列:解决广度优先搜索算法。
迷宫路径:最优解

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

栈和队列各有各的优点,各有各的缺点,在做栈和队列问题时,画图是必不可少的,画图能帮助我们理清做题的思路,方便求解题目。
栈和队列感觉还有点不懂,应该再反复认真学习!

2.PTA实验作业

2.1 jmu-报数游戏

2.1.1 代码截图


2.1.2 本题PTA提交列表说明。


部分正确:题目没看清,少了“!”。
部分正确:中间多打了一个空格。

2.2 jmu-ds-舞伴问题

2.2.1 代码截图


2.2.2 本题PTA提交列表说明。


格式错误:少了一个空格。

3.阅读代码

3.1 用两个栈实现队列

题目:
用两个栈实现一个队列。队列的声明如下,请实现它的两个函数 appendTail 和 deleteHead ,分别完成在队列尾部插入整数和在队列头部删除整数的功能。(若队列中没有元素,deleteHead 操作返回 -1 )
解题代码:

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.1.1 该题的设计思路


插入元素
插入元素对应方法 appendTail
如果 stack1 非空,则将 stack1 内的元素依次弹出并依次压入 stack2,直至 stack1 内的全部元素都被弹出
将新元素 value 压入 stack1 内
如果 stack2 非空,则将 stack2 内的元素依次弹出并依次压入 stack1,直至 stack2 内的全部元素都被弹出
将 size 的值加 1
时间复杂度:O(n)。
空间复杂度:O(n)。

删除元素
删除元素对应方法 deleteHead
如果 size 为 0,则队列为空,返回 -1
如果 size 大于 0,则队列非空,将 size 的值减 1,从 stack1 弹出一个元素并返回
时间复杂度:O(1)。
空间复杂度:O(1)。

3.1.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.1.3 运行结果

3.1.4分析该题目解题优势及难点。
优势:
一个栈存储元素,一个栈辅助操作。新插入的元素在第一个栈的底部,第一个栈内的其余元素的顺序和插入元素之前保持一致。大大节省了时间复杂度。
难点:
进行辅助操作和存储操作的两个栈的元素间的调换。

3.2 根据身高重建队列

题目:
假设有打乱顺序的一群人站成一个队列。 每个人由一个整数对(h, k)表示,其中h是这个人的身高,k是排在这个人前面且身高大于或等于h的人数。 编写一个算法来重建这个队列。

注意:
总人数少于1100人。
解题代码:

class Solution {
public:
    vector<vector<int>> reconstructQueue(vector<vector<int>>& people) {
        // 先排序
        // [7,0], [7,1], [6,1], [5,0], [5,2], [4,4]
        
        // 再一个一个插入。
        // [7,0]
        // [7,0], [7,1]
        // [7,0], [6,1], [7,1]
        // [5,0], [7,0], [6,1], [7,1]
        // [5,0], [7,0], [5,2], [6,1], [7,1]
        // [5,0], [7,0], [5,2], [6,1], [4,4], [7,1]
        sort(people.begin(), people.end(), [](const vector<int>& a, const vector<int>& b) {
            if (a[0] > b[0]) return true;
            if (a[0] == b[0] && a[1] < b[1]) return true;
            return false;
        });
        
        vector<vector<int>> res;
        for (auto& e : people) {
            res.insert(res.begin() + e[1], e);
        }
        return res;
    }
};

3.2.1 该题的设计思路

假设候选队列为 A,已经站好队的队列为 B.
从 A 里挑身高最高的人 x 出来,插入到 B. 因为 B 中每个人的身高都比 x 要高,因此 x 插入的位置,就是看 x 前面应该有多少人就行了。比如 x 前面有 5 个人,那 x 就插入到队列 B 的第 5 个位置。

时间复杂度:O(n)
空间复杂度: O(1)

3.2.2 该题的伪代码

class Solution {
public:
    vector<vector<int>> reconstructQueue(vector<vector<int>>& people) {
    sort(people.begin(), people.end(), [](const vector<int>& a, const vector<int>& b) {
    先排序,按身高从高到矮;
    }
    vector<vector<int>> res;
        for (auto& e : people) {
           插入;
        }
}

3.2.3 运行结果

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

难点:
涉及多个c++容器,需要深入去了解这些容器的用法。
优势:
容易理清思路,不容易混淆。

posted on 2020-03-22 22:09  凉城有梦  阅读(243)  评论(0编辑  收藏  举报