软件理论课程作业(附件含代码)

目录

1. 设计一个DFA,使其接受2进制字符串w,并且所有w的逆都能被5整除。比如,该DFA接受二进制串11110,因为其逆为01111,代表十进制的15,所以可以被5整除。详述该DFA的设计。

1.1设计思路

1.2设计流程

1.2.1接收正序整除5字符串的DFA设计

1.2.2接收逆序整除5字符串的NFA设计

1.2.3NFA转DFA

2. 使用归纳证明法证明对于任意的NFA M=(Q, Σ,δ,q0,F) 一定存在CFG G=(Q, Σ,P,S) ​​​​​​​ ,使得​​​​​​​LG=L(M) ​​​​​​​

3. 正则表达式在信息检索中具有广泛的应用。请设计并编程实现一个基于正则表达式的英文单词检索系统,要求给定任意的正则表达式作为输入,将其转换为等价的自动机,并可以根据该自动机实现从输入文本中检测并输出所有符合正则表达式描述的单词。(C++实现)

3.1 设计思路

3.2 详细流程及关键代码

3.2.1正则表达式解析:添加显式连接符

3.2.2 正则表达式解析:中缀表达式转后缀表达式

3.2.3 McMaughton-Yamada-Thompson算法构建NFA

3.2.4 文本预处理

3.2.5 判断NFA是否接收字符串

3.2.6 系统类图

 3.3 实验结果

4. 请设计并编程实现一个简单分类器,要求各类别规则用正则表达式描述,实现输入任意一个字符串,自动给出该串的类别编号。例如定义类别1:0*1*;类别2:1*0*。则给出串0011,程序将输出结果为类别1。

4.1 设计思路

4.2 关键代码

4.3 实验结果

附件


1. 设计一个DFA,使其接受2进制字符串w,并且所有w的逆都能被5整除。比如,该DFA接受二进制串11110,因为其逆为01111,代表十进制的15,所以可以被5整除。详述该DFA的设计。

1.1设计思路

        先设计出能接受二进制字符串w且w能被5整除的DFA,考虑题目要求到w的逆能整除5,我们可以对原DFA的转移函数中的状态进行互调,再将开始状态作为接收状态,将接收状态作为开始状态,将得到一个NFA,再通过子集构造法,将NFA转化为新的DFA,该DFA便可接收受二进制字符串w,并且所有w的逆都能被5整除。

1.2设计流程

1.2.1接收正序整除5字符串的DFA设计

        一个二进制数能除5,余数可能存在5种情况,DFA设计5个状态q0至q4表示当前余数分别为:0、1、2、3、4。对于二进制数,末尾新增1时表示原数乘2加1,新增0表示原数乘2,所以对于不同的状态,我们可以构建下面的状态转移表,标红表示为接收状态,“→”表示开始状态:

接收符号

状态

0

1

→q0

q0

q1

q1

q2

q3

q2

q4

q0

q3

q1

q2

q4

q3

q4

        对应的DFA图如下:

       为了防止接收空串,还需要对上面的DFA进行修改,修改后如下:

1.2.2接收逆序整除5字符串的NFA设计

根据设计思路,对原DFA进行修改,得到如下NFA图:

1.2.3NFA转DFA

        通过子集构造法进行转换,转换后的DFA状态转移表如下:

接收符号

状态

0

1

→A={q0}

{q0, q5}

{q2}

B=*q0, q5

{q0}

{q2}

C={q2}

{q1}

{q3}

D={q1}

{q3}

{q0, q5}

E={q3}

{q4}

{q1}

F={q4}

{q2}

{q4}

        该DFA可以接收二进制字符串w,并且所有w的逆都能被5整除,对应DFA图如下:

       通过几组字符串进行的验证,结果如下表,可以看出我们设计的DFA满足题目要求。

输入串w

w的逆

逆序十进制数

状态转移流程

接收情况

101

101

5

A→C→D→B

接收

1010

0101

10

A→C→D→B→B

接收

11110

01111

15

A→C→E→D→B→B

接收

00101

10100

20

A→B→B→C→D→B

接收

10011

11001

25

A→C→D→E→D→B

接收

10

01

1

A→C→D

不接收

110

011

3

A→B→C→E

不接收

1011

1101

13

A→C→E→F→F

不接收

10000

00001

1

A→C→D→E→F→C

