湖南大学编译原理实验二:NFA确定化和DFA最小化

代码链接:编译原理实验二

实验二 NFA确定化和DFA最小化(4学时)

(一)NFA转DFA(2小时)

一、实验目的

学习和掌握将NFA转为DFA的子集构造法。

二、实验任务

(1)存储NFA与DFA;

(2)编程实现子集构造法将NFA转换成DFA。

三、实验内容
  1. 确定NFA与DFA的存储格式。

    要求为3个以上测试NFA准备好相应有限自动机的存储文件。(可利用实验一(二)的基础)

  2. 用C或C++语言编写将NFA转换成DFA的子集构造法的程序。

  3. 测试验证程序的正确性。

    测试不易。可求出NFA与DFA的语言集合的某个子集(如长度小于某个N),再证实两个语言集合完全相同!

  4. 测试用例参考:将下列语言用RE表示,再转换成NFA使用:

    (a) 以a开头和结尾的小字字母串;a (a|b|…|z)*a | a

    (b) 不包含三个连续的b的,由字母a与b组成的字符串;(e | b | bb) (a | ab | abb)*

    © ( a a | b ) * ( a | b b )*

(二)DFA最小化(2小时)

一、实验目的

学会编程实现等价划分法最小化DFA。

二、实验任务

先完善DFA,再最小化DFA。

三、实验内容

(1)准备3个以上测试DFA文件。(提示:其中一定要有没有最小化的DFA)

(2)用C或C++语言编写用等价划分法最小化DFA的程序。

(3)经测试无误。测试不易。可求出两个DFA的语言集合的某个子集(如长度小于某个N),再证实两个语言集合完全相同!

四、通用NFA存储格式的建议(用以上的第三个测试NFA为例)

在这里插入图片描述

2 // 字符集中的字符个数 (以下两行也可合并成一行)
a b // 以空格分隔的字符集。
4 // 状态个数 (以下两行也可合并成一行)
1 2 3 4 // 状态编号。若约定总是用从1开始的连续数字表示,则此行可省略
1 // 开始状态的编号。若约定为1,则此行可省略
1 // 结束状态个数。若约定为1,则此行可省略
3 // 结束状态的编号
3 2 1 // 状态1的所有出去的转换。按字符集中的字符顺序给出,并在最左边加上一列关于e的转换。-1表示出错状态。若下一个状态有多个时,多个状态用逗号分隔。
-1 1 -1
-1 3 4
-1 -1 3

五、集合状态的内部表示的建议

使用示性函数。也即:

  1. 含有单个状态的状态集合用2的幂次表示。即状态1 ~ N分别用数21 ~ 2N 表示。

  2. 数的存储:若用32位整型(__int32)、64位整型(__int64)存储,可分别表示32个或64个状态。更多的状态表示需要使用整型数组。

  3. 含有多个状态的状态集合也用数来表示。若两个状态集合A与B用数表示为m和n,则状态集合AÈB与AÇB的数可用“位运算”表示,分别为m|n和m&n。

  4. 若想知道某个状态集合A(用数m表示)中是否包含原先的第i个状态,也可使用基于“位运算”来判断:若(m | 2i )> 0,则包含,否则不包含。

实验通过测试后,按规定时间上交源代码、测试样例、输出文件(如有输出文件)和电子版实验报告。

实验步骤

确定NFA和DFA的存储格式

NFA格式:

2       // 输入的字符集的个数
ab       // 字符集 中间无空格
14       // NFA状态总数n:编号从0开始到n-1,默认起始状态为0
1       // 终结状态个数
13       // 终结状态编号
1,7 -1 -1   // 以下n行输入当前状态的转换
2,4 -1 -1   // 无转换用-1代替
-1 3 -1    // 最左一侧为ε的转换
6 -1 -1    // 其余列按照输入的字符集的顺序填写
-1 -1 5    // 有多个转换状态时用 , 分隔(不加空格)
6 -1 -1
1,7 -1 -1
-1 8 -1
9,11 -1 -1
-1 10 -1
13 -1 -1
-1 -1 12
13 -1 -1
-1 -1 -1

DFA格式与NFA相同

子集构造算法和DFA最小化算法的实现
输入部分
NFA在程序中的存储:
const int MAXN = 105;

char c_set[MAXN]; // 字符集
int c_set_len;    // 字符集长度

int state[MAXN]; // 状态集合
int state_len;   // 状态个数

int done_state_nfa[MAXN]; // 结束状态集合
int done_state_nfa_len; // 结束状态个数
int start_state_nfa;	// 起始状态

unordered_map<char, vector<int>> NFA_STATE[MAXN]; // NFA转移表

集合的表示采用64位整形,每位代表一个状态,为1则该状态在集合中。

结束状态由于不止有一个,所以使用一个数组存储(这里的存储方式不再是每位表示一个状态集合,而是直接保存状态的编号),起始状态保存方式相同。

使用哈希表构造NFA的转移表,由于是非确定状态机,所以每一个状态对应的输出可能有多个转移,所以使用vetcor数组存放转移到的状态。具体表示含义如下:

