编译原理:NFA转DFA(原理+完整代码+可视化实现)

NFA转换为DFA

【本文内容摘要】

  1. 什么是DFA
  2. 通过子集构造法将NFA转换为DFA
  3. 生成DFA的dot文件并且形成可视化。

如果本文对各位看官有用的话,请记得给一个免费的赞哦(收藏也不错)!

文章目录

  • NFA转换为DFA
    • 一、什么是DFA
    • 二、NFA转换为DFA
      • (A)关于如何构造NFA
      • (B)通过子集构造法构建DFA
    • 三、可视化DFA
    • 四、案例测试
    • 五、完整代码(包括了正规式转NFA的部分)


一、什么是DFA

根据百度上的内容。
DFA,即确定有限状态自动机,是编译原理中的一个重要概念。它是一种用于识别正则语言的自动机,也是编译器中词法分析器的核心算法之一。

DFA的工作原理是:从起始状态开始,根据输入符号逐步转移到下一个状态,直到达到终止状态。如果在这个过程中出现了无法转移的情况,那么这个输入符号串就不是这个DFA所能接受的。

那么它和NFA的区别在哪里?

  1. DFA是确定的,也就是说,对于每个状态和每个输入符号,只有一个转移的目标状态。而NFA是不确定的,也就是说,对于某些状态和输入符号,可能有多个或者没有转移的目标状态。
    【举个例子】在NFA中,A状态输入a可以转移到B和C两个状态,但是DFA中只能转移到一个状态。
  2. NFA可以有空串转移,也就是说,不需要输入符号就可以从一个状态转移到另一个状态。而DFA不允许有空串转移。
    【举个例子】在NFA中,A状态通过空串可以转移到B状态,但是在DFA中,不允许这种空串转移。

二、NFA转换为DFA

NFA转换为DFA的过程,可以用子集构造法来实现。子集构造法的基本思想是,将NFA的状态集合分成若干个子集,每个子集代表一个DFA的状态。子集之间的转移关系,由NFA中的转移关系和空转移决定。具体步骤如下:

  1. 计算NFA的初始状态的ε闭包,即包含初始状态和所有通过空转移可以到达的状态的集合。将这个集合作为DFA的初始状态,并标记为未处理。
  2. 从未处理的状态集合中选取一个状态(一般是按照顺序),对于每个输入符号,计算该状态经过该符号可以到达的NFA状态的集合,再计算这个集合的ε闭包,得到一个新的状态集合。如果这个集合是新出现的,就将其标记为未处理,否则就是已处理的。根据这个过程,建立DFA的转移关系。
  3. 重复第2步,直到没有未处理的状态集合为止。此时,DFA的状态集合和转移关系就构造完成了。
  4. 确定DFA的终止状态,即包含NFA的终止状态的状态集合。

(A)关于如何构造NFA

下面的链接中已经详细解释了如何将正规式转换为NFA,这里就不再赘述了。

编译原理词法分析:正则表达式/正规式转NFA(原理+完整代码+可视化实现)

(B)通过子集构造法构建DFA

(1)本文需要用到的结构体

/*构造NFA和DFA所需要的结构体*/
//NFA的节点
struct node
{string nodeName;
};//NFA的边
struct edge
{node startName;	//起始点node endName;	//目标点char tranSymbol;	//转换符号
};//NFA的组成单元,一个大的NFA单元可以是由很多小单元通过规则拼接起来
struct elem
{int edgeCount;	//边数edge edgeSet[100];	//该NFA拥有的边node startName;	//开始状态node endName; //结束状态
};// 定义 DFA 的状态
struct DFAState {set<string> nfaStates;	//一个包含NFA状态的集合string stateName;
};// 定义 DFA 的转换关系
struct DFATransition {DFAState fromState;DFAState toState;char transitionSymbol;
};
  • node,表示一个NFA的节点,它有一个字符串类型的成员nodeName,表示节点的名称。
  • edge,表示一个NFA的边,它有三个成员,分别是startNameendNametranSymbol,分别表示边的起始节点、目标节点和转换符号,其中节点类型是node,转换符号类型是char
  • elem,表示一个NFA的组成单元,它有四个成员,分别是edgeCountedgeSetstartNameendName,分别表示该单元的边数、边集合、开始状态和结束状态,其中边数类型是int,边集合类型是edge的数组,开始状态和结束状态类型是node
  • DFAState,表示一个DFA的状态,它有两个成员,分别是nfaStatesstateName,分别表示该状态包含的NFA状态的集合和该状态的名称,其中NFA状态的集合类型是string的集合,状态的名称类型是string
  • DFATransition,表示一个DFA的转换关系,它有三个成员,分别是fromStatetoStatetransitionSymbol,分别表示转换的起始状态、目标状态和符号,其中状态类型是DFAState,符号类型是char

(2)本文需要用到一些函数(这里留个印象就行)

// 检查 DFA 状态是否在状态集合中,即dfaStates里有没有找到targetState
bool isDFAStateInVector(const vector<DFAState>& dfaStates, const DFAState& targetState) {// for 循环遍历 dfaStates 中的每一个状态for (const DFAState& state : dfaStates) {// 检查当前遍历到的状态的状态名(stateName)是否与目标状态的状态名相等if (state.stateName == targetState.stateName) {// 如果找到匹配的状态,返回 truereturn true; }}// 如果遍历完整个状态集合仍未找到匹配的状态,返回 falsereturn false; 
}
// 检查转换边是否在边集合中,比如 a->b 是否已经在集合中
bool isTransitionInVector(DFAState dfaState, DFAState dfaNextState, char symbol, vector<DFATransition> dfaTransitions)
{// for 循环遍历 dfaTransitions 中的每一条转换边for (const DFATransition& transition : dfaTransitions) {// 检查当前遍历到的转换边是否与给定的 DFA 状态和符号匹配if (transition.fromState.stateName == dfaState.stateName && transition.toState.stateName == dfaNextState.stateName && symbol == transition.transitionSymbol) {// 如果找到匹配的转换边,返回 truereturn true;   }}// 如果遍历完整个转换边集合仍未找到匹配的转换边,返回 falsereturn false;
}

