实验B----CFG是P成员

<span style="font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255);">Problem description</span>
上下文无关文法CFG G是否派生某个串W。采用动态规划(Dynamic Programming)设计一个一个多项式时间的验证算法。
  实验方法:
  编写一个算法/程序,对于给定的输入,可以在多项式时间内判定ACFG
  实验结果:
  交一个程序验证。

Input
输入第一行为一个正整数n,接下来n行为一个满足乔姆斯基范式的文法描述。然后一个正整数m,接下来m行为m个小写字母组成的字符串(长度小于100) 表示m个待测试的串。
Output
对于每一个测试串返回"yes"或者"no",表示该串是否能被文法派生出来。
Sample Input
4
S->AB
A->AB|a
B->BC|b
C->CA|CC|c
3
ab
ac
bc
Sample Output
yes
no
no

解题思路:利用动态规划填表(n*n的对角线,n为待测串儿的长度)。在规则中有两种规则分别是A->XY和A->a,于是遍历规则,分成两个规则数组rule1和rule2,目的是为了方便查找。

在填表时,一个格子里可能会有多个变量,比如A->a,B->a,那么A和B都要填在一个格子里;同时,也可能会重复出现,如:A->AB|CB,而现在的表正好是:

A,C A,A
  B  
所以A会在一个格子里出现多次。

为了解决“判重”以及多个变量需要开三维char型数组(占很大的内存)的问题,可以采用特征串。只需要一个一维的int数组,大小为26,对应26个字母是有(1)还是无(0)。所以对于表格,只需要一个二维整型数组int T[n][n](n是待测串的长度)。

填表的时候通过位或操作:T[j][j]|=(1<<(rule1[p][0]-'A'));

查找特征串表示的字母时用按位与操作:(T[p][k]&(1<<(zimu-'A')))==(1<<(zimu-'A')) 判断zimu是否在T[p][k]表示的特征串中。