NFA_STATE[i][c]: 第i个状态在输入为c的情况下的转移
将输入格式转换为NFA的存储形式
    printf("输入字符集中的字符个数: ");
    scanf("%d", &c_set_len);
    c_set_len++;
    c_set[0] = '#';
    printf("输入字符集: (不要加空格)");
    scanf("%s", c_set + 1);
    c_set[c_set_len] = '\0';

读取字符集,首先读取字符的个数来分配空间,给第一个字符留空,表示 ϵ \epsilon ϵ,使用#表示,其余依次读取输入的字符

	printf("输入状态个数: (状态从0递增开始)");
    scanf("%d", &state_len);
    for (int i = 0; i < state_len; i++) {
        state[i] = i;
    }

    printf("起始状态为0\n");
    start_state_nfa = 0;

状态的编号默认从0开始递增,所以只需输入状态的总数,默认起始状态为0

    printf("输入结束状态个数: ");
    scanf("%d", &done_state_nfa_len);
    printf("输入结束状态: ");
    for (int i = 0; i < done_state_nfa_len; i++) {
        int a;
        scanf("%d", &a);
        if (a >= state_len) {
            printf("不合法\n");
            i--;
        } else
            done_state_nfa[i] = a;
    }

输入结束状态,要判断是否合法:即是不是存在的状态,不存在则不可输入,否则放入结束状态集中。

    printf("输入转移表: \n");
    for (int i = 0; i < state_len; i++) {
        for (int j = 0; j < c_set_len; j++) { // 解析输入的状态字符串
            string nxt;
            vector<int> ret;
            cin >> nxt;
            if (nxt.find(",") == string::npos) {
                ret.push_back(stoi(nxt));
            } else {
                int idx = 0;
                while (nxt.find(",") != string::npos) {
                    idx = nxt.find(",");
                    string num = nxt.substr(0, idx);
                    ret.push_back(stoi(num));
                    idx++;
                    nxt = nxt.substr(idx);
                }
                ret.push_back(stoi(nxt));
            }
            NFA_STATE[i][c_set[j]] = ret;
        }
    }

输入转移表的部分。首先以字符串的形式读入,因为要解析字符串,把以’,‘分隔的数字读出。解析字符串时,判断有没有’,‘(line 7-9),没有则直接进行转换,调用stoi函数转换为整型;如果有,则循环寻找’,’,并且切割出子串,转换为整型,直到没有’,’,在剩余字符串中(line 10-18),最后将这一个vector数组放到对应的状态的输入转移中。

读入部分完成。

子集构造算法
DFA在程序中的存储
int done_state_dfa[MAXN]; // 结束状态集合
int done_state_dfa_len;
int start_state_dfa[MAXN]; // 其实只有一个起始状态,这里没必要使用数组。
int start_dfa_len;

unordered_map<char, int> DFA_STATE[MAXN]; // DFA状态
unordered_map<int, int> DFA_NFA;
int mark[MAXN];
int dfa_state_len;

与NFA不同的是,由于DFA是确定的,所以对于某一个字符的转移只有一个下一状态。

DFA_NFA表示NFA的状态和DFA的状态相对应的形式,即一个DFA状态是由哪个NFA状态得到的。

mark数组表示DFA的状态有没有被标记过

算法步骤

从初始状态出发,计算 ϵ − c l o s u r e \epsilon-closure ϵclosure,作为新的状态A

标记状态A,对每个输入求 m o v e ( A , c ) move(A, c) move(A,c),再求其 ϵ − c l o s u r e \epsilon-closure ϵclosure,如果是个新的状态,则加入到DFA状态集合中,同时更新 D t r a n [ A , c ] = B Dtran[A,c] = B Dtran[A,c]=B状态转移表

当所有状态都被标记后,算法结束。

ϵ − c l o s u r e \epsilon-closure ϵclosure计算方法
int epsilon_closure(int state) { // state位表示状态
    int ans = 0;
    vector<int> state_; // 得到的状态集合
    int cnt = 0;
    for (int i = 1; i <= (1 << state_len); i <<= 1, cnt++) {
        if (state & i) {
            state_.push_back(cnt);
            ans |= i;
        }
    }
    for (auto v : state_) { // 对该状态集合中每个状态求 ε闭包
        for (auto vec : NFA_STATE[v][c_set[0]]) {
            if (vec != -1) {
                ans |= (1 << vec);
            }
        }
    }
    return ans;
}

首先从状态集合state中取出包含的状态(line 5-10),之后,对每个状态求 ϵ \epsilon ϵ的转移,根据NFA转移表得到下一状态,加入到状态集合ans中(line 11 - 17),最后返回状态集合ans

由于这只是单步的 ϵ − c l o s u r e \epsilon-closure ϵclosure,而要求的为无限多步,所以

    int closure = epsilon_closure(init_state);
    while (closure != epsilon_closure(closure)) {
        closure = epsilon_closure(closure);
    }

一直计算,直到闭包不再变化,此时得到了所有的 ϵ − c l o s u r e \epsilon-closure ϵclosure