(3)计算ε闭包(ε-closure)
ε闭包是包含这个集合和所有通过空转移可以到达的状态的集合。简单来说,就是这个状态,通过空串的转移,最终能到达哪些状态(包括它自己)。
在这里插入图片描述
【举个例子】上图中,0状态通过ε可以转移到1247状态,因此ε-closure(0) = {0,1,2,4,7} 。

下面我们来看看代码吧:

// 计算 NFA 状态的ε闭包
DFAState eClosure(const set<string>& nfaStates,elem nfa) {DFAState eClosureState;eClosureState.nfaStates = nfaStates;stack<string> stateStack;// 初始化栈,将初始状态加入栈,最开始nfaState里只有NFA_Elem.startNamefor (const string& nfaState_name : nfaStates) {stateStack.push(nfaState_name);}while (!stateStack.empty()) {string currentState = stateStack.top();stateStack.pop();// 遍历 NFA 的边for (int i = 0; i < nfa.edgeCount; i++) {edge currentEdge = nfa.edgeSet[i];// 如果边的起始状态是当前状态,并且边的转换符号是#,那么将目标状态加入ε闭包if (currentEdge.startName.nodeName == currentState && currentEdge.tranSymbol == '#') {// 检查目标状态是否已经在ε闭包中,避免重复添加if (eClosureState.nfaStates.find(currentEdge.endName.nodeName) == eClosureState.nfaStates.end()) {eClosureState.nfaStates.insert(currentEdge.endName.nodeName);// 将目标状态加入栈以便进一步处理stateStack.push(currentEdge.endName.nodeName);}}}}// 为ε闭包分配一个唯一的名称for (const string& nfaState_name : eClosureState.nfaStates) {eClosureState.stateName += nfaState_name;}return eClosureState;
}

它的参数是一个字符串的集合,表示NFA状态的名称,和一个elem结构体,表示一个NFA。它的返回值是一个DFAState结构体,表示这个ε闭包对应的DFA状态。

这段代码的主要逻辑如下:

step1:初始化一个DFAState结构体,将其nfaStates字段设为参数中的字符串集合,表示这个ε闭包包含的NFA状态。将其stateName字段设为空字符串,表示这个ε闭包的名称。
step2:初始化一个栈,将字符串集合中的所有元素压入栈中,表示需要处理的NFA状态。
step3:当栈不为空时,重复以下步骤:

  • 弹出栈顶的元素,记为当前状态,表示正在处理的NFA状态。
  • 遍历NFA的所有边,如果边的起始状态是当前状态,且边的转换符号是#,表示这是一条空转移,那么执行以下步骤:
    • 检查边的目标状态是否已经在DFAStatenfaStates字段中,如果不在,就表示这是一个新的NFA状态,需要加入ε闭包中。将其插入DFAStatenfaStates字段中,并将其压入栈中,以便进一步处理。

step4:遍历DFAStatenfaStates字段,将所有元素拼接起来,作为DFAStatestateName字段的值,表示这个ε闭包的名称。返回DFAState结构体。

(3)计算DFA状态的转移
move(T,a)函数代表T状态通过a转移到的所有状态的集合。
在这里插入图片描述
【举个例子】由T = ε-closure(0) = {0,1,2,4,7},求move(T,a):
状态2可以通过a到状态3,状态7可以通过a到状态8,那么,mov(T,a) = {3, 8}。
下面来看代码:

//move函数
DFAState move(const DFAState& dfaState, char transitionSymbol,elem nfa) {DFAState nextState;// 遍历 DFAState 中的每个 NFA 状态for (const string& nfaState_name : dfaState.nfaStates) {// 在这里遍历所有 NFA 状态的边for (int i = 0; i < nfa.edgeCount; i++) {edge currentEdge = nfa.edgeSet[i];// 如果边的起始状态是当前状态,且边的转换符号等于输入符号,将目标状态加入 nextStateif (currentEdge.startName.nodeName == nfaState_name && currentEdge.tranSymbol == transitionSymbol&&currentEdge.tranSymbol!='#') {nextState.nfaStates.insert(currentEdge.endName.nodeName);}}}// 为 nextState 分配一个唯一的名称for (const string& nfaState_name : nextState.nfaStates) {nextState.stateName += nfaState_name;}return nextState;
}

解释:

  1. 函数接收当前 DFA 状态(dfaState)、输入符号(transitionSymbol)和一个表示 NFA 的结构体(elem nfa)。
  2. 创建一个空的 DFA 状态 nextState 用于存储移动后的状态。
  3. 遍历当前 DFA 状态中的每个 NFA 子状态。对于每个 NFA 子状态,遍历 NFA 的边集合,查找符合起始状态和转换符号的边。
  4. 如果找到符合条件的边,则将边的目标状态添加到 nextState 的状态集合中。
  5. 为了确保状态名称的唯一性,遍历 nextState 的状态集合,并将每个子状态的名称连接起来作为新状态的名称。
  6. 返回新的 DFA 状态 nextState

(4)将NFA转换为DFA
大致过程如下:首先,通过计算NFA初始状态的ε闭包,初始化DFA状态集合。然后,通过迭代遍历DFA状态集合,对每个状态和输入符号进行移动操作,得到下一个状态,并计算其ε闭包。在这个过程中,新的DFA状态和转换关系逐步添加到对应的向量中,确保构建了一个等价于原NFA的DFA。
在这里插入图片描述

【举个例子】上图的答案:
在这里插入图片描述在这里插入图片描述

