0.PTA得分截图
1.本周学习总结
1.1总结栈和队列内容
栈的顺序存储结构及其基本运算
栈是限制插入和删除只能在一个位置上进行的表,采用顺序存储结构的栈称为顺序栈。
顺序栈的结构体定义:
typedef struct
{
ElemType data[MaxSize] ;//存放栈中的数据元素
int top;//栈顶指针,即存放栈顶元素在data数组中的下标
}SqStack;//顺序栈类型
初始化栈
该运算创建一个空栈,由s指向它。实际上就是分配-一个顺序栈空间,并将栈顶指针设置为-1。
void InitStack(SqStack * &s)
{
s= (SqStack * )malloc( sizeof(SqStack)); //分配一个顺序栈空间, 首地址存放在s中
s-> top=-1;//栈顶指针置为-1
}
判断栈是否为空
该运算实际上用于判断条件s-> top==-1是否成立。
bool StackEmpty(SqStack *s)
{
return(s-> top==-1);
}
进栈Push(&s,e)
该运算的执行过程是,在栈不满的条件下先将栈顶指针增1,然后在该位置上插人元素e,并返回真;否则返回假。
bool Push(SqStack * &s, ElemType e)
{
if (s-> top==MaxSize- 1)//栈满的情况,即栈上溢出
return false;
s-> top++ ;//栈顶指针增1
s-> data[s-> top]=e;//元素e放在栈顶指针处
return true;
}
出栈Pop(&s,e)
该运算的执行过程是,在栈不为空的条件下先将栈顶元素赋给e,然后将栈顶指针减1,并返回真;否则返回假。
bool Pop(SqStack * &s, ElemType &e)
{
if (s-> top==- 1)//栈为空的情况,即栈下溢出
return false;
e=s-> data[s-> top] ;//取栈顶元素
s->top--;//栈顶指针减1
return true;
}
取栈顶元素GetTop(s ,&e )
该运算在栈不为空的条件下将栈顶元素赋给e并返回真;否则返回假。
bool GetTop(SqStack * s, ElemType &e)
{
if (s-> top==- 1)//栈为空的情况,即栈下溢出
return false;
e=s-> data[s-> top];//取栈顶元素
return true;
}
栈的链式存储结构及其基本运算
把栈顶放在单链表的头部,用链表来存储栈的的数据结构称为链栈。
链式栈的结构体定义:
typedef struct linknode
{
ElemType data;//数据域
struct linknode *next;//指针域
} LinkStNode;//链栈结点类型
初始化栈InitStack(&s)
该运算创建一个空链栈s,实际上是创建链栈的头结点,并将其next 域置为NULL。
void InitStack(LinkStNode * &s)
{
s= (LinkStNode * )malloc(sizeof(LinkStNode));
s-> next= NULL;
}
判断栈是否为空StackEmpty(&s)
该运算判断s-> next= NULL的条件是否成立。
bool StackEmpty(LinkStNode *s)
{
return(s-> next== NULL);
}
进栈Push(&s,e)
该运算新建一个结点,用于存放元素e(由p指向它),然后将其插入到头结点之后作为新的首结点。
void Push(LinkStNode * &s, ElemTlype e)
{
LinkStNode * p;
p= (LinkStNode * )malloc( sizeof LinkStNode));//新建结点p
p->data=e;//存放元素e
p->next=s>next;//将p结点插入作为首结点
s-> next= p;
}
出栈Pop(&s ,&e )
该运算在栈不为空的条件下提取首结点的数据域赋给引用型参数e,然后将其删除。
bool Pop(LinkStNode * &s, ElemType &e)
{
LinkStNode * p;
if (s-> next== NULL)//栈空的情况
return false;//返回假
p=s->next;//p指向首结点
e=p->data;//提取首结点值
s->next=p->next;//删除首结点
free(p); //释放被删结点的存储空间
return true;//返回真
}
取栈顶元素GetTop(s ,&e )
该运算在栈不为空的条件下提取首结点的数据域赋给引用型参数e。
bool GetTop( LinkStNode * s, ElemType &e)
{
if(s→> next==NULL) //栈空 的情况
return false;//返回假
e=s->next->data;//提取首结点值
return true;//返回真
}
栈的应用
- “()”,“[]"和“{}”,这些符号都必须是左右匹配的,这就是我们所说的符合匹配类型,当然符合不仅需要个数相等,而且需要先左后右的依次出现,否则就不符合匹配规则,如“)(”,明显是错误的匹配,而“()”才是正确的匹配。有时候符合如括号还会嵌套出现,如“[9*(5+2)]”,而嵌套的匹配原则是一个右括号与其前面最近的一个括号匹配。
- 判断原则:设置str是输入的字符串,从左到右依次对字符串str中的每个字符str[i]进行语法检测,如果str[i]是左括号则入栈,如果str[i]是右括号则出栈,若此时出栈的字符str[i]为左括号,则说明这一对括号匹配正常,如果此时栈为空或者出栈字符不为左括号,则表示缺少与str[i]匹配的左括号,即目前不完整。重复执行(1)操作,直到str检测结束,如果此时栈为空,则全部括号匹配,如果栈中还有左括号,是说明缺少右括号。
- 执行流程:
队列
- 队列是一种特殊的线性表,特殊之处在于它只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作,和栈一样,队列是一种操作受限制的线性表。
队列的顺序存储结构及其基本运算
- 顺序队类型SqQueue声明如下:
typedef struct
ElemType data[MaxSize] ;//存放队中元素
int front, rear;//队头和队尾指针
} SqQueue;//顺序队类型
-
队空的条件: q-> front==q-> rear.
-
队满的条件: q-> rear== MaxSize- l(data数组的最大下标)。
-
元素e的进队操作:先将rear增1.然后将元素e放在data数组的rear位置。
-
出队操作:先将front增1,然后取出data数组中front位置的元素。
初始化队列InitQueue(&q)
构造一个空队列q,将front和rear指针均设置成初始状态,即-1值。
void InitQueue(SqQueue * &q)
{
q= (SqQueue * )malloe sizeof(SqQueue)) ;
q-> front=q-> rear =-1;
判断队列是否为空QueueEmpty(q)
若队列q为空,返回真;否则返回假。
bool QueueEmpty(SqQueue *q)
{
return(q- > front==q -> rear);
}
进队列enQueue(&q ,e)
在队列q不满的条件下先将队尾指针rear增1,然后将元素e插人到该位置。
bool enQueue(SqQueue * &q, ElemType e)
{
if (q-> rear== MaxSize- 1)//队满上溢出
return false;//返回假
q-> rear++;//队尾增1
q>datta[q-> rear]=e;//rear位置插入元素e
return true;//返回真
}
出队列deQueue(&q ,&e )
在队列q不空的条件下先将队头指针front 增1,并将该位置的元素值赋给e。算法如下:
bool deQueue(SqQueue * &q, ElemType &e)
{
if(q-> front==q-> rear)//队空下溢出
return false;
q-> front++;
e=q-> data[q-> front];
return true;
}
环形队列
- 队头指针front循环增1:
front=(front+1)%MaxSize
- 队尾指针rear循环增1:
rear=(rear+1)%MaxSize
初始化队列InitQueue(&q )
构造一个空队列q,将front 和rear指针均设置成初始状态.即0值。算法如下:
void InitQueue(SqQueue * &q)
{
q= (SqQueue *)malloc (sizeof(SqQueue));
q-> front=q-> rear= 0;
}
进队列enQueue(&q,e)
在队列不满的条件下先将队尾指针rear 循环增1,然后将元素插人到该位置。
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;
}
出队列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) % MaxSize;
e=q-> data[q-> front];
return true;
}
- 说明:在环形队列中,队头指针front指向队中队头元素的前一个位置,队尾指针rea指向队中的队尾元素,队列中的元素个数= (rear- front + MaxSize) % MaxSize。
队列的链式存储结构及其基本运算
链队中数据结点的类型DataNode声明如下:
typedef struct qnode
{
ElemType data;
struet qnode * next; //下一个结点指针
}DataNode;//链队数据结点的类型
链队头结点的类型LinkQuNode声明如下:
typedef struct
{
DataNode * front; // 指向队首结点
DataNode * rear;//指向队尾结点
}LinkQuNode;//链队结点的类型
- 队空的条件: q-> rear== NULL或q-> front == NULL。
- 队满的条件:不考虑。
- 元素e的进队操作:新建一个结点存放元素e(由p指向它),将结点p插入作为尾结点。
- 出队操作:取出队首结点的data值并将其删除。
初始化队列InitQueue(&q)
构造一个空队,即创建一个链队结点,其front和rear域均置为NULL。
void InitQueue( LinkQuNode * &q)
{
q= (LinkQuNode * )malloc(sizeof( LinkQuNode));
q-> front=q-> rear= NULL;
}
进队列enQueue(&q,e)
创建一个新结点用于存放元素e(由p指向它)。若原队列为空,则将链队结点的两个域均指向结点p,否则将结点p链接到单链表的末尾,并让链队结点的rear域指向它。
void enQueue(LinkQuNode * &q ElemType e)
{
DataNode * p;
p= (DataNode * )malloe( sizeof(DataNode); //创建新结点
p-> data=e;
p-next= NULL
if (q-> rear== NULL)//若链队为空, 则新结点既是队首结点又是队尾结点
q-> front=q->rear= p;//若链队不空
else
{
q->rear->next=p;//将结点p链到队尾,并将rear指向它
q->rear=p;
}
}
出队列deQueue(&q ,&e )
若原队列为空,则下溢出返回假;若原队列不空,则将首结点的data 域值赋给e,并删除之,若原队列只有一个结点,则需将链队结点的两个域均置为NULL,表示队列已为空。
bool deQueue( LinkQuNode * &q, ElemType &.e)
{
DataNode * t;
if (q-> rear= = NULL)//原来队列为空
return false;
t=q > front;//t指向首结点
if (q-> front==q-> rear)//原来队列中只有一个数据结点时
q-> front=q-> rear= NULL;
else//原来队列中有两个或两个以上结点时
q- > front=q- > front- > next;
e=t-> data;
free(t);
return true;
}
队列的应用
求解报数问题
问题描述:
设有n个人站成一排,从左向右的编号分别为1~n,现在从左往右报数“1,2.1,2...”,数到“1”的人出列,数到“2”的立即站到队伍的最右端。报数过程反复进行,直到n个人都出列为止。要求给出他们的出列顺序。
例如,当n=8时初始序列为:12345678
则出列顺序为:13572648
设计运算算法:
用一个队列解决出列问题,由于这里不需要使用已经出队后的元素,所以采用环形队列。采用的算法思想是先将n个人的编号进队,然后反复执行以下操作,直到队列为空。首先出队一个元素,输出其编号(报数为1的人出列)。若队列不空,再出队一个元素,并将刚出列的元素进队(报数为2的人站到队伍的最右端,即队尾)。
对应的算法如下:
void number(int n)
{
inti;
ElemType e;
SqQueue *q;//环形队列指针q
InitQueue(q) ;//初始化队列q
for (i=1;i<=n;i++)//构建初始序列
while (!QueueEmpty(q))//队列不空循环
{
deQueue(q,e);//出队一个元素e
printf("%d ",e);//输出元素编号
if (!QueueEmpty(q))//队列不空
{
deQueue(q,e);//出队一个元素e
enQueue(q,e);//将刚出队的元素进队
}
}
printf("\n");
DestroyQueeue(q) ;//销毁队列q
}
1.2谈谈你对栈和队列的认识及学习体会
- 栈是限制在表的一端进行插入和删除运算的线性表,通常称插入、删除的这一端为栈顶,另一端为栈底,当表中没有元素时称为空栈。队列是一种简单的等待序列,在尾部加入元素时队列加长,在前端删除数据时队列缩短。与栈不同,队列是一种使用两端的结构:一端用来加入新元素,另一端用来删除元素。队列是先进先出的结构。其两者不同点在于:一,删除数据元素的位置不同,栈的删除操作在表尾进行,队列的删除操作在表头进行。二,常见栈的应用场景包括括号问题的求解,表达式的转换和求值等,而常见的队列的应用场景包括计算机系统中各种资源的管理,广度优先搜索遍历等。三,顺序栈能够实现多栈空间共享,而顺序队列不能。本章学习内容比较多,主要是顺序栈,链栈,顺序队列,链队列及环形队列它们的结构特点以及删除和插入等操作,虽然看起来大同小异,但是操作起来还是记不住或者容易搞混,这时候画图就可以帮助加深印象和理解。
PTA实验作业
1.题目名称:7-2 jmu-字符串是否对称
1.1设计思路代码
伪代码简要介绍设计思路
定义一个字符数组
初始化一个字符类型的栈S
输入一行字符
for i=0 to str[i]!=’\0’
{
把输入的字符入栈
}
end for
for i=0 to str[i]!=’\0’
{
top保存栈顶元素
If(输入的元素等于栈顶元素)
{
flag=1;//标记是对称字符串
break;
}
end if
删除栈顶元素
}
end for
具体代码
1.2本题PTA提交列表说明
编译错误:S.top()写的时候总是漏了括号,vs也没有错误提示。
部分正确:for循环里面if语句里没有加上break,结果总是会输出yes。
1.题目名称:7-3 jmu-ds-符号配对
2.1设计思路代码
●伪代码简要介绍设计思路
初始化一个字符类型的栈s
定义一个字符数组ch
for i=0 to strlen(ch)
{
If(数组元素为’(‘或’[‘或’{‘) 数组元素入栈
else if(数组元素为’)’)
{
If(栈为空) 输出no返回0
If(此时栈顶元素等于’(‘) 出栈
}
else if(数组元素为’]’)
{
If(栈为空) 输出no返回0
If(此时栈顶元素等于’[‘) 出栈
}
else if(数组元素为’}’)
{
If(栈为空) 输出no返回0
If(此时栈顶元素等于’{‘) 出栈
}
}
end for
If(栈不为空) 输出栈顶元素并换行输出no
●具体代码
2.2本题PTA提交列表说明。
编译错误:一开始没注意到编译器是c(gcc),一直提交一直错误。
多种错误:栈空的测试点提示段错误,原因是判断栈空的时候只输出no就结束了,还让程序继续执行下面的语句。
答案错误:判断栈空的时候,输出no并break跳出循环,结果运行发现最终输出了多个no,因此栈空的时候return 0终止程序。
编译错误:写的时候又忘记给s.top()加上括号了,而且vs也没有错误提示,总是容易忘记。
部分正确:若不匹配,输出当前栈顶元素再换行输出no,最后也应该return 0终止程序,否则结果输出会多一个yes。
3.阅读代码
3.1 求前缀表达式的值
3.1.1 该题的设计思路
- 对前缀表达式求值,要从右至左扫描表达式,首先从右边第一个字符开始判断,若当前字符是数字则一直到数字串的末尾再记录下来,若为运算符,则将右边离得最近的两个“数字串”作相应运算,然后以此作为一个新的“数字串”并记录下来;扫描到表达式最左端时扫描结束,最后运算的值即为表达式的值。
该题的算法时间复杂度为O(n),空间复杂度为O(n)。
解题代码:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#define N 30
#define TRUE 1
#define FALSE 0
typedef struct {
double data[N];
int top;
} Stack;
/*
** 入栈
*/
void Push(Stack* ptrs, double item)
{
if (ptrs->top == N - 1) {
printf("Stack is full.\n");
return;
}
else {
ptrs->data[++(ptrs->top)] = item;
return;
}
}
/*
** 岀栈
*/
double Pop(Stack* ptrs)
{
if (ptrs->top == -1) {
printf("Stack is empty.\n");
return 0;
}
else
return ptrs->data[(ptrs->top)--];
}
/*
** 判断是否操作符
*/
int IsOperator(char ch)
{
if (ch == '+' || ch == '-' || ch == '*' || ch == '/')
return TRUE;
else
return FALSE;
}
/*
** 计算
*/
double Calculate(double a, double b, char ch)
{
switch (ch) {
case '+': return a + b; break;
case '-': return a - b; break;
case '*': return a * b; break;
case '/': return a / b;
}
}
int main()
{
char expr[N];
gets_s(expr);
int len = strlen(expr);
Stack ss;
ss.top = -1;
double cc = 1;
double tem_sum = 0;
double operand_a;
double operand_b;
double result;
int error = 0; // 记录除数为0的错误情况
int i;
for (i = len - 1; i >= 0; --i) {
if (expr[i] >= '0' && expr[i] <= '9') {
tem_sum += (expr[i] - '0') * cc;
cc *= 10;
if (expr[i - 1] == '+') {
//printf( "%d\n", tem_sum );
Push(&ss, tem_sum);
tem_sum = 0;
cc = 1;
i -= 2; // 跳过下一个正号和空格
continue;
}
else if (expr[i - 1] == '-') {
tem_sum = -tem_sum;
//printf( "%d\n", tem_sum );
Push(&ss, tem_sum);
tem_sum = 0;
i -= 2; // 跳过下一个负号和空格
continue;
}
else if (expr[i - 1] == ' ') { // 一个数字处理完了
//printf( "%d\n", tem_sum );
Push(&ss, tem_sum);
tem_sum = 0;
cc = 1;
i--;
continue;
}
}
else if (expr[i] == '.') {
tem_sum /= cc * 1.0;
cc = 1;
}
else if (IsOperator(expr[i])) {
operand_a = Pop(&ss);
operand_b = Pop(&ss);
if (expr[i] == '/' && operand_b == 0) {
error = 1;
break;
}
else {
result = Calculate(operand_a, operand_b, expr[i]);
//printf( "result:%.1lf\n", result );
Push(&ss, result);
i--; // 跳过下一个空格
}
}
}
if (error != 1)
printf("%.1lf\n", Pop(&ss));
else
printf("ERROR\n");
return 0;
}
3.1.2 该题的伪代码
定义一个字符数组expr,存放输入的字符串
初始一个空栈ss
for i=strlen(expr)-1 to i>=0
{
if(数组元素在'0'到'9'之间)
{
把数组元素变成可进行运算的数值
if(数组最下个元素为'+')
{
将数值入栈
跳过下一个正号和空格
continue;
}
else if(数组最下个元素为'-')
{
将数值入栈
跳过下一个正号和空格
continue;
}
else if(数组最下个元素为' ')
{
将数值入栈
继续往左扫描
continue;
}
else if(是运算符)
{
将两个数值出栈
if(运算符为'/'而且除数为0)
{
记录下错误情况
break;
}
else
{
进行运算,并将运算结果出栈
跳过下一个空格
}
}
}
}
3.1.3 运行结果
3.1.4分析该题目解题优势及难点。
- 此方法还是比使用链栈来解题操作简单一点,复杂的地方是对输入的处理,需要判断输入是运算符还是操作符分情况处理,特别要注意若运算符是除号,则要考虑除数为0的错误情况。
3.2 根据身高重建队列
3.2.1 该题的设计思路
- 先对数组按照身高从高到低排序,如果身高一样,则按照第二维大小从小到大排序。遍历数组,根据第二维大小将人插到顺序队列中。
解题代码:
3.2.3 运行结果
3.2.4分析该题目解题优势及难点。
- 代码很精简,仅仅几行代码便可实现排序后插入。难点主要是题目不易理解,我们要把身高最高的先插入,并且它的第二位一定是0,接下来要考虑把次高的元素插入队列的哪个元素对队列中的第二位没有影响,以此进行,把剩下较高的元素插入合适位置且不会对原队列产生影响。