NFA识别语言

题目描述

对于给出的NFA和输入的字符串,判断字符串是否是NFA识别的语言。

输入数据

输入有多组数据。每组数据的第一行是两个整数N(N<=50)和M(M<=27),表示NFA有N个状态,以及字母表有M-1个字符。NFA的N个状态用整数0~N-1表示,状态0为起始状态。字母表包含小写英文字母的前M-1个字符。接下来的N行,每行有M个整数集(用'{'和'}'括起)。其中,第i行第1列的整数集表示在状态i-1时,对应于є(空串)的状态迁移;第i行第j(j>1)列的整数集,表示NFA在状态i-1,当输入符号为第j-1个小写字母时,迁移到的状态集。接下来的一行包含若干个整数,代表NFA的接受状态,这一行以-1结尾。接下来的每一行是一个待识别的字符串,字符串的长度在1到50之间且只含有小写字母。字符串"#"代表本组数据结束。N=M=0表示输入结束。

输出数据

对于每个待识别的字符串,如果能被给出的NFA识别,输出YES;否则输出NO。

输入样例

4 3
{} {0,1} {0}
{} {} {2}
{} {} {3}
{} {} {}
3 -1
aaabb
abbab
abbaaabb
abbb
#
0 0

输出样例

YES
NO
YES
NO

题目解析

由于是NFA,有两点情况需要尤其注意:

  • 输入一个字符可能转移到多种状态,因此需要用一个数据结构维护在识别完第i个字符后可能会处于哪些状态
  • 千万小心空串!因为在读入一个字符前可以先由空串转移好几个状态,然后再读入这个字符进行转移。

基于上述分析,在识别的时候我们采用如下步骤(以现在在识别第i个字符为例):

  • 首先遍历识别完第i-1个字符可能处于的状态,对于每个状态都尝试进行识别空串的转移,并把能转移到的状态全部加入队列,直到没有新的可能状态加入队列为止,这是因为一个状态可以识别若干个空串转移到很远的一个状态,所以对于识别空串后转移的状态也需要再进行分析,看是否能转移到新的状态。将现在队列中所有的状态作为识别完第i-1个字符可能处于的状态。
  • 现在开始识别第i个字符,将识别完第i个字符所有可能处于的状态保留下来,用于识别第i+1个字符。

在识别完所有的字符之后,我们还需要进行一次空串的转移判断,即识别完第n个字符后再去识别空串,看看还能转移到哪些状态。

最后,我们遍历最后保留下来的可能处于的这些状态,如果这其中有接收状态,我们就认为这个字符串可以被成功识别。

源代码

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <algorithm>
#include <vector>
using namespace std;

int n, m;		//f[i][j][k]表示输入字母i时能从状态j迁移到状态k
bool f[28][51][51], end_state[51];