void buildDFAFromNFA(const elem& NFA_Elem, vector<DFAState>& dfaStates, vector<DFATransition>& dfaTransitions) {// 初始化 DFA 状态集合和转换关系set<string> nfaInitialStateSet;nfaInitialStateSet.insert(NFA_Elem.startName.nodeName);DFAState dfaInitialState = eClosure(nfaInitialStateSet, NFA_Elem); // 计算 NFA 初始状态的 ε闭包dfaStates.push_back(dfaInitialState);// 开始构建 DFAfor (int i = 0; i < dfaStates.size(); i++) {DFAState dfaState = dfaStates[i];for (int j = 0; j < NFA_Elem.edgeCount; j++) {char symbol = NFA_Elem.edgeSet[j].tranSymbol;DFAState nextState = move(dfaState, symbol, NFA_Elem);DFAState dfaNextState = eClosure(nextState.nfaStates, NFA_Elem);	//计算move操作后的ε闭包if (!nextState.nfaStates.empty()) {// 如果下一个状态不为空,且在 DFA 状态集合中还未添加,则加入 DFA 状态集合if (!isDFAStateInVector(dfaStates, dfaNextState)) {dfaStates.push_back(dfaNextState);}// 对于边也要去重,因为等于a的边可能会遍历到两次// 如果当前边在 DFA 转换关系中还未添加,则加入 DFA 转换关系if (!isTransitionInVector(dfaState, dfaNextState, symbol, dfaTransitions)) {dfaTransitions.push_back({ dfaState, dfaNextState, symbol });}}}}
}

代码解释如下:

  1. 初始化 DFA 状态集合,包含 NFA 初始状态的ε闭包,并将其加入 DFA 状态集合中。
  2. 使用两层循环,外层循环遍历 DFA状态集合,内层循环遍历 NFA 的边集合。
  • 对于每个 DFA 状态和输入符号,通过移动操作得到下一个状态,并计算下一个状态的ε闭包。
  • 如果下一个状态不为空,且在 DFA 状态集合中还未添加,则将其加入 DFA 状态集合。
  • 如果当前边在 DFA转换关系中还未添加,则将其加入 DFA 转换关系。

最终,通过这个过程,构建出完整的 DFA。

三、可视化DFA

原理解释:

函数 generateDotFile_DFA 用于生成描述 DFA(Deterministic Finite Automaton,确定性有限自动机)的 DOT 文件,以便后续使用 Graphviz 等工具可视化显示 DFA 图形。DOT 文件是一种文本格式,描述图的结构、节点和边的关系。

在该函数中,DFA 的状态和转换被映射为 DOT 文件中的节点和边。节点表示状态,边表示状态之间的转移,而边上的标签表示转移所对应的输入符号。

代码:

//生成DFA的dot文件
void generateDotFile_DFA(vector<DFAState>& dfaStates, vector<DFATransition>& dfaTransitions) {// 打开名为 "dfa_graph.dot" 的文件流std::ofstream dotFile("dfa_graph.dot");// 如果文件流成功打开if (dotFile.is_open()) {// 写入 DOT 文件的头部信息dotFile << "digraph DFA {\n";dotFile << "  rankdir=LR;  // 横向布局\n\n";dotFile << " node [shape = circle];   // 初始状态\n\n";dotFile << dfaStates.back().stateName << "[shape = doublecircle];\n";// 添加DFA状态for (const auto& state : dfaStates) {dotFile << "  " << state.stateName;dotFile << " [label=\"State " << state.stateName;if (state.stateName == dfaStates.front().stateName) dotFile << "\\n(startState)";if (state.stateName == dfaStates.back().stateName) { dotFile << "\\n(endState)"; }dotFile << "\"];\n";}dotFile << "\n";// 添加DFA转移for (const auto& transition : dfaTransitions) {dotFile <<"  " <<transition.fromState.stateName << " -> " << transition.toState.stateName << " [label=\"" << transition.transitionSymbol << "\"];\n";}// 写入 DOT 文件的尾部信息dotFile << "}\n";// 关闭文件流并输出成功信息dotFile.close();std::cout << "DFA DOT file generated successfully.\n";}else {// 输出错误信息,表示无法打开 DOT 文件std::cerr << "Unable to open DOT file.\n";}
}

以下是该函数的详细解释:

  1. 文件流初始化: 首先,函数尝试打开名为 “dfa_graph.dot” 的文件以供写入。如果成功打开,则创建一个名为 dotFile 的文件流用于写入DOT文件。

    std::ofstream dotFile("dfa_graph.dot");
    
  2. 文件是否成功打开: 检查文件是否成功打开。如果未成功打开,则输出错误信息并提前结束函数。

    if (dotFile.is_open()) {// 文件打开成功,继续执行
    } else {std::cerr << "Unable to open DOT file.\n";return;
    }
    
  3. 写入DOT文件头部: 写入DOT文件的头部信息,包括指定图的方向为横向布局(LR),以及设置节点的形状为圆形。同时,将终止状态标记为双圈。

    dotFile << "digraph DFA {\n";
    dotFile << "  rankdir=LR;  // 横向布局\n\n";
    dotFile << " node [shape = circle];   // 初始状态\n\n";
    dotFile << dfaStates.back().stateName << "[shape = doublecircle];\n";
    
  4. 添加DFA状态: 遍历DFA状态集合,为每个状态添加一个DOT文件中的节点,包括状态名称和标签。如果是起始状态,则附加 “(startState)” 标签,如果是终止状态,则附加 “(endState)” 标签。

    for (const auto& state : dfaStates) {dotFile << "  " << state.stateName;dotFile << " [label=\"State " << state.stateName;if (state.stateName == dfaStates.front().stateName) dotFile << "\\n(startState)";if (state.stateName == dfaStates.back().stateName) { dotFile << "\\n(endState)"; }dotFile << "\"];\n";
    }
    
  5. 添加DFA转移: 遍历DFA转换集合,为每个转换添加一个DOT文件中的边,标注起始状态、目标状态和转换符号。

    for (const auto& transition : dfaTransitions) {dotFile <<"  " <<transition.fromState.stateName << " -> " << transition.toState.stateName << " [label=\"" << transition.transitionSymbol << "\"];\n";
    }
    
  6. 写入DOT文件尾部: 写入DOT文件的尾部信息,表示图的结束。

    dotFile << "}\n";
    
  7. 文件流关闭: 关闭文件流,确保文件操作完成。

    dotFile.close();
    
  8. 输出信息: 如果所有操作都成功完成,则输出生成DOT文件成功的信息,否则输出无法打开文件的错误信息。

    std::cout << "DFA DOT file generated successfully.\n";
    