不接收

01011

11010

26

A→B→C→D→B→C

不接收

2. 使用归纳证明法证明对于任意的​​​​​​​NFA M=(Q, Σ,δ,q0,F) ​​​​​​​ 一定存在​​​​​​​CFG G=(Q, Σ,P,S) ​​​​​​​ ,使得​​​​​​​LG=L(M) ​​​​​​​


3. 正则表达式在信息检索中具有广泛的应用。请设计并编程实现一个基于正则表达式的英文单词检索系统,要求给定任意的正则表达式作为输入,将其转换为等价的自动机,并可以根据该自动机实现从输入文本中检测并输出所有符合正则表达式描述的单词。(C++实现)

        例如,当输入文本文件input.txt包含以下英文文本及正则表达式s{a,…,z}*n时:

        Shenzhen University (SZU, Chinese: 深圳大学) is a public university established in 1983 located in Nanshan district, Shenzhen, Guangdong, China. It is accredited by the State Council of the People's Republic of China and is funded by the Shenzhen City Government. The university took its first enrollment the same year at what Deng Xiaoping called "Shenzhen Speed". Deng also was named the "father of Shenzhen University." It is regarded as the fastest developing university in China, and also one of the "Top 100 Universities in China" and one of the top university which is listed in the World Top Ranking Universities.

输出:

Shenzhen

Shenzhen

Shenzhen

Shenzhen

Shenzhen

为所有符合该正则表达式描述得单词,注意,此处单词间以空格分隔,并且对大小写字母不敏感。请在提交报告中给出详细的设计方案、关键代码实现、以及实验测试结果。

3.1 设计思路

        正则表达式检索引擎大致可分为基于NFA和基于DFA两类,要实现该检索引擎则需要将正则表达式先转化为相应的有限自动机FA,再将文本中的每个单词作为FA的输入,通过FA最终是否到达终结状态,判断单词是否匹配该正则表达式。

        本实验根据文章《Implementing a Regular Expression Engine》的提供思路,用C++语言实现了基于NFA的正则表达式检索系统。系统实现流程图如下:

3.2 详细流程及关键代码

3.2.1正则表达式解析:添加显式连接符

        在构建NFA时,对于原始的正则表达式,程序并不能直接知道该如何连接NFA,我们需要先对其进行预处理,通过添加“.”表示连接两个NFA,例如正则表达式“abc”和“(a|b)c”添加连接符后分别为“a·b·c”和“(a|b)·c”。连接规则如下表,“√”表示在两个符号间添加连接符,“×”表示不添加。

右侧符号

左侧符号

(

)

*

+

|

字母

(

×

×

×

×

×

×

)

×

×

×

×

*

×

×

×

×

+

×

×

×

×

|

×

×

×

×

×

×

字母

×

×

×

×

因为ASICII中没有“·”,实际代码用“.”替代,并通过const char定义各类字符,代码如下:

string CInfixToPostfix::InsertConcatOperator()
{
	m_ConcatExp = "";
	for (unsigned i = 0; i < m_Exp.size(); i++) {
		char token = m_Exp[i];
		//cout << "token: " << token << endl;
		m_ConcatExp += token;
		if (token == LEFT_BRACKET_OPERATOR || token == UNION_OPERATOR) {
			continue;
		}
		if (i < m_Exp.size() - 1) {
			char tokenAhead = m_Exp[i + 1];
			if (tokenAhead == CLOSURE_OPERATOR || tokenAhead == RIGHT_BRACKET_OPERATOR ||
				tokenAhead == UNION_OPERATOR || tokenAhead == ONE_OR_MORE_OPERATOR) {
				continue;
			}
			m_ConcatExp += CONCATENATION_OPERATOR;
			//cout << "tokenAhead: " << tokenAhead << endl;
		}
		//cout << "m_ConcatExp: " << m_ConcatExp << endl;
	}
	return m_ConcatExp;
}

3.2.2 正则表达式解析:中缀表达式转后缀表达式

        因为运算符之间具有不同的优先级,而中缀正则表达式不利于分析它们之间的优先级关系,所以需要通过栈处理将添加了连接符的正则表达式转为后缀表达式。

        运算符优先级:闭包=正闭包>连接符>并,对应代码如下:

CInfixToPostfix::CInfixToPostfix()
{
	m_Map.insert(pair<char, int>(UNION_OPERATOR, 1));
	m_Map.insert(pair<char, int>(CONCATENATION_OPERATOR, 2));
	m_Map.insert(pair<char, int>(ONE_OR_MORE_OPERATOR, 3));
	m_Map.insert(pair<char, int>(CLOSURE_OPERATOR, 3));
}

        根据其优先级设置转化规则:

        1. 遇到左括号,将左括号直接入栈;

        2. 遇到右括号,将栈中左括号之前的元素依次出栈并保存;

        3. 遇到运算符,依次弹出栈顶优先级大于或等于该运算符的运算符,然后将其入栈

        4. 遇到其他符号,直接入栈;

        5. 字符串读取完成后,将栈中剩余元素依次出栈并保存,最后得到后缀表达式。

        实现代码如下:

/*中缀表达式转后缀表达式*/
string CInfixToPostfix::IntfixToPosfix()
{
	m_PostfixExp = "";
	for (int i = 0; i < m_ConcatExp.size(); i++) {
		char token = m_ConcatExp[i];
		//cout << "token: " << token << endl;
		//遇到右括号,左括号前的元素全部出栈
		if (token == RIGHT_BRACKET_OPERATOR) {
			while (!m_ExpStack.empty() && m_ExpStack.top() != LEFT_BRACKET_OPERATOR) {
				m_PostfixExp += m_ExpStack.top();
				m_ExpStack.pop();
			}
			m_ExpStack.pop(); //删除左括号
			//cout << m_Stack.size() << endl;
		}
		//遇到运算符,依次弹出栈顶优先级大于或等于该运算符的运算符,然后将其入栈
		else if (token == CONCATENATION_OPERATOR || token == CLOSURE_OPERATOR ||
			token == UNION_OPERATOR || token == ONE_OR_MORE_OPERATOR) {
			while (!m_ExpStack.empty() && m_Map[m_ExpStack.top()] && (m_Map[m_ExpStack.top()] >= m_Map[token])) {
				m_PostfixExp += m_ExpStack.top();
				//cout << "m_Stack.top(): " << m_Stack.top() << endl;
				m_ExpStack.pop();
			}
			m_ExpStack.push(token);
		}
		//遇到左括号,入栈
		else if (token == LEFT_BRACKET_OPERATOR) {
			m_ExpStack.push(token);
		}
		//其他符号直接入栈
		else {
			m_PostfixExp += token;
		}
		//cout << "m_PostfixExp: " << m_PostfixExp << endl;
	}
	while (!m_ExpStack.empty()) {
		m_PostfixExp += m_ExpStack.top();
		m_ExpStack.pop();
	}
	//cout << "m_PostfixExp: " << m_PostfixExp << endl;
	return string();
}

3.2.3 McMaughton-Yamada-Thompson算法构建NFA

        我们可以通过McMaughton-Yamada-Thompson算法将正则表达式转换为对应的NFA,它包含基本规则和归纳规则。

        基本规则:处理没有运算符的子表达式。

        1. 当表达式为空时,构造如下NFA:

        2. 当表达式为a时,构造如下NFA:

        基本规则实现代码如下:

CNFA CAutomata::BasicNFA(char token)
{
	CState startState(false);
	CState endState(true);

	startState.SetStateNum(m_Count++);
	endState.SetStateNum(m_Count++);

	if (token) {
		startState.AddTransition(token, endState);
	}
	else {
		startState.AddEpsilionTransition(endState);
	}

	m_AllStates.push_back(startState);
	m_AllStates.push_back(endState);

	CNFA *nfa = new CNFA(startState, endState);
	return *nfa;
}

        归纳规则:利用运算符从较小的NFA构造较大的NFA。假设有两个正则表达式S和T,他们对应的NFA分别为N(S)和N(T),对于如下的正则表达式R,构造其N(R)。

        1. 并“|”:当R=S|T时,构造如下N(R):

        2. 连接“·”:当R=S·T时,构造如下N(R):

        3. 正闭包“+”:当R=S+时,构造如下N(R):

        4. 闭包“*”:当R=S*时,构造如下N(R):

        归纳规则部分实现代码如下:

CNFA CAutomata::UnionNFA(CNFA &NFA1, CNFA &NFA2)
{
	CState startState(false);
	CState endState(true);

	startState.SetStateNum(m_Count++);
	endState.SetStateNum(m_Count++);

	startState.AddEpsilionTransition(NFA1.GetStartState());
	startState.AddEpsilionTransition(NFA2.GetStartState());

	NFA1.GetEndState().AddEpsilionTransition(endState);
	NFA1.GetEndState().SetIsEndState(false);
	NFA2.GetEndState().AddEpsilionTransition(endState);
	NFA2.GetEndState().SetIsEndState(false);

	m_AllStates.push_back(startState);
	m_AllStates.push_back(endState);
	int temp1 = NFA1.GetEndState().GetStateNum();
	int temp2 = NFA2.GetEndState().GetStateNum();
	for (unsigned i = 0; i < m_AllStates.size(); i++) {
		if (m_AllStates[i].GetStateNum() == temp1 ||
			m_AllStates[i].GetStateNum() == temp2) {
			m_AllStates[i].AddEpsilionTransition(endState);
			m_AllStates[i].SetIsEndState(false);
		}
	}
	//cout << "Debug:" << NFA1.GetEndState().GetStateNum() << endl;
	//cout << "Debug:" << NFA1.GetEndState().m_EpsilionTransition.size() << endl;
	CNFA *nfa = new CNFA(startState, endState);
	return *nfa;
}


CNFA CAutomata::ConcatNFA(CNFA &NFA1, CNFA &NFA2)
{
	CState startState = NFA1.GetStartState();
	CState endState = NFA2.GetEndState();

	NFA1.GetEndState().AddEpsilionTransition(NFA2.GetStartState());
	NFA1.GetEndState().SetIsEndState(false);

	int temp = NFA1.GetEndState().GetStateNum();
	//cout << "temp = " << temp << endl;
	for (unsigned i = 0; i < m_AllStates.size(); i++) {
		if (m_AllStates[i].GetStateNum() == temp) {
			m_AllStates[i].AddEpsilionTransition(NFA2.GetStartState());
			m_AllStates[i].SetIsEndState(false);
			break;
		}
	}

	CNFA *nfa = new CNFA(startState, endState);
	return *nfa;
}


CNFA CAutomata::ClosureNFA(CNFA &NFA)
{
	CState startState(false);
	CState endState(true);

	startState.SetStateNum(m_Count++);
	endState.SetStateNum(m_Count++);

	startState.AddEpsilionTransition(NFA.GetStartState());
	startState.AddEpsilionTransition(endState);

	NFA.GetEndState().AddEpsilionTransition(NFA.GetStartState());
	NFA.GetEndState().AddEpsilionTransition(endState);
	NFA.GetEndState().SetIsEndState(false);

	m_AllStates.push_back(startState);
	m_AllStates.push_back(endState);
	int temp = NFA.GetEndState().GetStateNum();
	for (unsigned i = 0; i < m_AllStates.size(); i++) {
		if (m_AllStates[i].GetStateNum() == temp) {
			m_AllStates[i].AddEpsilionTransition(endState);
			m_AllStates[i].AddEpsilionTransition(NFA.GetStartState());
			m_AllStates[i].SetIsEndState(false);
			break;
		}
	}

	CNFA *nfa = new CNFA(startState, endState);
	return *nfa;
}


CNFA CAutomata::OneOrMoreNFA(CNFA &NFA)
{
	CState startState(false);
	CState endState(true);

	startState.SetStateNum(m_Count++);
	endState.SetStateNum(m_Count++);

	startState.AddEpsilionTransition(NFA.GetStartState());

	NFA.GetEndState().AddEpsilionTransition(NFA.GetStartState());
	NFA.GetEndState().AddEpsilionTransition(endState);
	NFA.GetEndState().SetIsEndState(false);

	m_AllStates.push_back(startState);
	m_AllStates.push_back(endState);
	int temp = NFA.GetEndState().GetStateNum();
	for (unsigned i = 0; i < m_AllStates.size(); i++) {
		if (m_AllStates[i].GetStateNum() == temp) {
			m_AllStates[i].AddEpsilionTransition(endState);
			m_AllStates[i].AddEpsilionTransition(NFA.GetStartState());
			m_AllStates[i].SetIsEndState(false);
			break;
		}
	}

	CNFA *nfa = new CNFA(startState, endState);
	return *nfa;
}

       NFA构建规则代码实现后,我们便可以从左到右读上一个步骤取处理好的后缀表达式,利用栈辅助构建:

        1. 遇到字母,用基本规则构建基本NFA N(S),并将其入栈;

        2. 遇到“|”,栈弹出两个元素N(S)、N(T),用归纳规则构建N(R),R=S|T,并将其入栈;

       3. 遇到连接符“·”,栈弹出两个元素N(S)、N(T),用归纳规则构建N(R),R=S·T,并将其入栈;

       4. 遇到闭包或正闭包,栈弹出一个元素N(S),用归纳规则构建N(R),R=S*或者R=S+,并将其入栈。

        实现代码如下:

CNFA CAutomata::BuildExpNFA(string exp)
{
	for (unsigned i = 0; i < exp.size(); i++){
		char token = exp[i];
		if (token == UNION_OPERATOR) {
			CNFA NFA2 = m_NFAStack.top();
			m_NFAStack.pop();
			CNFA NFA1 = m_NFAStack.top();
			m_NFAStack.pop();
			m_NFAStack.push(UnionNFA(NFA1, NFA2));
		}
		else if (token == CONCATENATION_OPERATOR) {//注意,栈顶的NFA为连接符后面的NFA
			CNFA NFA2 = m_NFAStack.top();
			m_NFAStack.pop();
			CNFA NFA1 = m_NFAStack.top();
			m_NFAStack.pop();
			m_NFAStack.push(ConcatNFA(NFA1, NFA2));
		}
		else if (token == CLOSURE_OPERATOR) {
			CNFA NFA = m_NFAStack.top();
			m_NFAStack.pop();
			m_NFAStack.push(ClosureNFA(NFA));
		}
		else if (token == ONE_OR_MORE_OPERATOR) {
			CNFA NFA = m_NFAStack.top();
			m_NFAStack.pop();
			m_NFAStack.push(OneOrMoreNFA(NFA));
		}
		else 
			m_NFAStack.push(BasicNFA(token));
	}
	//cout << "m_NFAStack.size() :" << m_NFAStack.size() << endl;
	m_NFA = m_NFAStack.top(); //注意,类对象包含const成员时无法复制
	m_NFAStack.pop();
	return m_NFA;
}