代码如下:

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<fstream.h>
int main()
{
	freopen("testfile.txt","r",stdin);
	//输入的处理
	int n,m;
	char rules[30][100];
	char rule1[30][100];
	char rule2[30][30];
	char test[300][300];//待测的串
	int T[300][300];//动态规划所填的表
	char right[1000];//用来保存格子里的大写字母(会不止一个,所以用数组)
	char left[1000];
	int sizer,sizel;//记录表的一个格子里有几个大写字母
	int i,j,p,q,k,l,w,z,r,index;
	//length表示一条规则的长度。
	//len表示待测串的长度
	//size1表示一条规则里可能会有多少个终结符
	//size2表示一条规则里可能会有多少对两个大写字母
	int length,size1,size2,len;
	
	scanf("%d",&n);//n条规则
	for(i=0;i<n;i++)
		scanf("%s",&rules[i]);
	scanf("%d",&m);//m个待测的串	
	for(i=0;i<m;i++)
	{
		scanf("%s",&test[i]);
	}
	
	for(i=0;i<n;i++)//第i行规则
	{
		length=strlen(rules[i]);
	//	printf("第一条规则的长度是:%d\n",length);
		size1=1;
		size2=1;
		for(j=3;j<length;j++)
		{
			//查找A->a这种派生,并放入rule1中
			if(rules[i][j]>='a'&&rules[i][j]<='z')//如果在第i行规则中第j个位置找到了终结符
			{
				rule1[i][0]=rules[i][0];
				rule1[i][size1++]=rules[i][j];
				//printf("!!!%c\n",rule1[i][size1-1]);
			}
			rule1[i][size1]='\0';
			//查找S->AB这种派生,并放入rule2中
			if(rules[i][j]>='A'&&rules[i][j]<='Z'&&rules[i][j+1]>='A'&&rules[i][j+1]<='Z')//如果有连续两个大写字母
			{
				rule2[i][0]=rules[i][0];
				rule2[i][size2++]=rules[i][j];
				rule2[i][size2++]=rules[i][j+1];
			}
			rule2[i][size2]='\0';
		}
	//	printf("%d\n",size1);
	//	printf("%d\n",size2);
		/*
		//测试rule1的值
		for(j=0;j<size1;j++)
		{
			printf("%c",rule1[i][j]);
		}
		printf("\n");
		//测试rule2的值
		printf("%c",rule2[i][0]);
		for(j=1;j<size2;j+=2){
			printf("%c",rule2[i][j]);
			printf("%c",rule2[i][j+1]);
		}
		printf("\n");*/
	}

	//上述代码已经把规则分别保存到了rule1和rule2中

	//下面开始用位或来填表
	int S=1<<(rules[0][0]-'A');//用数字代替字符,统一转化标准
	for(i=0;i<m;i++)//先处理第一个串儿
	{
		//初始化表T
		for(k=0;k<300;k++)
			for(p=0;p<300;p++)
				T[k][p]=0;
		//先填对角线
		len=strlen(test[i]);//第一个待测串的长度为len
		for(j=0;j<len;j++)//从第一个待测串的第一个小写字母开始
		{
			for(p=0;p<n;p++)//第p行规则
			{
				size1=strlen(rule1[p]);
				//printf("size1: %d",size1);
				for(q=1;q<size1;q++)
				{
				//	printf("......%c\n",rule1[0][1]);
					if(test[i][j]==rule1[p][q])//在第P行规则的q位置找到终结符
					{
						T[j][j]|=(1<<(rule1[p][0]-'A'));//按位或避免 判重 !如果在多行规则里都能找到一样的终结符
					//	printf("p is:%d !!%d\n",p,T[j][j]);
					//	printf("...%c\n",rule1[p][q]);
						break;
					}
				}
			}
			/*
			for(k=0;k<len;k++)
				printf("%d\n",T[k][k]);*/
		}
		//以上填好了斜对角线
		//下面填其他的对角线了!
		for(l=1;l<len;l++)//剩余多少条对角线
		{
			//k=0;
			//for(p=0,q=l+1;p<len-l-1,q<=len-l;p++,q++)//要填的格子是T[p][q]
			for(p=0;p<=len-l-1;p++)
			{
				q=p+l;
				//k=0;//用于分裂位置
				//找存在的字母
				char zimu;
			//	while(k>=p&&(k+1)<=q){
				for(k=p;k>=p&&(k+1)<=q;k++){
					sizel=0;
					sizer=0;
					for(zimu='A';zimu<='Z';zimu++)//把T[p][k]与T[k+1][q]中的所有字母都放在了left和right中
					{
						//printf("zimu 开始是:%c\n",zimu);						
						if((T[p][k]&(1<<(zimu-'A')))==(1<<(zimu-'A')))//在T[p][k]中找到了大写字母zimu
						{
						//	printf("T[%d][%d]:%d\n",p,k,T[p][k]);
						//	printf("current zimu is:%c\n",zimu);
							left[sizel++]=zimu;
						//	printf("left zimu is:%c\n",zimu);
						}
						if((T[k+1][q]&(1<<(zimu-'A')))==(1<<(zimu-'A')))//在T[k+1][q]中找到了大写字母zimu
						{
						//	printf("T[%d][%d]:%d\n",k+1,q,T[k+1][q]);
						//	printf("current zimu is:%c\n",zimu);
							right[sizer++]=zimu;
						//	printf("right zimu is:%c\n",zimu);
						}
					}
					
					for(w=0;w<sizer;w++)
						for(z=0;z<sizel;z++)
						{
							for(r=0;r<n;r++)//第r行规则
							{
								size2 = strlen(rule2[r]);
								for(index=1;index<size2;index+=2)
									if((left[z]==rule2[r][index])&&right[w]==rule2[r][index+1])
									{
										T[p][q]|=(1<<(rule2[r][0]-'A'));
									}
							}
						}
					}
			//	printf("T[%d][%d]:%d\n",p,q,T[p][q]);
				}
			}
			if((T[0][len-1]&S)==S)//如果找到S
				printf("yes\n");
			else
				printf("no\n");
		}
	return 0;
}


通过这次实验,不仅接触了位操作的便利,也体会到了文件读操作的简便。

#include<fstream.h>

freopen("testfile.txt","r",stdin);

实现直接从testfile.txt中读所有的输入,代码里正常的scanf就行,只是不从dos界面获取输入,转而从文件中获取所需的输入!




展开阅读全文

没有更多推荐了,返回首页