这样,函数完成了将DFA的结构转换为DOT文件的过程,以便进一步的图形可视化。

四、案例测试

1、(a|b|c)*
在这里插入图片描述
上图为visual studio中的运行结果。
下面打开文件夹以及命令提示符,将dot文件可视化
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

NFA图片:
在这里插入图片描述
DFA图片:
在这里插入图片描述

2、a(b|c)*de
在这里插入图片描述
NFA:
在这里插入图片描述

DFA:
在这里插入图片描述

五、完整代码(包括了正规式转NFA的部分)

//head.h
#ifndef HEAD_H
#define HEAD_H#include <iostream>
#include <stdio.h>
#include <cctype>
#include <stack>
#include <string>
#include <map>
#include <set>
#include <vector>
#include<iterator>
#include <fstream>using namespace std;/*构造NFA和DFA所需要的结构体*/
//NFA的节点
struct node
{string nodeName;
};//NFA的边
struct edge
{node startName;	//起始点node endName;	//目标点char tranSymbol;	//转换符号
};//NFA的组成单元,一个大的NFA单元可以是由很多小单元通过规则拼接起来
struct elem
{int edgeCount;	//边数edge edgeSet[100];	//该NFA拥有的边node startName;	//开始状态node endName; //结束状态
};// 定义 DFA 的状态
struct DFAState {set<string> nfaStates;	//一个包含NFA状态的集合string stateName;
};// 定义 DFA 的转换关系
struct DFATransition {DFAState fromState;DFAState toState;char transitionSymbol;
};/*下面是转换为DFA的主要函数*/// 计算 NFA 状态的ε闭包
DFAState eClosure(const set<string>& nfaStates, elem nfa);// 计算 DFA 的状态转移
DFAState move(const DFAState& dfaState, char transitionSymbol,elem nfa);// 检查 DFA 状态是否在状态集合中
bool isDFAStateInVector(const vector<DFAState>& dfaStates, const DFAState& targetState);//检查转换边是否在边集合中,比如a->b是否已经在集合中
bool isTransitionInVector(DFAState, DFAState, char,vector<DFATransition>);//NFA转换为DFA
void buildDFAFromNFA(const elem& NFA_Elem, vector<DFAState>& dfaStates, vector<DFATransition>& dfaTransitions);// 显示 DFA 状态和转移关系
void displayDFA(const vector<DFAState>& dfaStates, const vector<DFATransition>& dfaTransitions);//生成dot文件
void generateDotFile_DFA(vector<DFAState>& dfaStates, vector<DFATransition>& dfaTransitions);/*下面是构造NFA的主要函数*/
//创建新节点
node new_node();//处理 a
elem act_Elem(char);//处理a|b
elem act_Unit(elem, elem);//组成单元拷贝函数
void elem_copy(elem&, elem);//处理ab
elem act_join(elem, elem);//处理 a*
elem act_star(elem);void input(string&);string add_join_symbol(string);	//两个单元拼接在一起相当于中间有一个+,如ab相当于a+bclass infixToPostfix {
public:infixToPostfix(const string& infix_expression);int is_letter(char check);int ispFunc(char c);int icpFunc(char c);void infToPost();string getResult();private:string infix;string postfix;map<char, int> isp;map<char, int> icp;
};elem express_to_NFA(string);void Display(elem);int is_letter(char check);void generateDotFile_NFA(const elem& nfa);
#endif
//func.cpp
#include "head.h"int nodeNum = 0;/*下面是转换为DFA的主要函数*/// 计算 NFA 状态的ε闭包
DFAState eClosure(const set<string>& nfaStates,elem nfa) {DFAState eClosureState;eClosureState.nfaStates = nfaStates;stack<string> stateStack;// 初始化栈,将初始状态加入栈,最开始nfaState里只有NFA_Elem.startNamefor (const string& nfaState_name : nfaStates) {stateStack.push(nfaState_name);}while (!stateStack.empty()) {string currentState = stateStack.top();stateStack.pop();// 遍历 NFA 的边for (int i = 0; i < nfa.edgeCount; i++) {edge currentEdge = nfa.edgeSet[i];// 如果边的起始状态是当前状态,并且边的转换符号是#,那么将目标状态加入ε闭包if (currentEdge.startName.nodeName == currentState && currentEdge.tranSymbol == '#') {// 检查目标状态是否已经在ε闭包中,避免重复添加if (eClosureState.nfaStates.find(currentEdge.endName.nodeName) == eClosureState.nfaStates.end()) {eClosureState.nfaStates.insert(currentEdge.endName.nodeName);// 将目标状态加入栈以便进一步处理stateStack.push(currentEdge.endName.nodeName);}}}}// 为ε闭包分配一个唯一的名称for (const string& nfaState_name : eClosureState.nfaStates) {eClosureState.stateName += nfaState_name;}return eClosureState;
}//move函数
DFAState move(const DFAState& dfaState, char transitionSymbol,elem nfa) {DFAState nextState;// 遍历 DFAState 中的每个 NFA 状态for (const string& nfaState_name : dfaState.nfaStates) {// 在这里遍历所有 NFA 状态的边for (int i = 0; i < nfa.edgeCount; i++) {edge currentEdge = nfa.edgeSet[i];// 如果边的起始状态是当前状态,且边的转换符号等于输入符号,将目标状态加入 nextStateif (currentEdge.startName.nodeName == nfaState_name && currentEdge.tranSymbol == transitionSymbol&&currentEdge.tranSymbol!='#') {nextState.nfaStates.insert(currentEdge.endName.nodeName);}}}// 为 nextState 分配一个唯一的名称for (const string& nfaState_name : nextState.nfaStates) {nextState.stateName += nfaState_name;}return nextState;
}// 检查 DFA 状态是否在状态集合中,即dfaStates里有没有找到targetState
bool isDFAStateInVector(const vector<DFAState>& dfaStates, const DFAState& targetState) {for (const DFAState& state : dfaStates) {if (state.stateName == targetState.stateName) {return true; // 找到匹配的状态}}return false; // 没有找到匹配的状态
}//检查转换边是否在边集合中,比如a->b是否已经在集合中
bool isTransitionInVector(DFAState dfaState, DFAState dfaNextState, char symbol,vector<DFATransition> dfaTransitions)
{for (const DFATransition& transition : dfaTransitions) {if (transition.fromState.stateName == dfaState.stateName && dfaNextState.stateName == dfaNextState.stateName&&symbol==transition.transitionSymbol) {return true;	//找到匹配的状态}}return false;
}void buildDFAFromNFA(const elem& NFA_Elem, vector<DFAState>& dfaStates, vector<DFATransition>& dfaTransitions) {// 初始化 DFA 状态集合和转换关系set<string> nfaInitialStateSet;nfaInitialStateSet.insert(NFA_Elem.startName.nodeName);DFAState dfaInitialState = eClosure(nfaInitialStateSet, NFA_Elem); // 计算 NFA 初始状态的 ε闭包dfaStates.push_back(dfaInitialState);// 开始构建 DFAfor (int i = 0; i < dfaStates.size(); i++) {DFAState dfaState = dfaStates[i];for (int j = 0; j < NFA_Elem.edgeCount; j++) {char symbol = NFA_Elem.edgeSet[j].tranSymbol;DFAState nextState = move(dfaState, symbol, NFA_Elem);DFAState dfaNextState = eClosure(nextState.nfaStates, NFA_Elem);if (!nextState.nfaStates.empty()) {// 如果下一个状态不为空,且在 DFA 状态集合中还未添加,则加入 DFA 状态集合if (!isDFAStateInVector(dfaStates, dfaNextState)) {dfaStates.push_back(dfaNextState);}// 对于边也要去重,因为等于a的边可能会遍历到两次// 如果当前边在 DFA 转换关系中还未添加,则加入 DFA 转换关系if (!isTransitionInVector(dfaState, dfaNextState, symbol, dfaTransitions)) {dfaTransitions.push_back({ dfaState, dfaNextState, symbol });}}}}
}// 显示 DFA 状态和转移关系,包括起始和结束状态
void displayDFA(const vector<DFAState>& dfaStates, const vector<DFATransition>& dfaTransitions) {cout << "DFA States:" << endl;for (const DFAState& state : dfaStates) {cout << "State " << state.stateName << " (NFA States: ";for (const string& nfaState_name : state.nfaStates) {cout << nfaState_name << " ";}cout << ")";if (state.stateName == dfaStates.front().stateName) {cout << " (Initial State)";}if (state.stateName == dfaStates.back().stateName) {cout << " (Final State)";}cout << endl;}cout << "DFA Transitions:" << endl;for (const DFATransition& transition : dfaTransitions) {cout << "State " << transition.fromState.stateName << " --(" << transition.transitionSymbol << ")--> State " << transition.toState.stateName << endl;}
}//生成DFA的dot文件
void generateDotFile_DFA(vector<DFAState>& dfaStates, vector<DFATransition>& dfaTransitions) {std::ofstream dotFile("dfa_graph.dot");if (dotFile.is_open()) {dotFile << "digraph DFA {\n";dotFile << "  rankdir=LR;  // 横向布局\n\n";dotFile << " node [shape = circle];   // 初始状态\n\n";dotFile << dfaStates.back().stateName << "[shape = doublecircle];\n";// 添加DFA状态for (const auto& state : dfaStates) {dotFile << "  " << state.stateName;dotFile << " [label=\"State " << state.stateName;if (state.stateName == dfaStates.front().stateName) dotFile << "\\n(startState)";if (state.stateName == dfaStates.back().stateName) { dotFile << "\\n(endState)"; }dotFile << "\"];\n";}dotFile << "\n";// 添加DFA转移for (const auto& transition : dfaTransitions) {dotFile <<"  " <<transition.fromState.stateName << " -> " << transition.toState.stateName << " [label=\"" << transition.transitionSymbol << "\"];\n";}dotFile << "}\n";dotFile.close();std::cout << "DFA DOT file generated successfully.\n";}else {std::cerr << "Unable to open DOT file.\n";}
}/*下面是构造NFA的主要函数*///创建新节点
node new_node()
{node newNode;newNode.nodeName = nodeNum + 65;//将名字用大写字母表示nodeNum++;return newNode;
}//接收输入正规表达式
void input(string& RE)
{cout << "请输入正则表达式:  (操作符:() * |;字符集:a~z A~Z)" << endl;cin >> RE;
}//组成单元拷贝函数
void elem_copy(elem& dest, elem source)
{for (int i = 0; i < source.edgeCount; i++) {dest.edgeSet[dest.edgeCount + i] = source.edgeSet[i];}dest.edgeCount += source.edgeCount;
}//处理 a
elem act_Elem(char c)
{//新节点node startNode = new_node();node endNode = new_node();//新边edge newEdge;newEdge.startName = startNode;newEdge.endName = endNode;newEdge.tranSymbol = c;//新NFA组成元素(小的NFA元素/单元)elem newElem;newElem.edgeCount = 0;	//初始状态newElem.edgeSet[newElem.edgeCount++] = newEdge;newElem.startName = newElem.edgeSet[0].startName;newElem.endName = newElem.edgeSet[0].endName;return newElem;
}//处理a|b
elem act_Unit(elem fir, elem sec)
{elem newElem;newElem.edgeCount = 0;edge edge1, edge2, edge3, edge4;//获得新的状态节点node startNode = new_node();node endNode = new_node();//构建e1(连接起点和AB的起始点A)edge1.startName = startNode;edge1.endName = fir.startName;edge1.tranSymbol = '#';//构建e2(连接起点和CD的起始点C)edge2.startName = startNode;edge2.endName = sec.startName;edge2.tranSymbol = '#';//构建e3(连接AB的终点和终点)edge3.startName = fir.endName;edge3.endName = endNode;edge3.tranSymbol = '#';//构建e4(连接CD的终点和终点)edge4.startName = sec.endName;edge4.endName = endNode;edge4.tranSymbol = '#';//将fir和sec合并elem_copy(newElem, fir);elem_copy(newElem, sec);//新构建的4条边newElem.edgeSet[newElem.edgeCount++] = edge1;newElem.edgeSet[newElem.edgeCount++] = edge2;newElem.edgeSet[newElem.edgeCount++] = edge3;newElem.edgeSet[newElem.edgeCount++] = edge4;newElem.startName = startNode;newElem.endName = endNode;return newElem;
}//处理 N(s)N(t)
elem act_join(elem fir, elem sec)
{//将fir的结束状态和sec的开始状态合并,将sec的边复制给fir,将fir返回//将sec中所有以StartState开头的边全部修改for (int i = 0; i < sec.edgeCount; i++) {if (sec.edgeSet[i].startName.nodeName.compare(sec.startName.nodeName) == 0){sec.edgeSet[i].startName = fir.endName; //该边e1的开始状态就是N(t)的起始状态}else if (sec.edgeSet[i].endName.nodeName.compare(sec.startName.nodeName) == 0) {sec.edgeSet[i].endName = fir.endName; //该边e2的结束状态就是N(t)的起始状态}}sec.startName = fir.endName;elem_copy(fir, sec);//将fir的结束状态更新为sec的结束状态fir.endName = sec.endName;return fir;
}//处理a*
elem act_star(elem Elem)
{elem newElem;newElem.edgeCount = 0;edge edge1, edge2, edge3, edge4;//获得新状态节点node startNode = new_node();node endNode = new_node();//e1edge1.startName = startNode;edge1.endName = endNode;edge1.tranSymbol = '#';	//闭包取空串//e2edge2.startName = Elem.endName;edge2.endName = Elem.startName;edge2.tranSymbol = '#';//e3edge3.startName = startNode;edge3.endName = Elem.startName;edge3.tranSymbol = '#';//e4edge4.startName = Elem.endName;edge4.endName = endNode;edge4.tranSymbol = '#';//构建单元elem_copy(newElem, Elem);//将新构建的四条边加入EdgeSetnewElem.edgeSet[newElem.edgeCount++] = edge1;newElem.edgeSet[newElem.edgeCount++] = edge2;newElem.edgeSet[newElem.edgeCount++] = edge3;newElem.edgeSet[newElem.edgeCount++] = edge4;//构建NewElem的启示状态和结束状态newElem.startName = startNode;newElem.endName = endNode;return newElem;
}int is_letter(char check) {if (check >= 'a' && check <= 'z' || check >= 'A' && check <= 'Z')return true;return false;
}
//
string add_join_symbol(string add_string)
{int length = add_string.size();int return_string_length = 0;char* return_string = new char[2 * length + 2];//最多是两倍char first, second;for (int i = 0; i < length - 1; i++){first = add_string.at(i);second = add_string.at(i + 1);return_string[return_string_length++] = first;//要加的可能性如ab 、 *b 、 a( 、 )b 等情况//若第二个是字母、第一个不是'('、'|'都要添加if (first != '(' && first != '|' && is_letter(second)){return_string[return_string_length++] = '+';}//若第二个是'(',第一个不是'|'、'(',也要加else if (second == '(' && first != '|' && first != '('){return_string[return_string_length++] = '+';}}//将最后一个字符写入secondreturn_string[return_string_length++] = second;return_string[return_string_length] = '\0';string STRING(return_string);cout << "加'+'后的表达式:" << STRING << endl;return STRING;
}//类里的各类元素定义
infixToPostfix::infixToPostfix(const string& infix_expression) : infix(infix_expression), postfix("") {isp = { {'+', 3}, {'|', 5}, {'*', 7},  {'(', 1}, {')', 8}, {'#', 0} };icp = { {'+', 2}, {'|', 4}, {'*', 6}, {'(', 8}, {')', 1}, {'#', 0} };
}int infixToPostfix::is_letter(char check) {if (check >= 'a' && check <= 'z' || check >= 'A' && check <= 'Z')return true;return false;
}int infixToPostfix::ispFunc(char c) {int priority = isp.count(c) ? isp[c] : -1;if (priority == -1) {cerr << "error: 出现未知符号!" << endl;exit(1);  // 异常退出}return priority;
}int infixToPostfix::icpFunc(char c) {int priority = icp.count(c) ? icp[c] : -1;if (priority == -1) {cerr << "error: 出现未知符号!" << endl;exit(1);  // 异常退出}return priority;
}void infixToPostfix::infToPost() {string infixWithHash = infix + "#";stack<char> stack;int loc = 0;while (!stack.empty() || loc < infixWithHash.size()) {if (is_letter(infixWithHash[loc])) {postfix += infixWithHash[loc];loc++;}else {char c1 = (stack.empty()) ? '#' : stack.top();char c2 = infixWithHash[loc];if (ispFunc(c1) < icpFunc(c2)) {stack.push(c2);loc++;}else if (ispFunc(c1) > icpFunc(c2)) {postfix += c1;stack.pop();}else {if (c1 == '#' && c2 == '#') {break;}stack.pop();loc++;}}}
}string infixToPostfix::getResult() {postfix = ""; // 清空结果infToPost();return postfix;
}/**表达式转NFA处理函数,返回最终的NFA集合
*/
elem express_to_NFA(string expression)
{int length = expression.size();char element;elem Elem, fir, sec;stack<elem> STACK;for (int i = 0; i < length; i++){element = expression.at(i);switch (element){case '|':sec = STACK.top();STACK.pop();fir = STACK.top();STACK.pop();Elem = act_Unit(fir, sec);STACK.push(Elem);break;case '*':fir = STACK.top();STACK.pop();Elem = act_star(fir);STACK.push(Elem);break;case '+':sec = STACK.top();STACK.pop();fir = STACK.top();STACK.pop();Elem = act_join(fir, sec);STACK.push(Elem);break;default:Elem = act_Elem(element);STACK.push(Elem);}}cout << "已将正则表达式转换为NFA!" << endl;Elem = STACK.top();STACK.pop();return Elem;
}//打印NFA
void Display( elem Elem) {cout << "NFA States:" << endl;cout << "Start State: " << Elem.startName.nodeName << endl;cout << "End State: " << Elem.endName.nodeName << endl;cout << "NFA Transitions:" << endl;for (int i = 0; i < Elem.edgeCount; i++) {cout << "Edge " << i + 1 << ": ";cout << Elem.edgeSet[i].startName.nodeName << " --(" << Elem.edgeSet[i].tranSymbol << ")--> ";cout << Elem.edgeSet[i].endName.nodeName << endl;}cout << "End" << endl;
}//生成NFAdot文件
void generateDotFile_NFA(const elem& nfa) {std::ofstream dotFile("nfa_graph.dot");if (dotFile.is_open()) {dotFile << "digraph NFA {\n";dotFile << "  rankdir=LR;  // 横向布局\n\n";dotFile << " node [shape = circle];   // 状态节点\n\n";dotFile << nfa.endName.nodeName << " [shape=doublecircle];\n";// 添加 NFA 状态dotFile << "  " << nfa.startName.nodeName << " [label=\"Start State: " << nfa.startName.nodeName << "\"];\n";dotFile << "  " << nfa.endName.nodeName << " [label=\"End State: " << nfa.endName.nodeName << "\"];\n";// 添加 NFA 转移for (int i = 0; i < nfa.edgeCount; i++) {const edge& currentEdge = nfa.edgeSet[i];dotFile << "  " << currentEdge.startName.nodeName << " -> " << currentEdge.endName.nodeName << " [label=\"" << currentEdge.tranSymbol << "\"];\n";}dotFile << "}\n";dotFile.close();std::cout << "NFA DOT file generated successfully.\n";}else {std::cerr << "Unable to open NFA DOT file.\n";}
}
//main.cpp
#include "head.h" // 包含提供的头文件int main() {string Regular_Expression;elem NFA_Elem;input(Regular_Expression);if (Regular_Expression.length() > 1)    Regular_Expression = add_join_symbol(Regular_Expression);infixToPostfix Solution(Regular_Expression);//中缀转后缀cout << "后缀表达式为:";Regular_Expression = Solution.getResult();cout << Regular_Expression << endl;//表达式转NFANFA_Elem = express_to_NFA(Regular_Expression);//显示Display(NFA_Elem);//生成NFAdot文件generateDotFile_NFA(NFA_Elem);// 初始化 DFA 状态集合和转换关系vector<DFAState> dfaStates; //用于存储所有的DFA状态vector<DFATransition> dfaTransitions; //用于存储DFA状态之间的转移set<string> nfaInitialStateSet;   //存储NFA的初始状态buildDFAFromNFA(NFA_Elem, dfaStates, dfaTransitions);//从NFA构造DFA// 显示 DFAdisplayDFA(dfaStates, dfaTransitions);//生成DFAdot文件generateDotFile_DFA(dfaStates,dfaTransitions);return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/198527.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【GO】protobuf在golang中的测试用例

上篇文章介绍了如何安装protobuf环境&#xff0c;文章链接如下 【Go】protobuf介绍及安装-CSDN博客 本节介绍protobuf在gRPC中具体如何使用&#xff0c;并编写测试用例 一、Protobuf是如何工作的 .proto文件是protobuf一个重要的文件&#xff0c;它定义了需要序列化数据的结…

企业微信配置可信域名

首先去申请一个域名&#xff0c;然后将域名绑定到有公网ip的云服务器上&#xff0c;绑定到具体的网站&#xff1b;然后再企业微信&#xff0c;管理后台&#xff0c;点击具体的应用&#xff0c;进【网页授权及JS-SDK】&#xff1b;点击底部的【申请校验域名】点击下载文件&#…

postgresql pg_hba.conf 配置详解

配置文件之pg_hba.conf介绍 该文件用于控制访问安全性&#xff0c;管理客户端对于PostgreSQL服务器的访问权限&#xff0c;内容包括&#xff1a;允许哪些用户连接到哪个数据库&#xff0c;允许哪些IP或者哪个网段的IP连接到本服务器&#xff0c;以及指定连接时使用的身份验证模…

第73讲:深入理解MySQL数据库InnoDB存储引擎:内存结构、磁盘结构与后台线程全面解析

文章目录 1.InnoDB存储引擎的架构2.InnoDB存储引擎的内存结构2.1.Buffer Pool缓冲池2.2.Change Buffer更改缓冲区2.3.自适应Hash索引2.4.Log Buffer日志缓冲区 3.InnoDB存储引擎的磁盘结构3.1.System Tablespace系统表空间3.2.File-Per-Table Tablespaces每个表都有单独的表空间…

红警For Mac(RAM芯片可玩)

1、文件损坏解决版本&#xff01; 执行以下命令&#xff0c;&#xff08;注意&#xff1a;命令2应用路径根据实际情况修改&#xff09; sudo spctl --master-disable sudo xattr -r -d com.apple.quarantine /Applications/红警2尤里复仇M芯片.app2、新系统14&#xff0c;第一…

孩子都能学会的FPGA:第二十一课——用线性反馈移位寄存器实现伪随机序列

&#xff08;原创声明&#xff1a;该文是作者的原创&#xff0c;面向对象是FPGA入门者&#xff0c;后续会有进阶的高级教程。宗旨是让每个想做FPGA的人轻松入门&#xff0c;作者不光让大家知其然&#xff0c;还要让大家知其所以然&#xff01;每个工程作者都搭建了全自动化的仿…

腾讯云轻量应用服务器怎么使用宝塔面板?

腾讯云轻量应用服务器宝塔面板怎么用&#xff1f;轻量应用服务器如何安装宝塔面板&#xff1f;在镜像中选择宝塔Linux面板腾讯云专享版&#xff0c;在轻量服务器防火墙中开启8888端口号&#xff0c;然后远程连接到轻量服务器执行宝塔面板账号密码查询命令&#xff0c;最后登录和…

采集伪原创洗稿,实现文章创作的方法

各位写手小伙伴们&#xff0c;今天要和大家分享一些关于伪原创的方法和经验&#xff0c;希望这些建议能够在你们的写作之旅中派上用场。 首先我们需要明确一下&#xff0c;伪原创并不是鼓励抄袭&#xff0c;而是一种在保留原文核心思想的同时&#xff0c;通过巧妙的方式改写&a…

Qt应用开发(Quick篇)——布局类与布局模块

一、前言 实际 应用中&#xff0c;布局是常用的功能&#xff0c;布局最直观的就是提供空间使用率&#xff0c;改善空间的流动和模块之间的重叠&#xff0c;让界面更加的美观。 二、布局类Layout 2.1 介绍 将Layout类型的对象附加到布局的子元素上&#xff0c;提供有关该项的特…

在AWS Lambda上部署标准FFmpeg工具——自定义层的方案

大纲 1 确定Lambda运行时环境1.1 Lambda系统、镜像、内核版本1.2 运行时1.2.1 Python1.2.2 Java 2 打包FFmpeg3 创建Lambda的Layer4 测试4.1 创建Lambda函数4.2 附加FFmpeg层4.3 添加测试代码4.4 运行测试 参考文献 FFmpeg被广泛应用于音/视频流处理领域。对于简单的需求&#…

阿里云Arthas使用——在日志没有输出异常情况下,如何进行线上bug定位 stack命令 和 trace命令

前言 Arthas 是一款线上监控诊断产品&#xff0c;通过全局视角实时查看应用 load、内存、gc、线程的状态信息&#xff0c;并能在不修改应用代码的情况下&#xff0c;对业务问题进行诊断&#xff0c;包括查看方法调用的出入参、异常&#xff0c;监测方法执行耗时&#xff0c;类…

FreeRTOS-Plus-CLI移植

FreeRTOS-Plus-CLI移植 Fang XS.1452512966qq.com如果有错误&#xff0c;希望被指出&#xff0c;学习技术的路难免会磕磕绊绊量的积累引起质的变化 介绍 FreeRTOS-Plus-CLI是FreeRTOS的组件之一。FreeRTOS-Plus-CLI提供了一种简单、小巧、可扩展且RAM高效的启用方法方便Free…

分享67个节日PPT,总有一款适合您

分享67个节日PPT&#xff0c;总有一款适合您 67个节日PPT下载链接&#xff1a;https://pan.baidu.com/s/1oU-UUCV_69e8Gp5Y6zrzVA?pwd6666 提取码&#xff1a;6666 Python采集代码下载链接&#xff1a;采集代码.zip - 蓝奏云 学习知识费力气&#xff0c;收集整理更不易…

uniapp-hubildx配置

1.配置浏览器 &#xff08;1&#xff09;运行》运行到浏览器配置》配置web服务器 &#xff08;2&#xff09;选择浏览器安装路径 &#xff08;3&#xff09;浏览器安装路径&#xff1a; &#xff08;3.1&#xff09; 右键点击图标》属性 &#xff08;3.2&#xff09;选择目标&…

FutureTask

1. 作用 异步操作获取执行结果取消任务执行&#xff0c;判断是否取消执行判断任务执行是否完毕 2. demo public static void main(String[] args) throws Exception {Callable<String> callable () -> search();FutureTask<String> futureTasknew FutureTask&…

Java多线程技术二:线程间通信——ThreadLocal的使用

1 概述 变量值的共享可以使用public static 的声明方式&#xff0c;所有的线程都是用同一个public static变量&#xff0c;那如果想实现每一个线程都有自己的变量该如何解决呢&#xff1f;JDK提供的ThreadLocal就派上用场了。 ThreadLocal类主要的作用就是将数据放入当前线程对…

web前端开发HTML/css用户登录界面

代码&#xff1a; <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns"http://www.w3.org/1999/xhtml"> <head> <meta http-equi…

神经网络常用归一化和正则化方法解析(一)

&#x1f380;个人主页&#xff1a; https://zhangxiaoshu.blog.csdn.net &#x1f4e2;欢迎大家&#xff1a;关注&#x1f50d;点赞&#x1f44d;评论&#x1f4dd;收藏⭐️&#xff0c;如有错误敬请指正! &#x1f495;未来很长&#xff0c;值得我们全力奔赴更美好的生活&…

Win环境中安装Jenkins指南

目录 安装Java环境 下载并安装Jenkins Jenkins版本 启动Jenkins 如何删除Jenkins 安装Java环境 访问 Oracle官方网站 下载并安装JDK 安装完成后&#xff0c;设置系统环境变量 JAVA_HOME 到你的 JDK 安装路径&#xff0c;并将 %JAVA_HOME%\bin 添加到系统 PATH 中。 下载…

Apollo新版本Beta技术沙龙参会感受:未来的自动驾驶之旅

Apollo新版本Beta技术沙龙参会感受&#xff1a;未来的自动驾驶之旅 &#x1f697;&#x1f4a1; 文章目录 Apollo新版本Beta技术沙龙参会感受&#xff1a;未来的自动驾驶之旅 &#x1f697;&#x1f4a1;摘要引言正文&#x1f4cd; 参会流程介绍&#x1f31f; 参会收获&#x1…