3.2.4 文本预处理

        对于需要检索的txt文本,先根据空格提取单词。其中部分单词可能包含标点符号,如题目中的“(SZU,”、“Chinese:”、“"Shenzhen”、“Shenzhen,”、“China.”等,它们会影响自动机的检索,所以需要对标点符号进行删除。实现代码如下:

int CAutomata::MatchTxtWithExp(string address)
{
	ifstream inFile;
	inFile.open(address);

	vector<string> words;//存放取出来的单词
	string str;
	//提取单词,删除标点符号
	int temp = 1;
	while (inFile >> str) {
		string::iterator it;
		for (it = str.begin(); it < str.end(); it++) {
			if (*it == '.' || *it == '?' || *it == '\"' || *it == ':' ||
				*it == '{' || *it == '}' || *it == '(' || *it == ')' ||
				*it == '[' || *it == ']' || *it == ',') {
				str.erase(it);
				if (it != str.begin())
					it--;
			}
		}
		words.push_back(str);
		cout << str << " ";
		//if (temp++ % 5 == 0)
			cout << endl;
	}
	int count = 1;
	for (unsigned i = 0; i < words.size(); i++){
		if (MatchStringWithExp(words[i])) {
			cout << "【" << count++ << "】匹配成功:" << words[i] << endl;
		}
	}
	return 0;
}

3.2.5 判断NFA是否接收字符串

        正常流程我们需要先将NFA转化为DFA再去判断其能否接收字符串,在实际代码中我们并不需要构建显式的DFA,可以利用子集构造法的思想去遍历当前的NFA,整个过程类似于图的检索。

比如读取的第一个字符为“S”,我们则先求开始状态的空闭包作为CurrentState{…},再遍历闭包中的所有状态的转移方程是否接收“S”(大小写不敏感),如果接收,则将该状态及其空闭包添加进NextState{…},遍历完成后则将CurrentState{…}更新为NextState{…},并读取字符串的下一个字符,重复上诉操作。字符串读取完成后判断CurrentState是否包含接收状态,包含则表示匹配成功,反之表示匹配失败。

        部分匹配代码如下:

bool CAutomata::MatchStringWithExp(string str)
{
	CState startState = m_NFA.GetStartState();
	vector<CState> currentState = GetClosure(startState);
	for (unsigned k = 0; k < str.size(); k++) {
		char token = str[k];
		vector<CState> nextState;
		for (unsigned i = 0; i < currentState.size(); i++) {
			//cout << "Token为:" << token << " 当前状态为:" << currentState[i].GetStateNum() << endl;
			for (unsigned j = 0; j < currentState[i].m_Transition.size(); j++) {
				//cout << "判断状态" << currentState[i].GetStateNum() << "是否接收Token" << endl;
			    //小大写匹配不敏感
				if (token >= 65 && token <= 90) { //大写字母
					if (currentState[i].m_Transition[j].first == token ||
						currentState[i].m_Transition[j].first == token + 32) {
						//cout << "接收Token,求状态" << currentState[i].m_Transition[j].second.GetStateNum() << "的闭包" << endl;
						vector<CState> temp = GetClosure(currentState[i].m_Transition[j].second);
						nextState.insert(nextState.end(), temp.begin(), temp.end());
					}
				}
				else if (token >= 97 && token <= 122) { //小写字母
					if (currentState[i].m_Transition[j].first == token ||
						currentState[i].m_Transition[j].first == token - 32) {
						vector<CState> temp = GetClosure(currentState[i].m_Transition[j].second);
						nextState.insert(nextState.end(), temp.begin(), temp.end());
					}
				}
				else { //其他字符
					if (currentState[i].m_Transition[j].first == token) {
						vector<CState> temp = GetClosure(currentState[i].m_Transition[j].second);
						nextState.insert(nextState.end(), temp.begin(), temp.end());
					}
				}
			}
		}
		vector<CState>::iterator it;
		for (it = currentState.begin(); it != currentState.end(); ) {
			it = currentState.erase(it);
		}
		currentState.insert(currentState.end(), nextState.begin(), nextState.end());
		/*cout << "Token为:" << token << " 当前可到达状态:{";
		for (unsigned i = 0; i < currentState.size(); i++) {
			cout << currentState[i].GetStateNum() << ",";
		}
		cout << "}" << endl;*/
	}
	bool flag = false;
	for (unsigned i = 0; i < currentState.size(); i++) {
		if (currentState[i].GetIsEndState() == true) {
			flag = true;
		//	cout << "匹配成功:"<< str << endl;
			break;
		}
	}
	if (flag == false) {
	//	cout << "匹配失败:" << str << endl;
	}
	return flag;
}

        求一个状态S1的空闭包基本思想为先将S1入栈,遍历其转移函数,如果当前状态与另一状态S2存在空转移,则将S2入栈,并保存进Vector中作为空闭包的一个元素,当S1完成遍历后则删除S1­,再遍历剩余栈元素,直至栈为空,期间我们需要对已访问的状态做一个表示防止陷入死循环,整体思想类似于树的深度优先搜索。实现代码如下:

/*通过栈操作求当前state的epsilion闭包,并存储在vector中*/
vector<CState> CAutomata::GetClosure(CState startState)
{
	int index = FindStateByNum(startState.GetStateNum());
	//cout << "求状态" << startState.GetStateNum() << "的所有闭包:" << endl;
	//cout << "该状态有" << m_AllStates[index].m_EpsilionTransition.size() << "个直接空转移" << endl;
	//cout << "{" << startState.GetStateNum() << ",";
	vector<CState> visitedState;
	stack<CState> closureState;
	visitedState.push_back(m_AllStates[index]);
	closureState.push(m_AllStates[index]);
	while (!closureState.empty()) {
		//cout << closureState.size() << endl;
		CState state = closureState.top();
		CState nextState;
		//cout << endl << "状态" << state.GetStateNum() << "的直接闭包:";
		closureState.pop();
		for (unsigned i = 0; i < state.m_EpsilionTransition.size(); i++) {
			bool flag = false;
			int nextIndex;
			for (int j = 0; j < visitedState.size(); j++) {
				if (visitedState[j].GetStateNum() == state.m_EpsilionTransition[i].GetStateNum()) {
					flag = true;
					break;
				}
			}
			if (flag == false) {
				nextIndex = FindStateByNum(state.m_EpsilionTransition[i].GetStateNum());
				visitedState.push_back(m_AllStates[nextIndex]);
				//cout << state.m_EpsilionTransition[i].GetStateNum() << ",";
				closureState.push(m_AllStates[nextIndex]);
			}
		}
	}
	//cout << endl << "}" << endl;
	return visitedState;
}

3.2.6 系统类图

        系统类图如下,包含状态类、正则表达式预处理类、NFA类、自动机类、文本匹配类:

 3.3 实验结果

        根据题目要求输入正则表达式s(a|b|c|d|e|f|g|h|i|j|k|l|m|n|o|p|q|r|s|t|u|v|w|x|y|z)*n,已题目中的文本作为输入文本。

        检索系统先给正则表达式添加连接符,再转化为后缀表达式,结果如下:

        根据后缀表达式,系统将其转化为对应的NFA,输出每个状态及其转移函数,结果如下,一共生成了108个状态: 

        提取文本单词,结果如下,暂时不支持中文词提取:

         利用NFA,匹配从文本提取出的单词,结果如下,满足题目要求:

        其他正则表达式测试,检索c开头且至少包含一个i的单词:

c(a|b|c|d|e|f|g|h|i|j|k|l|m|n|o|p|q|r|s|t|u|v|w|x|y|z)*i+(a|b|c|d|e|f|g|h|i|j|k|l|m|n|o|p|q|r|s|t|u|v|w|x|y|z)*

        结果如下:


4. 请设计并编程实现一个简单分类器,要求各类别规则用正则表达式描述,实现输入任意一个字符串,自动给出该串的类别编号。例如定义类别1:0*1*;类别2:1*0*。则给出串0011,程序将输出结果为类别1。

4.1 设计思路

        分类器的原理与单词检索系统一样,先将正则表达式转化为对应的NFA/DFA,再将字符串放入有限自动机进行匹配,只是单词检索系统的输入只有一个正则表达式,分类器包含多个预设的正则表达式。所以我们可以复用题目3中检索系统代码,声明多个自动机类的对象处理不同的正则表达式,当输出字符串时后,将该串分别输入不同的自动机进行匹配,根据自动机接收情况,对其进行分类。

4.2 关键代码

        在题目3的代码基础上新增CRegexClassifier类,分类器类图如下:

        通过Vector保存正则表达式对应的自自动机。自动机生成代码如下:

void CRegexClassifier::SetRegexs()
{
	CInfixToPostfix pretreatment;
	vector<string> resgexs;
	//要添加的正则表达式
	resgexs.push_back("0*1*");
	resgexs.push_back("1*0*");
	resgexs.push_back("a(b|c)*d");
	for (unsigned i = 0; i < resgexs.size(); i++) {
		pretreatment.SetExp(resgexs[i]);
		pretreatment.InsertConcatOperator();
		//cout << "添加连接符的正则表达式:" << pretreatment.GetConcatExp() << endl;
		pretreatment.IntfixToPosfix();
		//cout << "中缀表达式转后缀表达式:" << pretreatment.GetPostfixExp() << endl;
		CAutomata automato;
		automato.BuildExpNFA(pretreatment.GetPostfixExp());
		m_Automatas.push_back(automato);
	}

	cout << "类别:正则表达式" << endl;
	cout << "1: 0*1*" << endl;
	cout << "2: 1*0*" << endl;
	cout << "3: a(b|c)*d" << endl;
}

       对字符串进行匹配并分类,实现代码如下:

void CRegexClassifier::ClassifyString(string str)
{
	bool flag = false;
	for (unsigned i = 0; i < m_Automatas.size(); i++) {
		if (m_Automatas[i].MatchStringWithExp(str) == true) {
			cout << "类别:" << i + 1 << endl;
			flag = true;
			break;
		}
	}
	if (!flag)
		cout << "该字符串不属于以上类别" << endl;
}

4.3 实验结果

        定义三个类别的正则表达式:1:0*1*;2:1*0*;3:a(b|c)*d。输入字符串0011、1000、acd、0110、a11d。分类结果如下,满足题目要求:

附件

        附件包含课程论文所用的Visio图以及程序代码,题目3和题目4在同一个项目RegexSearchAndClassifier中实现。下图为代码说明。

         下载地址:

          课程作业——自动机实现-C/C++文档类资源-CSDN下载

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值