int main() {

	while (1) {
		scanf("%d%d", &n, &m);

		if (n == 0 && m == 0) {
			break;
		}

		memset(f, 0, sizeof(f));
		memset(end_state, 0, sizeof(end_state));

		//下面进行状态转移的预处理


		string str;		

		for (int i = 0; i < n; i++) {
			for (int j = 0; j < m; j++) {
				cin >> str;				//注意!题目样例中的{}后面有空格的!
				int len = str.length(), num = 0;
				bool num_begin = false;	//意味着是否开始识别数字

				for (int k = 0; k < len; k++) {
					if (str[k] >= '0' && str[k] <= '9') {
						if (!num_begin) {
							num_begin = true;
						}

						num = num * 10 + str[k] - '0';
					}
					else {
						if (num_begin) {
							num_begin = false;
							f[j][i][num] = 1;
							num = 0;
						}
					}
				}
			}
		}

		/*for (int i = 0; i < m; i++) {
			for (int j = 0; j < n; j++) {
				for (int k = 0; k < n; k++) {
					printf("f[%d][%d][%d] = %d\n", i, j, k, f[i][j][k]);
				}
			}
		}*/
		//状态转移输入结束
		/*下面开始处理空串,即计算一个状态在遇到某一个字符时先经过若干个空串然后再
		输入这个字符可以到达的状态集合*/

		//下面开始预处理接收状态

		int end;
		while (1) {
			scanf("%d", &end);

			if (end == -1) {
				break;
			}

			end_state[end] = true;
		}
		//预处理接收状态结束

		//下面开始识别字符串,看是否可以识别

		



		while (1) {
			string input_str;
			int possible_final_state[50];//记录有哪些状态是识别完这个当前字符可能处于的状态
			int next_state[50];
			int possible_final_state_num = 1, next_state_num = 0;;
			int tmp_state;
			cin >> input_str;
			if (input_str[0] == '#') {
				break;
			}

			memset(possible_final_state, 0, sizeof(possible_final_state));

			int input_len = input_str.length();

			for (int i = 0; i < input_len; i++) {
				for (int j = 0; j < possible_final_state_num; j++) {
					
					//下面开始进行空串的处理,首先找到当前状态可以通过空串走到哪些状态
					for (int k = 0; k < n; k++) {
						if (f[0][possible_final_state[j]][k] == 1) {
							tmp_state = k;

							bool hasbeentrans = false; // 用于判断当前状态是否查找过空串可到达状态
							for (int l = 0; l < possible_final_state_num; l++) {
								if (possible_final_state[l] == tmp_state) {
									hasbeentrans = true;
								}
							}

							// 如果没有查找过,则加入数组记录
							if (hasbeentrans == false) {
								possible_final_state[possible_final_state_num++] = tmp_state;
							}
						}
					}
					//空串处理结束


					// 下面开始字符读入处理,查找读入当前字符能到达的状态
					for (int k = 0; k < n; k++) {
						//printf("f[%d][%d][%d] = %d\n", input_str[i] - 'a', possible_final_state[j], k, f[input_str[i] - 'a'][possible_final_state[j]][k]);
						if (f[input_str[i] - 'a' + 1][possible_final_state[j]][k] == 1) {
							tmp_state = k;

							bool hasbeenfind = false;
							for (int l = 0; l < next_state_num; l++) {
								if (tmp_state == next_state[l]) {
									hasbeenfind = true;
								}
							}

							if (hasbeenfind == false) {
								next_state[next_state_num++] = tmp_state;
							}
						}
					}
				}
					
				//现在计算出的next_state就是读入当前字符后可能处于的所有状态
				//我们把它赋值给possible_final_state,进行下一轮迭代
				for (int k = 0; k < next_state_num; k++) {
					possible_final_state[k] = next_state[k];
				}

				possible_final_state_num = next_state_num;

				/*for (int l = 0; l < possible_final_state_num; l++) {
					printf("%d ", possible_final_state[l]);
				}

				puts("");*/

				//printf("possible_num = %d\n", possible_final_state_num);
				next_state_num = 0;
			}
			
			//现在已经读入了所有的字符,我们再看看它再读入空串能到达哪些状态
			for (int j = 0; j < possible_final_state_num; j++) {
				int tmp_state;
				for (int k = 0; k < n; k++) {
					if (f[0][possible_final_state[j]][k] == 1) {
						tmp_state = k;

						bool hasbeentrans = false; // 用于判断当前状态是否查找过空串可到达状态
						for (int l = 0; l < possible_final_state_num; l++) {
							if (possible_final_state[l] == tmp_state) {
								hasbeentrans = true;
							}
						}

						// 如果没有查找过,则加入队列
						if (hasbeentrans == false) {
							possible_final_state[possible_final_state_num++] = tmp_state;
						}
					}
				}
			}

			/*puts("-------------------");

			for (int l = 0; l < possible_final_state_num; l++) {
				printf("%d ", possible_final_state[l]);
			}

			puts("");*/

			bool accepted = false;
			for (int j = 0; j < possible_final_state_num; j++) {
				//printf("!%d\n", possible_final_state[j]);

				if (end_state[possible_final_state[j]] == true) {
					accepted = true;
					break;
				}
			}

			if (accepted) {
				puts("YES");
			}
			else {
				puts("NO");
			}
		}

	}

	return 0;
}

posted @ 2021-01-02 16:17  随心所欲丶蜻蜓卡兹克  阅读(455)  评论(0)    收藏  举报