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)初始化队列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
队满:frontrear
front指向队头元素问题,解决方案:
(1).另外设一个标志以区别队空、队满
(2).少用一个元素空间,front指队头前一个位置
队空:frontrear
队满:(rear+1)%Mfront
(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++容器,需要深入去了解这些容器的用法。
优势:
容易理清思路,不容易混淆。