目前学校正在开编译原理这门课。为了加深对正则表达式转最小化DFA的理解,利用c++编写了这个程序。现在把我的程序及实现的方法分享出来,希望能对大家的学习有所帮助。
完整代码:https://github.com/GgBondXiang/RexToMinDFA
本文主要从以下几个方面进行讲解,建议搭配代码一起看
正则表达式
操作数
本程序的支持的操作数为小写字母‘a’~‘z’。
运算符
正则表达式包含三个运算符
1)或运算符“|”
2)连接运算符“.”,一般省略不写,本程序中用“&”代替
3)闭包运算符“*”,即任意有限次的自重复连接
规定算符的优先顺序为先“*”,再“.”,最后“|”。
算法流程
1.正则表达式的预处理
预处理是把表达式中省略的连接运算符加上,方便计算机运算
需要添加“&”的有六种情况,分别为“a a” 、“a (” 、“* a” 、“* (” 、“) a” 、 “) (”
总结起来为当第一位是操作数、“*”、“)”且第二位为操作数或“(”
以表达式"(a|b)* abb"为例,预处理后的表达式为:“(a|b)* &a&b&b”
2.中缀表达式转后缀表达式
运算符的优先级为 闭包‘*’ > 连接‘&’ > 或‘|’
转换过程中需要用到一个运算符栈,具体过程如下:
- 如果遇到操作数,直接将其输出。
- 如果遇到运算符:
- 遇到‘(’直接压入栈中;
- 遇到‘)’将运算符出栈并输出,直到遇到‘(’,将‘(’出栈但不输出;
- 遇到‘*’、‘&’、‘|’运算符:
- 如果栈为空,直接将运算符压入栈中;
- 如果栈不为空,弹出栈中优先级大于等于当前运算符的运算符并输出,再将当前运算符入栈。
- 当输入串读取完之后,如果栈不为空,则将栈中元素依次出栈并输出。
/*
*本程序中比较优先级的方法是为每一个运算符返回一个权值,通过权值的大小来比较优先级。
*但是实际操作中会遇到bug,这是因为程序无法返回左括号的优先级。
*为了保证弹出优先级大于等于当前运算符的运算符时,左括号不会出栈,将左括号的权值设为0
*/
int priority(char ch)
{
if(ch == '*')
{
return 3;
}
if(ch == '&')
{
return 2;
}
if(ch == '|')
{
return 1;
}
if(ch == '(')
{
return 0;
}
}
下面看这个例子“(a|b)* &a&b&b”
-
1.遇到“(”,直接入栈
- 栈:(
- 输出区: 2.遇到“a”,直接输出
- 栈:(
- 输出区:a 3.遇到“|”,栈中没有优先级大于等于它的运算符,直接压入栈中
- 栈:( |
- 输出区:a 4.遇到“b”,直接输出
- 栈:( |
- 输出区:a b 5.遇到“)”,“|”出栈并输出,左括号出栈
- 栈:
- 输出区:a b | 6.遇到“*”,栈为空,直接入栈
- 栈:*
- 输出区:a b | 7.遇到“&”,“*”出栈并输出,再将“&”入栈
- 栈:&
- 输出区:a b | * 8.遇到“a”,直接输出
- 栈:&
- 输出区:a b | * a 9.遇到“&”,“&”出栈并输出,再将“&”入栈
- 栈:&
- 输出区:a b | * a & 10.遇到“b”,直接输出
- 栈:&
- 输出区:a b | * a & b 11.遇到“&”,“&”出栈并输出,再将“&”入栈
- 栈:&
- 输出区:a b | * a & b & 12.遇到“b”,直接输出
- 栈:&
- 输出区:a b | * a & b & b 13.字符串读完,栈不为空,将栈中元素出栈并输出
- 栈:
- 输出区:a b | * a & b & b &
所以转换完的后缀表达式为“ab|* a&b&b&”
3.后缀表达式创建NFA
首先介绍一下NFA的存储结构,下图为一个NfaState
struct NfaState /*定义NFA状态*/
{
int index; /*NFA状态的状态号*/
char input; /*NFA状态弧上的值,默认为“#”*/
int chTrans; /*NFA状态弧转移到的状态号,默认为-1*/
IntSet epTrans; /*当前状态通过ε转移到的状态号集合*/