m o v e move move计算方法
int move(int state, char c) { // state位表示状态 c为当前输入字符
    int ans = 0;
    vector<int> state_;
    int cnt = 0;
    for (int i = 1; i <= (1 << state_len); i <<= 1, cnt++) {
        if (state & i)
            state_.push_back(cnt);
    }
    for (auto v : state_) {
        for (auto vec : NFA_STATE[v][c]) {
            if (vec != -1) {
                ans |= (1 << vec);
            }
        }
    }
    return ans;
}

ϵ − c l o s u r e \epsilon-closure ϵclosure相似,只不过对于转移得到的状态,不再是 ϵ \epsilon ϵ,而是对于当前的输入字符。同样的,先从状态集合state中抽出状态放到state_中,之后找到转移得到的状态,放入新状态集合ans中,返回ans

子集构造算法具体实现

初始化部分:

    done_state_dfa_len = 0;
    dfa_state_len = 0;
    start_dfa_len = 0;
    memset(mark, 0, sizeof(mark));
    set<int> already_exist;
    int init_state = 1;

首先将DFA相关的值赋值为0,将mark数组置0,mark数组用于标记是不是还有DFA的状态没被标记,already_exist集合用于判断是不是已经存在当前状态;init_state为1,由于起始状态为0,所以状态集合的最低位为1,十进制表示为1。

    int closure = epsilon_closure(init_state);
    while (closure != epsilon_closure(closure)) {
        closure = epsilon_closure(closure);
    }

初始的计算闭包

    for (int k = 0; k < done_state_nfa_len; k++) {
        if (closure & (1 << done_state_nfa[k])) {
            done_state_dfa[done_state_dfa_len++] = dfa_state_len;
            break;
        }
    }
    if (closure & (1 << start_state_nfa)) {
        start_state_dfa[start_dfa_len++] = dfa_state_len;
    }
    DFA_NFA[dfa_state_len++] = closure;

首先判断这一个 ϵ − c l o s u r e \epsilon-closure ϵclosure是不是结束状态,如果包含了NFA的结束状态,那么对应的DFA的结束状态要进行设置,对于起始状态的处理也是同样的。(line 1-9)

