201903-4 csp 消息传递接口
消息传递接口(201903-4 csp认证考题)
题目描述
老师给了 T 份 MPI 的样例代码,每份代码都实现了 n 个进程通信。这些进程标号 从 0 到 n − 1,每个进程会顺. 序. 执. 行. 自己的收发指令,如:“S x”,“R x”。“S x”表示向 x 号进程发送数据,“R x”表示从 x 号进程接收数据。每一对收发命令必须匹配执行才 能生效,否则会“死锁”。
举个例子,x 号进程先执行发送命令“S y”,y 号进程必. 须. 执行接送命令“R x”,这 一对命令才执行成功。否则 x 号进程会一直等待 y 号进程执行对应的接收命令。反之, 若 y 号进程先执行接收命令“R x”,则会一直等待 x 号进程执行发送命令“S y”,若 x 号进程一直未执行发送命令“S y”,则 y 号进程会一直等待 x 号进程执行对应的发送 命令。上述这样发送接收命令不匹配的情况都会造成整个程序出现“死锁”。另外,x 号.进.程.不.会.执.行.“S x”或.“R x”,即.不.会.从.自.己.的.进.程.收.发.消.息.。
现在老师请你判断每份样例代码是否会出现“死锁”的情况。每个进程的指令最少 有 1 条,最多有 8 条,这些指令按顺序执行,即第一条执行完毕,才能执行第二条,依 次到最后一条。
输入格式
从标准输入读入数据。
输入第一行两个正整数 T, n,表示有 T 份样例代码,实现了 n 个进程通信。 接下来有 T × n 行,每行有若干个(1 − 8 个)字符串,相邻之间有一个空格隔开,
表示相应进程的收发指令。不存在非法指令。对于第 2 + i, 0 ≤ i ≤ (T × n − 1) 行,表示 第 i ÷ n(商)份代码的 i ??? n(余数)号进程的收发指令。
(比如,“S1”表示向 1 号进程发送消息,“R1”表示从 1 号进程接收消息。细节请 参考样例。)
输出格式
输出到标准输出。
输出共 T 行,每行一个数字,表示对应样例代码是否出现“死锁”的情况。1 表示 死锁,0 表示不死锁。
样例
【样例1】
输入:
3 2
R1 S1
S0 R0
R1 S1
R0 S0
R1 R1 R1 R1 S1 S1 S1 S1
S0 S0 S0 S0 R0 R0 R0 R0
输出:
0
1
0
解释:
第 1 份代码中,(1)0 号进程执行的“R1”和 1 号进程执行的“S0”成功执行;(2) 0 号进程执行的“S1”和 1 号进程执行的“R0”成功执行,所以未发生“死锁”,程序 顺利运行。
第 1 份代码中,(1)0 号进程执行的“R1”和 1 号进程执行的“R0”一直在等待 发送命令,进入“死锁”状态。
【样例2】
输入:
2 3
R1 S1
R2 S0 R0 S2
S1 R1
R1
R2 S0 R0
S1 R1
输出:
0
1
解释:
第 1 份代码中,(1)2 号进程执行的“S1”和 1 号进程执行的“R2”成功执行;(2) 0 号进程执行的“R1”和 1 号进程执行的“S0”成功执行;(3)0 号进程执行的“S1” 和 1 号进程执行的“R0”成功执行;(4)1 号进程执行的“S2”和 2 号进程执行的
“R1”成功执行;所以未发生“死锁”,程序顺利运行。
第 1 份代码中,(1)2 号进程执行的“S1”和 1 号进程执行的“R2”成功执行;(2)
0 号进程执行的“R1”和 1 号进程执行的“S0”成功执行;(3)1 号进程执行的“R0” 和 2 号进程执行的“R1”一直在等待发送命令;进入“死锁”状态。
题解
读完题最直观的思考应该是:找到每个进程的第一个指令后边数字对应的进程,并判断这个两个进程的第一个指令是否能相互对应也就是不会产生“死锁”。这里考虑使用队列,每次取出队首的指令(即每个进程的第一个指令),然后判断,有对应的指令就删除这两个相对应的指令,没有就继续下一个进程直到所有的进程中的指令都为空,这时说明没有产生“死锁”;当有一个进程的第一个指令后边数字所指示的进程中指令为空或者是所有进程的第一个指令都找不到对应的操作时说明产生了死锁。先附上这种思路的代码(没注释,进阶思路有注释):
#include<iostream>
#include<queue>
#include<string>
#include<cstdio>
#include<cstdlib>
using namespace std;
struct order
{
int num;
int sym;
};
int tonum(string str)
{
int i,num=0,flag=1;
for(i=str.size()-1;i>0;--i)
{
num+=flag*(str[i]-'0');
flag*=10;
}
return num;
}
void split(string str,queue<order> &que)
{
int i,sum=0,sta=0,lth=str.size();
string s;
for(i=0;i<lth;++i)
{
if(str[i]==' ')
{
order mid;
s=str.substr(sta,sum);
if(s[0]=='S')
{
mid.sym=0;
}
else
{
mid.sym=1;
}
mid.num=tonum(s);
que.push(mid);
sta=i+1;
sum=0;
}
else
{
++sum;
}
}
order mid;
s=str.substr(sta,str.size()-sta);
if(s[0]=='S')
{
mid.sym=0;
}
else
{
mid.sym=1;
}
mid.num=tonum(s);
que.push(mid);
}
int main()
{
int T,n,i,sum,flag,j;
string mid;
bool lock;
while(cin>>T>>n)
{
getchar();
while(T--)
{
sum=0;
queue<order> que[n];
int res[n];
for(i=0;i<n;++i)
{
getline(cin,mid);
split(mid,que[i]);
sum+=que[i].size();
res[i]=que[i].size();
}
if(sum%2!=0)
{
cout<<"1"<<endl;
continue;
}
lock=false;
while(sum>0)
{
flag=0;
for(i=0;i<n;++i)
{
if(res[i]!=0)
{
j=que[i].front().num;
if(res[j]!=0)
{
if((que[i].front().sym^que[j].front().sym==1)
&&(que[j].front().num==i))
{
que[i].pop();
--res[i];
que[j].pop();
--res[j];
sum-=2;
break;
}
else
{
++flag;
}
}
else
{
++flag;
}
}
else
{
++flag;
}
if(flag==n)
{
lock=true;
sum=0;
break;
}
}
}
if(lock)
{
cout<<"1"<<endl;
}
else
{
cout<<"0"<<endl;
}
}
}
return 0;
}
这种思路提交后会超时,不过也能拿到90分了。之后去看了一下其他博主的题解,发现基本都是递归解决,不太喜欢递归,所以白嫖是不可能白嫖的,还是自己想吧。
这里其实稍稍做一点改变就好,我们的思路是查找当前进程的第一个指令有没有一个进程的第一个操作可以与之对应,因此需要不断的从第一个进程开始遍历查找,但这里其实做了大量不必要的判断,因为当第一遍遍历时如果第一个进程的第一个指令始终没有找到对应的指令那么跳过这个进程就好,这里有两种情况。其一,第一个进程的第一个指令对应的指令确实没有跳过也无妨;其二,上一次遍历的时候有一对指令被删去了,之后的新的两个指令中有第一个进程中第一个指令对应的指令,不过由于第一个进程所对应的指令一定是在之后的进程,因此,跳过第一个进程也不会影响指令对应的判断。
这里进一步思考,因为可能出现对应指令的进程只能是上一次进程中指令发生更新的进程,因此每次只需查找指令更新的进程即可,也就是是说跳过没有发生指令更新的进程和空的进程即可,这里通过res数组和vis数组标记。另外,查找的总趋势还是由序号小的进程向序号大的进程查找,这样可以保证每次之前跳过的进程都是没有更新的进程,并且所有的更新的进程都有可能被查找一边。这样,每次有进程出现更新后,下一轮查找从更新的进程序号中更小的一个开始。下面附上代码(有注释,运行结果100分,运行时间300ms左右):
//每次从可能有对应的指令的进程查找,避免不必要的
//查找过程,因此从有更新的进程中查找即可
#include<iostream>
#include<queue>
#include<string>
#include<cstdio>
#include<cstdlib>
#include<cstring>
using namespace std;
struct order
{
int num;
int sym;
};//结点,存储每个进程中的信息。
//sym存储传送方式是”发送(S)“还是“接受(R)”
//num代表这是发往还是接受第几个进程的信息
int tonum(string str)
{//将每个进程信息中指令的数字字符转为数字
int i,num=0,flag=1;
for(i=str.size()-1;i>0;--i)
{
num+=flag*(str[i]-'0');
flag*=10;
}
return num;
}
void split(string str,queue<order> &que)
{//将接收的字符串处理为进程中的单个指令
int i,sum=0,sta=0,lth=str.size();
string s;
for(i=0;i<lth;++i)
{
if(str[i]==' ')
{
order mid;
s=str.substr(sta,sum);
if(s[0]=='S')
{
mid.sym=0;
}
else
{
mid.sym=1;
}
mid.num=tonum(s);
que.push(mid);
sta=i+1;
sum=0;
}
else
{
++sum;
}
}
order mid;
s=str.substr(sta,str.size()-sta);
if(s[0]=='S')
{
mid.sym=0;
}
else
{
mid.sym=1;
}
mid.num=tonum(s);
que.push(mid);
}
int main()
{
int T,n,i,sum,flag1,flag2,j;
string mid;
bool lock;
while(cin>>T>>n)
{
getchar();
while(T--)
{
sum=0;
queue<order> que[n];//que[i]存储第i个进程中的所有指令
int res[n];//记录每个进程中有多少个指令
bool vis[n];//记录每个进程当前要执行的指令是否有对应的接收或者
//发送指令
memset(vis,true,sizeof(vis));
for(i=0;i<n;++i)
{
getline(cin,mid);
split(mid,que[i]);
sum+=que[i].size();
res[i]=que[i].size();
}
if(sum%2!=0)
{//当指令的数量不是偶数说明一定死锁
cout<<"1"<<endl;
continue;
}
lock=false;//标记进程是否死锁
i=0;
flag1=0;//记录当前有多少个进程已经没有指令了
while(sum>0)
{//当所有进程中的指令总数变为0的时候结束
flag2=i;//每次从第i个进程的第一个指令开始搜索,看有没有配对的指令
//i之前的都是vis标记为false的进程,代表这些进程的第一个指令没有与
//之配对的指令或者是空的进程,因此无需浪费时间判断
for(;i<n;++i)
{
if(!vis[i])
{//当vis[i]=false时说明,之前的进程的第一个指令都是没有与之
//配对的指令或者是空进程因此判断下一个进程
++flag2;
}
else
{
break;
}
}
if(flag1>=n)
{//如果都是空进程说明没有死锁现象
break;
}
else if(flag2>=n)
{//如果所有进程都是没有与之配对的或者是空进程说明
//出现了死锁现象
lock=true;
break;
}
else
{
j=que[i].front().num;//第i个进程的第一个指令可能有配对的指令,
//因此取出第一个指令对应的进程
if(res[j]>0)
{//如果此进程不为空,则继续判断
if((que[i].front().sym^que[j].front().sym==1)&&(que[j].front().num==i))
{
que[i].pop();//找到对应的指令后将两个对应的指令从进程中去掉
vis[i]=true;//同时将vis[i]和vis[j]标记为true,说明第i个进程
//和第j个进程可能有与之对应的指令
--res[i];//对应进程中的指令数减1
if(res[i]<=0)
{//当第i个进程中的指令数量小于等于0的时候将flag1标记加1
//指令为0的进程只会增加不会减少
++flag1;
vis[i]=false;//如果为0则永远不会找到与之对应的指令
//因此标记为false
}
que[j].pop();
vis[j]=true;
--res[j];
if(res[j]<=0)
{
++flag1;
vis[j]=false;
}
sum-=2;//指令数的总数减2
i=j>i?i:j;//改变i的值,每次从序号最小的进程开始查找
//保证每一个可能有对应指令的进程都都可以被查找一遍
}
else
{//如果没有对应的指令则将vis[i]置为false
vis[i]=false;
++i;//并且i+1,即下一次从第i+1个进程开始查找
if(i>=n)
{//如果i>=n说明所有的进程都找不到对应的指令了,即
//出现了死锁
lock=true;
break;
}
}
}
else
{//对应进程为空说明当前进程的第一个指令永远找不到对应的指令
//也就是出现了死锁现象
lock=true;
break;
}
}
}
if(lock)
{
cout<<"1"<<endl;
}
else
{
cout<<"0"<<endl;
}
}
}
return 0;
}