之后,DFA的0状态对应于这一个 ϵ − c l o s u r e \epsilon-closure ϵclosure,放到DFA_NFA中

	int all_marked = 0;
    while (!all_marked) { // 全都被标记过,结束
        int i;
        for (i = 0; i < dfa_state_len; i++) { // 找第一个未被标记的状态
            if (mark[i] == 0) {
                mark[i] = 1;
                break;
            }
        }
        if (i == dfa_state_len) { // 未找到,全被标记
            all_marked = 1;
            continue;
        }

这里进行while循环,如果所有DFA状态都被标记,那么可以结束,all_marked为1,结束循环。否则找到第一个未标记的状态进行标记。

        int now_nfa_state = DFA_NFA[i]; // 得到对应的NFA状态
        already_exist.insert(now_nfa_state);

通过找到的第一个未被标记的状态得到NFA对应的状态集合,并将其插入到已存在中,那么接下来就是对其求 ϵ − c l o s u r e \epsilon-closure ϵclosure和move步了。

        for (int j = 1; j < c_set_len; j++) {              // 遍历所有输入
            int new_state = move(now_nfa_state, c_set[j]); // move步
            int closure = epsilon_closure(new_state);      // closure步
            while (closure != epsilon_closure(closure)) {
                closure = epsilon_closure(closure);
            }

首先求对输入的move步,得到新状态再求 ϵ − c l o s u r e \epsilon-closure ϵclosure

			if (closure == 0) { // 没有对应的转换
                DFA_STATE[i][c_set[j]] = {-1};
            }

如果closure = 0的话,表示没有对应的转换,那么将转移表中该项设置为-1

			else {
                if (already_exist.find(closure) == already_exist.end()) { // 转换的状态不存在
                    already_exist.insert(closure);                        // 加入到set中
                    if (closure & (1 << start_state_nfa)) {
                        start_state_dfa[start_dfa_len++] = dfa_state_len;
                    }
                    for (int k = 0; k < done_state_nfa_len; k++) {
                        if (closure & (1 << done_state_nfa[k])) {
                            done_state_dfa[done_state_dfa_len++] = dfa_state_len;
                            break;
                        }
                    }
                    DFA_NFA[dfa_state_len++] = closure; // 有了一个新的DFA状态
                }

否则的话,如果当前的状态不存在,则发现了一个新状态,插入到set中,并且判断是不是结束状态,是不是起始状态(line 4-12),再将他对应的DFA,NFA状态插入DFA_NFA的对应关系表中。

                // 向当前状态当前输入的转换中添加新状态
                int k;
                for (k = 0; k < dfa_state_len; k++) { // 找要添加的DFA状态
                    if (DFA_NFA[k] == closure) {
                        break;
                    }
                }
                DFA_STATE[i][c_set[j]] = k;

在这之后,就可以更新DFA的转移表,首先找到这一个新状态 k 的编号,放到状态 i 在 c[j] 的转移下

完整代码
int epsilon_closure(int state) { // state位表示状态
    int ans = 0;
    vector<int> state_; // 得到的状态集合
    int cnt = 0;
    for (int i = 1; i <= (1 << state_len); i <<= 1, cnt++) {
        if (state & i) {
            state_.push_back(cnt);
            ans |= i;
        }
    }
    for (auto v : state_) { // 对该状态集合中每个状态求 ε闭包
        for (auto vec : NFA_STATE[v][c_set[0]]) {
            if (vec != -1) {
                ans |= (1 << vec);
            }
        }
    }

    return ans;
}
int move(int state, char c) { // state位表示状态 c为当前输入字符
    int ans = 0;
    vector<int> state_;
    int cnt = 0;
    for (int i = 1; i <= (1 << state_len); i <<= 1, cnt++) {
        if (state & i)
            state_.push_back(cnt);
    }
    for (auto v : state_) {
        for (auto vec : NFA_STATE[v][c]) {
            if (vec != -1) {
                ans |= (1 << vec);
            }
        }
    }
    return ans;
}
void getDFA() {
    done_state_dfa_len = 0;
    dfa_state_len = 0;
    start_dfa_len = 0;
    memset(mark, 0, sizeof(mark));
    set<int> already_exist;
    int init_state = 1;
    int closure = epsilon_closure(init_state);
    while (closure != epsilon_closure(closure)) {
        closure = epsilon_closure(closure);
    }
    for (int k = 0; k < done_state_nfa_len; k++) {
        if (closure & (1 << done_state_nfa[k])) {
            done_state_dfa[done_state_dfa_len++] = dfa_state_len;
            break;
        }
    }
    if (closure & (1 << start_state_nfa)) {
        start_state_dfa[start_dfa_len++] = dfa_state_len;
    }
    DFA_NFA[dfa_state_len++] = closure;
    int all_marked = 0;
    while (!all_marked) { // 全都被标记过,结束
        int i;
        for (i = 0; i < dfa_state_len; i++) { // 找第一个未被标记的状态
            if (mark[i] == 0) {
                mark[i] = 1;
                break;
            }
        }
        if (i == dfa_state_len) { // 未找到,全被标记
            all_marked = 1;
            continue;
        }

        int now_nfa_state = DFA_NFA[i]; // 得到对应的NFA状态
        already_exist.insert(now_nfa_state);
        for (int j = 1; j < c_set_len; j++) {              // 遍历所有输入
            int new_state = move(now_nfa_state, c_set[j]); // move步
            int closure = epsilon_closure(new_state);      // closure步
            while (closure != epsilon_closure(closure)) {
                closure = epsilon_closure(closure);
            }
            if (closure == 0) { // 没有对应的转换
                DFA_STATE[i][c_set[j]] = {-1};
            } else {
                if (already_exist.find(closure) == already_exist.end()) { // 转换的状态不存在
                    already_exist.insert(closure);                        // 加入到set中
                    if (closure & (1 << start_state_nfa)) {
                        start_state_dfa[start_dfa_len++] = dfa_state_len;
                    }
                    for (int k = 0; k < done_state_nfa_len; k++) {
                        if (closure & (1 << done_state_nfa[k])) {
                            done_state_dfa[done_state_dfa_len++] = dfa_state_len;
                            break;
                        }
                    }
                    DFA_NFA[dfa_state_len++] = closure; // 有了一个新的DFA状态
                }
                // 向当前状态当前输入的转换中添加新状态
                int k;
                for (k = 0; k < dfa_state_len; k++) { // 找要添加的DFA状态
                    if (DFA_NFA[k] == closure) {
                        break;
                    }
                }
                DFA_STATE[i][c_set[j]] = k;
            }
        }
    }
}

验证部分放在最后

DFA最小化算法
最小化DFA在程序中的存储
int done_state_mindfa[MAXN];
int done_state_mindfa_len;
int start_state_mindfa[MAXN];
int start_mindfa_len;

unordered_map<char, int> minDFA_STATE[MAXN]; // minDFA状态
unordered_map<int, int> minDFA_DFA;			// minDFA和DFA的对应关系
int fa[MAXN];								// 用于DFA集合的划分
int minDFA_len;

这里多增加了一个fa数组,用于在做最小化时,判断一个集合内的这些DFA状态转移方向是否相同。

其余与之前DFA相同。

算法步骤

在这里插入图片描述

接受状态和非接受状态
int findEnd() {
    int ans = 0;
    for (int i = 0; i < done_state_dfa_len; i++) {
        ans |= (1 << done_state_dfa[i]);
    }
    return ans;
}

由于最初的划分要分为这两个状态集,所以根据DFA的结束状态,很轻松的就可以找到一个结束状态集,根据存储状态的方式和集合的性质,可以使用:

    int end = findEnd();
    int netend = (1 << (dfa_state_len)) - 1 - end;

来得到非接收状态。

初始化部分
    if (netend == 0) {
        minDFA_DFA[0] = end;
        minDFA_len = 1;
    } else {
        minDFA_DFA[0] = netend;
        minDFA_DFA[1] = end;
        minDFA_len = 2;
    }

由于存在一种可能:只有接收状态,所以要进行判断。

fa数组的作用
void set_fa() {
    for (int i = 0; i < minDFA_len; i++) {
        int state = minDFA_DFA[i];
        int cnt = 0;
        for (int j = 1; j <= (1 << dfa_state_len); j <<= 1, cnt++) {
            if (state & j) {
                fa[cnt] = i;
            }
        }
    }
    return;
}

从最小化DFA和DFA对应的关系中,可以得到minDFA状态的编号,将其对应的DFA的状态的fa值均设置为其编号,用于在之后进行判断这一个DFA集是不是可分得。这里只需要注意fa数组的含义:fa[i] 编号为i的DFA状态在哪个minDFA状态集中

DFA最小化算法具体实现
    for (int i = 0; i < minDFA_len; i++) {
        if (divide(minDFA_DFA[i], i)) {
            i--;
        }
    }

当所有集合均不可分时,则结束,否则当前仍是可分得,所以使i - 1,相当于回溯的过程。

判断划分以及是否可分的实现:

int divide(int state, int idx) {
    vector<int> divide_list[minDFA_len + 1];

    vector<int> state_;
    int cnt = 0;
    for (int i = 1; i <= (1 << state_len); i <<= 1, cnt++) {
        if (state & i) {
            state_.push_back(cnt);
        }
    }

从当前的minDFA状态集合中抽取出DFA的状态,放到state_中

    int ret = 0;
    for (int c = 1; c < c_set_len; c++) {
        for (int i = 0; i <= minDFA_len; i++) {
            divide_list[i].clear();
        }
        char input = c_set[c];

对于每个输入input,进行判断其集合是不是可分

        for (auto v : state_) {
            int nxt = DFA_STATE[v][input];
            if (nxt == -1) {
                divide_list[minDFA_len].push_back(v);
                continue;
            }
            int set = fa[nxt];
            divide_list[set].push_back(v);
        }

对于每一个状态,得到其在当前输入下的转移nxt,要注意的是,nxt = -1即没有转移时,也算是一种可分情况,这时将其放入到divide_list的最后一个元素而不是放到fa[nxt]的位置;否则都放入divide_list中fa[nxt]的位置,表示这些DFA状态转移到了fa[nxt]的minDFA状态。

		int cnt = 0;
        for (int i = 0; i <= minDFA_len; i++) {
            if (divide_list[i].size() != 0)
                cnt++;
        }
        if (cnt == 1) {
            continue;
        }

之后判断,如果只有一个divide_list不为空,则表示不可分,继续查看下一个输入。

		else {
            int len = minDFA_len;
            int ret = 0;
            for (int i = 0; i <= len; i++) {
                if (divide_list[i].size() != 0 && ret != 0) {
                    minDFA_DFA[minDFA_len++] = cal_Set(divide_list[i]);
                }
                else if (divide_list[i].size() != 0 && ret == 0) {
                    minDFA_DFA[idx] = cal_Set(divide_list[i]);
                    ret++;
                }
            }
            set_fa();
            return 1;
        }

否则可分,对于找到的第一个divide_list新集合,把他放到当前的集合的位置,因为当前集合已经被拆开了,之后对于每一个新集合,扩张minDFA状态,放入其中。并在最后更新fa数组,返回1,进行回溯。cal_Set是根据状态计算状态集合

如果最后返回了0,那么表示不可分,算法结束。

转移表和接收状态起始状态的获取
void getTrans() {
    for (int i = 0; i < minDFA_len; i++) {
        int set = minDFA_DFA[i];
        int cnt = 0;
        int ret = 0;
        for (int k = 1; k <= (1 << dfa_state_len); k <<= 1, cnt++) {
            if (set & k)
                break;
        }
        for (int c = 0; c < c_set_len; c++) {
            char input = c_set[c];
            int nxt = DFA_STATE[cnt][input];
            if (nxt == -1)
                minDFA_STATE[i][input] = -1;
            else
                minDFA_STATE[i][input] = fa[nxt];
        }
    }
}

对于转移表,抽取出minDFA对应的当前DFA的状态集合,这一部分由于对于每一个输入,所有状态的转移是一样的,所有只需找一个DFA状态即可。之后得到DFA转移表中,当前状态在当前输入下的转移,找到其fa的位置,即对应的minDFA的转移,放入转移表中。

void getDone_StartState() {
    done_state_mindfa_len = 0;
    start_mindfa_len = 0;
    for (int i = 0; i < minDFA_len; i++) {
        int set = minDFA_DFA[i];
        for (int k = 0; k < done_state_dfa_len; k++) {
            if (set & (1 << done_state_dfa[k])) {
                done_state_mindfa[done_state_mindfa_len++] = i;
                break;
            }
        }
        for (int k = 0; k < start_dfa_len; k++) {
            if (set & (1 << start_state_dfa[k])) {
                start_state_mindfa[start_mindfa_len++] = i;
                break;
            }
        }
    }
}

获取接收状态和起始状态与DFA相同,不再细说。

完整代码
int findEnd() {
    int ans = 0;
    for (int i = 0; i < done_state_dfa_len; i++) {
        ans |= (1 << done_state_dfa[i]);
    }
    return ans;
}
int cal_Set(vector<int> ret) {
    int ans = 0;
    for (auto v : ret) {
        ans |= (1 << v);
    }
    return ans;
}
void set_fa() {
    for (int i = 0; i < minDFA_len; i++) {
        int state = minDFA_DFA[i];
        int cnt = 0;
        for (int j = 1; j <= (1 << dfa_state_len); j <<= 1, cnt++) {
            if (state & j) {
                fa[cnt] = i;
            }
        }
    }
    return;
}
int divide(int state, int idx) {
    vector<int> divide_list[minDFA_len + 1];

    vector<int> state_;
    int cnt = 0;
    for (int i = 1; i <= (1 << state_len); i <<= 1, cnt++) {
        if (state & i) {
            state_.push_back(cnt);
        }
    }
    // for (auto v: state_) cout << v << " ";
    // cout << endl;
    int ret = 0;
    for (int c = 1; c < c_set_len; c++) {
        for (int i = 0; i <= minDFA_len; i++) {
            divide_list[i].clear();
        }
        char input = c_set[c];
        for (auto v : state_) {
            int nxt = DFA_STATE[v][input];
            if (nxt == -1) {
                divide_list[minDFA_len].push_back(v);
                continue;
            }
            int set = fa[nxt];
            divide_list[set].push_back(v);
        }
        int cnt = 0;
        for (int i = 0; i <= minDFA_len; i++) {
            // cout << i << "->" << divide_list[i].size() << endl;
            if (divide_list[i].size() != 0)
                cnt++;
        }
        if (cnt == 1) {
            continue;
        } else {
            int len = minDFA_len;
            int ret = 0;
            for (int i = 0; i <= len; i++) {
                if (divide_list[i].size() != 0 && ret != 0) {
                    minDFA_DFA[minDFA_len++] = cal_Set(divide_list[i]);
                }
                else if (divide_list[i].size() != 0 && ret == 0) {
                    minDFA_DFA[idx] = cal_Set(divide_list[i]);
                    ret++;
                }
            }
            set_fa();
            return 1;
        }
    }
    return 0;
}
void getTrans() {
    for (int i = 0; i < minDFA_len; i++) {
        int set = minDFA_DFA[i];
        int cnt = 0;
        int ret = 0;
        for (int k = 1; k <= (1 << dfa_state_len); k <<= 1, cnt++) {
            if (set & k)
                break;
        }
        // cout << "cnt: " << cnt << " ";
        for (int c = 0; c < c_set_len; c++) {
            char input = c_set[c];
            int nxt = DFA_STATE[cnt][input];
            if (nxt == -1)
                minDFA_STATE[i][input] = -1;
            else
                minDFA_STATE[i][input] = fa[nxt];
        }
    }
}
void getDone_StartState() {
    done_state_mindfa_len = 0;
    start_mindfa_len = 0;
    for (int i = 0; i < minDFA_len; i++) {
        int set = minDFA_DFA[i];
        for (int k = 0; k < done_state_dfa_len; k++) {
            if (set & (1 << done_state_dfa[k])) {
                done_state_mindfa[done_state_mindfa_len++] = i;
                break;
            }
        }
        for (int k = 0; k < start_dfa_len; k++) {
            if (set & (1 << start_state_dfa[k])) {
                start_state_mindfa[start_mindfa_len++] = i;
                break;
            }
        }
    }
}
void minDFA() {
    int end = findEnd();
    int netend = (1 << (dfa_state_len)) - 1 - end;
    if (netend == 0) {
        minDFA_DFA[0] = end;
        minDFA_len = 1;
    } else {
        minDFA_DFA[0] = netend;
        minDFA_DFA[1] = end;
        minDFA_len = 2;
    }
    set_fa();
    for (int i = 0; i < minDFA_len; i++) {
        if (divide(minDFA_DFA[i], i)) {
            i--;
        }
    }
    getTrans();
    getDone_StartState();
}
实验结果验证
测试样例1:

正则表达式:(a|b)*abb(a|b)*

NFA输入

2
ab
18
1
17
1,7 -1 -1
2,4 -1 -1
-1 3 -1
6 -1 -1
-1 -1 5
6 -1 -1
1,7 -1 -1
-1 8 -1
-1 -1 9
-1 -1 10
11,17 -1 -1
12,14 -1 -1
-1 13 -1
16 -1 -1
-1 -1 15
16 -1 -1
11,17 -1 -1
-1 -1 -1

输出的转移表及对应关系

NFA转移表
	#	a	b	
0	1,7	-1	-1	
1	2,4	-1	-1	
2	-1	3	-1	
3	6	-1	-1	
4	-1	-1	5	
5	6	-1	-1	
6	1,7	-1	-1	
7	-1	8	-1	
8	-1	-1	9	
9	-1	-1	10	
10	11,17	-1	-1	
11	12,14	-1	-1	
12	-1	13	-1	
13	16	-1	-1	
14	-1	-1	15	
15	16	-1	-1	
16	11,17	-1	-1	
17	-1	-1	-1	
DONE STATE: 17 
START STATE: 0

DFA-NFA
DFA	NFA	
0	{0,1,2,4,7,}
1	{1,2,3,4,6,7,8,}
2	{1,2,4,5,6,7,}
3	{1,2,4,5,6,7,9,}
4	{1,2,4,5,6,7,10,11,12,14,17,}
5	{1,2,3,4,6,7,8,11,12,13,14,16,17,}
6	{1,2,4,5,6,7,11,12,14,15,16,17,}
7	{1,2,4,5,6,7,9,11,12,14,15,16,17,}
8	{1,2,4,5,6,7,10,11,12,14,15,16,17,}

DFA转移表
	a	b	
0	1	2	
1	1	3	
2	1	2	
3	1	4	
4	5	6	
5	5	7	
6	5	6	
7	5	8	
8	5	6	
DONE STATE: 4 5 6 7 8 
START STATE: 0 

minDFA-DFA
minDFA	DFA	
0	{0,2,}
1	{4,5,6,7,8,}
2	{3,}
3	{1,}

minDFA转移表
	a	b	
0	3	0	
1	1	1	
2	3	1	
3	3	2	
DONE STATE: 1 
START STATE: 0 

使用随机生成样例进行测试:

/* (a|b)*abb(a|b)* */
#include <iostream>
#include <random>
#include <time.h>
using namespace std;

int main() {
    srand(time(0));
    freopen("../input_file/input3_", "w", stdout);
    char s[2] = {'a', 'b'};
    for (int i = 0; i <= 4; i++) {
        for (int times = 0; times < 3; times++) {
            for (int j = 0; j < i; j++) {
                int choice = rand() % 2;
                cout << s[choice];
            }
            cout << "abb";
            for (int j = 0; j < i; j++) {
                int choice = rand() % 2;
                cout << s[choice];
            }
        }
        cout << endl;
    }
    for (int num = 0; num < 9; num++) {
        int times = rand() % 15 + 1;
        for (int i = 0; i < times; i++) {
            int choice = rand() % 2;
            cout << s[choice];
        }
        cout << endl;
    }
}

生成的输入字符串:

abbabbabb
babbbaabbababba
aaabbaabbabbbbbbabbba
bababbaabaababbaabbababbaba
bbbaabbbbabbabbabbbabaabbbabbbbba
ba
aaaaaaababaaa
babbaaa
aabaab
bbaaabbbbbaa
b
babbaaabbbba
ab
bbbabaa

输出结果:

abbabbabb:
NFA ACCEPT
DFA ACCEPT
minDFA ACCEPT

babbbaabbababba:
NFA ACCEPT
DFA ACCEPT
minDFA ACCEPT

aaabbaabbabbbbbbabbba:
NFA ACCEPT
DFA ACCEPT
minDFA ACCEPT

bababbaabaababbaabbababbaba:
NFA ACCEPT
DFA ACCEPT
minDFA ACCEPT

bbbaabbbbabbabbabbbabaabbbabbbbba:
NFA ACCEPT
DFA ACCEPT
minDFA ACCEPT

ba:
NFA FAIL
DFA FAIL
minDFA FAIL

aaaaaaababaaa:
NFA FAIL
DFA FAIL
minDFA FAIL

babbaaa:
NFA ACCEPT
DFA ACCEPT
minDFA ACCEPT

aabaab:
NFA FAIL
DFA FAIL
minDFA FAIL

bbaaabbbbbaa:
NFA ACCEPT
DFA ACCEPT
minDFA ACCEPT

b:
NFA FAIL
DFA FAIL
minDFA FAIL

babbaaabbbba:
NFA ACCEPT
DFA ACCEPT
minDFA ACCEPT

ab:
NFA FAIL
DFA FAIL
minDFA FAIL

bbbabaa:
NFA FAIL
DFA FAIL
minDFA FAIL

可以看出三种状态机同为ACCEPT或FAIL。

测试样例2:

正则表达式:(a|b)*a(a|b)

NFA输入

2
ab
14
1
13
1,7 -1 -1
2,4 -1 -1
-1 3 -1
6 -1 -1
-1 -1 5
6 -1 -1
1,7 -1 -1
-1 8 -1
9,11 -1 -1
-1 10 -1
13 -1 -1
-1 -1 12
13 -1 -1
-1 -1 -1

输出的转移表及对应关系

NFA转移表
	#	a	b	
0	1,7	-1	-1	
1	2,4	-1	-1	
2	-1	3	-1	
3	6	-1	-1	
4	-1	-1	5	
5	6	-1	-1	
6	1,7	-1	-1	
7	-1	8	-1	
8	9,11	-1	-1	
9	-1	10	-1	
10	13	-1	-1	
11	-1	-1	12	
12	13	-1	-1	
13	-1	-1	-1	
DONE STATE: 13 
START STATE: 0

DFA-NFA
DFA	NFA	
0	{0,1,2,4,7,}
1	{1,2,3,4,6,7,8,9,11,}
2	{1,2,4,5,6,7,}
3	{1,2,3,4,6,7,8,9,10,11,13,}
4	{1,2,4,5,6,7,12,13,}

DFA转移表
	a	b	
0	1	2	
1	3	4	
2	1	2	
3	3	4	
4	1	2	
DONE STATE: 3 4 
START STATE: 0 

minDFA-DFA
minDFA	DFA	
0	{0,2,}
1	{3,}
2	{1,}
3	{4,}

minDFA转移表
	a	b	
0	2	0	
1	1	3	
2	1	3	
3	2	0	
DONE STATE: 1 3 
START STATE: 0 

使用随机生成样例进行测试:

/* (a|b)*a(a|b) */
#include <iostream>
#include <random>
#include <time.h>
using namespace std;

int main() {
    srand(time(0));
    freopen("../input_file/input1_", "w", stdout);
    char s[2] = {'a', 'b'};
    for (int i = 0; i <= 4; i++) {
        for (int times = 0; times < 3; times++) {
            for (int j = 0; j < i; j++) {
                int choice = rand() % 2;
                cout << s[choice];
            }
            cout << 'a';
            int choice = rand() % 2;
            cout << s[choice] << endl;
        }
    }
    for (int num = 0; num < 8; num++) {
        int times = rand() % 7 + 1;
        for (int i = 0; i < times; i++) {
            int choice = rand() % 2;
            cout << s[choice];
        }
        cout << endl;
    }
}

生成的输入字符串:

ab
ab
aa
bab
aaa
bab
bbaa
aaab
aaaa
bbbab
abbab
abbaa
aabbaa
abaaaa
baaaaa
bb
bbaa
abbbb
bbb
ba
a
bbaba
aba

输出结果:

ab:
NFA ACCEPT
DFA ACCEPT
minDFA ACCEPT

ab:
NFA ACCEPT
DFA ACCEPT
minDFA ACCEPT

aa:
NFA ACCEPT
DFA ACCEPT
minDFA ACCEPT

bab:
NFA ACCEPT
DFA ACCEPT
minDFA ACCEPT

aaa:
NFA ACCEPT
DFA ACCEPT
minDFA ACCEPT

bab:
NFA ACCEPT
DFA ACCEPT
minDFA ACCEPT

bbaa:
NFA ACCEPT
DFA ACCEPT
minDFA ACCEPT

aaab:
NFA ACCEPT
DFA ACCEPT
minDFA ACCEPT

aaaa:
NFA ACCEPT
DFA ACCEPT
minDFA ACCEPT

bbbab:
NFA ACCEPT
DFA ACCEPT
minDFA ACCEPT

abbab:
NFA ACCEPT
DFA ACCEPT
minDFA ACCEPT

abbaa:
NFA ACCEPT
DFA ACCEPT
minDFA ACCEPT

aabbaa:
NFA ACCEPT
DFA ACCEPT
minDFA ACCEPT

abaaaa:
NFA ACCEPT
DFA ACCEPT
minDFA ACCEPT

baaaaa:
NFA ACCEPT
DFA ACCEPT
minDFA ACCEPT

bb:
NFA FAIL
DFA FAIL
minDFA FAIL

bbaa:
NFA ACCEPT
DFA ACCEPT
minDFA ACCEPT

abbbb:
NFA FAIL
DFA FAIL
minDFA FAIL

bbb:
NFA FAIL
DFA FAIL
minDFA FAIL

ba:
NFA FAIL
DFA FAIL
minDFA FAIL

a:
NFA FAIL
DFA FAIL
minDFA FAIL

bbaba:
NFA FAIL
DFA FAIL
minDFA FAIL

aba:
NFA FAIL
DFA FAIL
minDFA FAIL

通过上面测试可以看出,程序正确

关于源码使用提示 或 见README.md

generator: 随机生成对应的正则表达式的正例字符串和反例字符串

生成哪一组测试样例即调用相应的 input*_roll.cc 程序即可

input_file: 输入文件

input*: 输入的NFA图

输入格式:

2              // 输入的字符集的个数
ab             // 字符集 中间无空格
14             // NFA状态总数n:编号从0开始到n-1,默认起始状态为0
1              // 终结状态个数
13             // 终结状态编号
1,7 -1 -1      // 一下n行输入当前状态的转换
2,4 -1 -1      // 无转换用-1代替
-1 3 -1        // 最左一侧为ε的转换
6 -1 -1        // 其余列按照输入的字符集的顺序填写
-1 -1 5        // 有多个转换状态时用 , 分隔(不加空格)
6 -1 -1
1,7 -1 -1
-1 8 -1
9,11 -1 -1
-1 10 -1
13 -1 -1
-1 -1 12
13 -1 -1
-1 -1 -1

input*_: 构造出的测试样例

output_file: 输出文件

out*: 输出的NFA DFA minDFA的状态转移表,以及对应的状态集合,终结状态集合

check*: 用于比对输入的测试样例能否被接受,ACCEPT为接受,FAIL为不可接受

Exp2.cc: 实验二文件

使用时,请注意宏定义的声明,使用哪一组输入文件即定义如:

// 使用input1以及input1_:
#define TEST_CASE_1
// 使用input2以及input2_:
#define TEST_CASE_2
// 使用input3以及input3_:
#define TEST_CASE_3

只能声明1 2 3中的一个

完整实验代码

见文章开始的链接

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值