使用C语言实现正规表达式到自动机的转换

文章目录


前言

        一学期的编译原理学习完成,终于到了可以综合实践的时候了,撸起袖子加油干!!!

使用C语言实现正规表达式到自动机的转换

一、设计内容及要求

        正规表达式与自动机
        编程完成以下功能:
                (1) 将一个正规表达式转换为 NFA
                (2) 将一个 NFA 转换为 DFA
                (3) 对 DFA 进行最小化
                (4) 利用 DFA 判断输入的字符串是否为符合词法规则

二、实现原理

1.将一个正规表达式转换为 NFA

        将给出的正规表达式按照以下规则,转换成NFA。其中输入的正规表达式符号有:(,),*,|,空格(空格代表空串)

使用C语言实现正规表达式到自动机的转换

2.将一个 NFA 转换为 DFA

        我们知道DFA是NFA的特例,并且对于每个NFA M都存在一个DFA M’’,使L(M)=L(M’’)。那么如何才能将NFA转换成接受同样语言的DFA呢?下面介绍一种算法(这种算法称为子集构造法),来实现NFA到DFA的转换。
        子集构造法的中心思想:

使用C语言实现正规表达式到自动机的转换
        为了实现子集构造法的设计需要解决两个主要问题:
        (1)怎样处理NFA中的ε转移?
                解决方法:状态集合I的ε-闭包ε_Closure(I)

使用C语言实现正规表达式到自动机的转换

        (2)怎么表示某个状态在读入字符以后进入的状态集合?
                解决方法:状态集合I的a弧转换

使用C语言实现正规表达式到自动机的转换
        子集构造法实现流程

使用C语言实现正规表达式到自动机的转换
使用C语言实现正规表达式到自动机的转换

3.对 DFA 进行最小化

使用C语言实现正规表达式到自动机的转换
使用C语言实现正规表达式到自动机的转换
使用C语言实现正规表达式到自动机的转换
        化简(最小化)算法

使用C语言实现正规表达式到自动机的转换
使用C语言实现正规表达式到自动机的转换

4.利用 DFA 判断输入的字符串是否为符合词法规则

        当我们完成上面正规表达式到DFA的设计后,要判断输入的字符串是否符合词法规则,只需要将字符串输入,看看能不能从初态走到终态。如果可以,那么该字符串符合词法规则;否则不符合。

5.实例

        为了方便理解,下面提供一个实例来参考

使用C语言实现正规表达式到自动机的转换

三、代码实现

        依据上面的原理,使用C语言编写出将正规表达式转换成自动机的程序

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#define MAXN 1000
#define Visible_letter_count (Visible_letter_max - Visible_letter_min + 1)//算空格, 从32到126共94个可见字符.
#define Visible_letter_min 32
#define Visible_letter_max 126

const char kong = ' ';//空格代表空串

//-a->to
typedef struct NODE
{
    int to;//1
    char a;//a
}NODE;
typedef struct FA //邻接表存储FA
{
    int n, BEGIN;//n为节点个数(包括空节点),BEGIN为初态
    int ENDS[MAXN/100], len_ENDS;//终态集
    NODE data[MAXN][MAXN];//邻接表
    int len[MAXN];//data每一行的长度
    char alphabet[Visible_letter_count];//字母表
    int len_alph;//字母表的长度
}FA;
typedef struct BE//正则表达式转NFA时,用于记录一个NFA的初态和终态
{
    int BEGIN;
    int END;
}BE;



//------------------------------------------------------------------------------1//
int IsEND(FA *fa, int a)//判断一个节点是否为终态
{
    for (int i = 0; i < fa->len_ENDS; i++)
    {
        if (a == fa->ENDS[i]) return 1;//如果集合中包含之前NFA中的终态,那么该集合就是DFA中的终态
    }
    return 0;
}
int insert(FA *fa, int from, int to, char a)//向fa的邻接表中插入一条边
{
    int i;
    //先判断是否为新节点
    if (from >= fa->n)
    {
        if (fa->n >= MAXN) return 1;//节点个数是否超出最大值
        fa->len[fa->n++] = 0;//每行长度为0
    }
    if (to >= fa->n)
    {
        if (fa->n >= MAXN) return 1;
        fa->len[fa->n++] = 0;
    }

    //判断边是否已经存在
    for (i = 0; i < fa->len[from]; i++)
        if ((fa->data[from][i]).a == a && (fa->data[from][i]).to == to) return 0;

    if (fa->len[from] >= MAXN) return 1;//邻接表的长度是否超过最大值

    //插入
    (fa->data[from][fa->len[from]]).a = a;
    (fa->data[from][fa->len[from]++]).to = to;
    return 0;
}
void initAlphabet(FA *fa)//初始化字母表
{
    fa->len_alph = 0;
    int temp[Visible_letter_count] = {0};//哈希表
    for (int i = 0; i < fa->n; i++)
    {
        for (int j = 0; j < fa->len[i]; j++)
        {
            int a = fa->data[i][j].a;
            if (a != ' ') temp[a-Visible_letter_min] = 1;
        }
    }
    for (int i = 0; i < Visible_letter_count; i++)
        if (temp[i])
            fa->alphabet[fa->len_alph++] = i + Visible_letter_min;
}
int to(FA *fa, int from, char a)//计算from节点输入a时的下一个节点
{
    for (int i = 0; i < fa->len[from]; i++)
        if (fa->data[from][i].a == a) return fa->data[from][i].to;
    return -1;
}
int AndFa(FA *fa, BE *stack_FA, int *len_stack_FA)//正则表达式转NFA时的连接操作
{
    if (*len_stack_FA < 2) return 1;
    int b = stack_FA[--*len_stack_FA].BEGIN;
    int e = stack_FA[*len_stack_FA].END;
    int b1 = stack_FA[--*len_stack_FA].BEGIN;
    int e1 = stack_FA[*len_stack_FA].END;
    if (insert(fa, e1, b, kong)) return 1;
    //注意栈是先进后出的,所以b1是begin e是end
    stack_FA[*len_stack_FA].BEGIN = b1;
    stack_FA[(*len_stack_FA)++].END = e;
    return 0;
}
int OrFa(FA *fa, BE *stack_FA, int *len_stack_FA)//正则表达式转NFA时的|操作
{
    if (*len_stack_FA < 2) return 1;
    int b = stack_FA[--*len_stack_FA].BEGIN;
    int e = stack_FA[*len_stack_FA].END;
    int b1 = stack_FA[--*len_stack_FA].BEGIN;
    int e1 = stack_FA[*len_stack_FA].END;
    if (insert(fa, b1, b, kong)) return 1;
    if (insert(fa, e1, e, kong)) return 1;
    //注意栈是先进后出的,所以b1是begin e是end
    stack_FA[*len_stack_FA].BEGIN = b1;
    stack_FA[(*len_stack_FA)++].END = e;
    return 0;
}

//将正则表达式转换成NFA
int ReToNFA(char *re, FA *nfa)//此处用&代表连接
{
    nfa->len_ENDS = 0;//初始化终态集的个数为0个
    if (*re != '\0')//如果re的长度大于0
    {
        //len_stack_op:记录栈顶指针的位置   len_stack_FA:记录栈中元素个数
        int len_stack_op = 1, len_stack_FA = 0;
        //用于分析操作符的栈 stack_op   temp用来辅助分析&操作
        char stack_op[MAXN] = {'('}, temp;
        //用于分析状态
        BE stack_FA[MAXN];
        nfa->n = 0;
        strcat(re, ")");//在re的开始和结束添加括号,方便计算
        while (*re != '\0')  //    (a|b)(c|d)*     (  )   |  *  &  ab
        {
            switch (*re)
            {
            case '(':
                if (len_stack_op >= MAXN) return 1;//栈是否溢出
                stack_op[len_stack_op++] = '(';
                break;
            case ')':
                while (1)//先做前面的收尾工作
                {
                    if (len_stack_op < 1) return 1;//如果栈空,代表正则表达式括号不匹配
                    if (stack_op[--len_stack_op] == '(') break;

                    //此处要先判断&,再判断|,因为当&和|连续出现时,&靠近栈顶 ()&()|()
                    if (stack_op[len_stack_op] == '&')
                    {
                        if (AndFa(nfa, stack_FA, &len_stack_FA)) return 1;
                    }
                    else if (stack_op[len_stack_op] == '|')
                    {
                        if (OrFa(nfa, stack_FA, &len_stack_FA)) return 1;
                    }
                }
                //如果下一个输入时左括号或字符,则添加一个&
                temp = *(re+1);
                if (temp != 0 && temp != ')' &&  temp != '*' && temp != '|')
                {
                    if (len_stack_op > 0 && stack_op[len_stack_op-1] == '&')//()&()b  (
                    {
                        len_stack_op--;
                        if (AndFa(nfa, stack_FA, &len_stack_FA)) return 1;
                    }
                    if (len_stack_op >= MAXN) return 1;
                    stack_op[len_stack_op++] = '&';//()b
                }
                break;
            case '*'://闭包
                if (len_stack_FA < 1) return 1;//*前必有前面形成的NFA
                //如果是终结符
                if (insert(nfa, stack_FA[len_stack_FA-1].END, stack_FA[len_stack_FA-1].BEGIN, kong)) return 1;
                //不是终结符 
                temp = *(re+1);
                if (temp != 0 && temp != ')' &&  temp != '*' && temp != '|')
                {
                    if (len_stack_op > 0 && stack_op[len_stack_op-1] == '&') //如 // a&b*b
                    {
                        len_stack_op--;
                        if (AndFa(nfa, stack_FA, &len_stack_FA)) return 1;
                    }
                    if (len_stack_op >= MAXN) return 1;
                    stack_op[len_stack_op++] = '&';   //如 a*b 
                }
                break;
            case '|'://优先级与大于或,且与和或都是双目运算符 如 a&b|c
                if (len_stack_op > 0 && stack_op[len_stack_op-1] == '&')//前面有&则先做与运算
                {
                    len_stack_op--;
                    if (AndFa(nfa, stack_FA, &len_stack_FA)) return 1;
                }
                if (len_stack_op > 0 && stack_op[len_stack_op-1] == '|')//如果前面有|先做前面的或
                {
                    len_stack_op--;
                    if (OrFa(nfa, stack_FA, &len_stack_FA)) return 1;
                }
                if (len_stack_op >= MAXN) return 1;
                stack_op[len_stack_op++] = '|';//将此次的或进栈  a|
                break;
            default:
                if (*re < Visible_letter_min || *re > Visible_letter_max) return 1;//排除一些字符

                //插入边(字母)
                if (insert(nfa, nfa->n, nfa->n+1, *re)) return 1;
                //更新状态
                if (len_stack_FA >= MAXN) return 1;
                stack_FA[len_stack_FA].BEGIN = nfa->n-2;
                stack_FA[len_stack_FA++].END = nfa->n-1;
                //预判下一个字符是不是边(字母)
                temp = *(re+1);
                if (temp != 0 && temp != ')' &&  temp != '*' && temp != '|')
                {
                    if (len_stack_op > 0 && stack_op[len_stack_op-1] == '&')//如果前面包含一个& 如 a&bb
                    {
                        len_stack_op--;
                        if (AndFa(nfa, stack_FA, &len_stack_FA)) return 1;
                    }
                    if (len_stack_op >= MAXN) return 1;
                    stack_op[len_stack_op++] = '&'; //否则将与操作入栈 ab
                }
            }
            re++;
        }
        if (len_stack_op > 0 || len_stack_FA > 1) return 1;
        nfa->BEGIN = stack_FA[0].BEGIN;//初态
        nfa->ENDS[nfa->len_ENDS++] = stack_FA[0].END;//在此处只有一个终态
        initAlphabet(nfa);
    }
    else
    {
        //为了满足输入为空的特殊情况
        nfa->n = 2;
        nfa->BEGIN = nfa->len[0] = nfa->len[1] = 0;
        nfa->ENDS[nfa->len_ENDS++] = 1;
    }
    return 0;
}


typedef struct SET//集合
{
    int n; //记录当前t中的元素个数
    int t[MAXN];
}SET;

int set_insert(SET *set, int a)//集合插入
{
    int i;
    for (i = 0; i < set->n; i++) if ((set->t)[i] == a) return 1;
    if (set->n >= MAXN) return -1;
    (set->t)[set->n++] = a;
    return 0;
}
int set_delete(SET *set, int a)//删除集合的一个元素
{
    int i;
    for (i = 0; i < set->n; i++) if (set->t[i] == a) break;
    if (i == set->n) return -1;
    for (int j = i + 1; j < set->n; j++) set->t[j - 1] = set->t[j];
    set->n--;
    return 0;
}

//查找集合sets[sets_n]与前面的那个集合一样,如果没有一样的,则将sets[sets_n]加入sets
//集合内部是有序的
int if_same(SET *sets, int *sets_n)
{
    int i, j, len = sets[*sets_n].n;
    for (i = 0; i < *sets_n; i++)
    {
        if (sets[i].n != len) continue;
        for (j = 0; j < len; j++)
            if (sets[i].t[j] != sets[*sets_n].t[j]) break;

        if (j == len)
        {
            return i;
        }
    }
    return (*sets_n)++;
}
int cmp(const void* _a , const void* _b)//用于qsort
{
    return *((int*)_a) - *((int*)_b);
}


/*-----------------------------------------------------(2)将一个 NFA 转换为 DFA */ 
SET sets[MAXN];//在NFAToDFA存状态集,在DFA化简中存状态集
int len_sets = 0;
int NFAToDFA(FA *nfa, FA *dfa)
{
    dfa->n = 0;
    dfa->len_ENDS = 0;
    int i, j, k, m;

    //求ε-clouser(0)
    len_sets = 1;//记录当前集合内的个数
    sets[0].n = 0;
    set_insert(sets, 0);
    for (i = 0; i < sets[0].n; i++)
    {
        NODE *temp = (nfa->data)[sets[0].t[i]];
        int temp_len = (nfa->len)[sets[0].t[i]];//每一行的最大长度
        for (j = 0; j < temp_len; j++)
            if (temp[j].a == kong) set_insert(sets, temp[j].to);//如果连接的边为空就把状态加入
    }
    //将集合排序,方便判断两个集合是否一样
    qsort(sets[0].t, sets[0].n, sizeof(int), cmp);

    for (i = 0; i < len_sets; i++) //遍历状态集
    {
        for (j = 0; j < nfa->len_alph; j++) //遍历边
        {
            sets[len_sets].n = 0;
            //求move(sets[i],alphabet[j])
            for (k = 0; k < sets[i].n; k++)//遍历每个状态集中的元素
            {
                for (m = 0; m < (nfa->len)[sets[i].t[k]]; m++)//查看每个元素的邻接边
                    if ((nfa->data)[sets[i].t[k]][m].a == nfa->alphabet[j])
                        set_insert(sets+len_sets, (nfa->data)[sets[i].t[k]][m].to);
            }
            //求ε-clouser(move(sets[i],alphabet[j]))
            for (m = 0; m < sets[len_sets].n; m++)//遍历每个状态集中的元素
            {
                NODE *temp = (nfa->data)[sets[len_sets].t[m]];
                int temp_len = (nfa->len)[sets[len_sets].t[m]];
                for (k = 0; k < temp_len; k++)//将通过空能到达的元素插入
                    if (temp[k].a == kong) set_insert(sets+len_sets, temp[k].to);
            }
            qsort(sets[len_sets].t, sets[len_sets].n, sizeof(int), cmp);

            //如果集合不为空,且是新集合,就在dfa中加入对应的边
            if (sets[len_sets].n)
            {
                k = if_same(sets, &len_sets);
                insert(dfa, i, k, nfa->alphabet[j]);//形成DFA新的边
            }
        }
    }
    dfa->BEGIN = 0;
    //求终态
    for (i = 0; i < len_sets; i++)//每个状态集
    {
        for (j = 0; j < sets[i].n; j++)//每个状态集的全部元素
        {
            if (IsEND(nfa, sets[i].t[j]))//如果包含NFA的终态,则该状态就是终态
            {
                dfa->ENDS[dfa->len_ENDS++] = i;//加入终态集
                break;
            }
        }
    }

    //复制字母表
    dfa->len_alph = nfa->len_alph;
    for (i = 0; i < nfa->len_alph; i++) dfa->alphabet[i] = nfa->alphabet[i];
    return 0;
}

//找到元素a所在的集合
int FindInSets(SET *Sets, int set_n, int a)
{
    if (a < 0) return -1;
    for (int i = 0; i < set_n; i++)
    {
        for (int j = 0; j <Sets[i].n; j++)
            if (Sets[i].t[j] == a) return i;
    }
    return -1;
}

/*-----------------------------------------------------(3)对 DFA 进行最小化*/ 
int type[MAXN], len_type;//在SimplifyDFA中,存每个节点经过映射后所在的集合
void SimplifyDFA(FA *dfa)
{
    initAlphabet(dfa);//初始化字母表

    //终态和非终态放在两个集合   sets[0]初态    sets[1]终态
    len_sets = 2;
    sets[0].n = sets[1].n = 0;
    for (int i = 0; i < dfa->n; i++)//遍历dfa的所有状态
        if (IsEND(dfa, i)) set_insert(sets + 1, i);
        else set_insert(sets, i);


    int flag = 1;
    while (flag)
    {
        flag = 0;
        for (int i = 0; i < len_sets; i++)//遍历DFA每个状态
        {
            len_type = sets[i].n;//获取集合中的元素个数
            if (len_type < 2) continue;//只有一个元素的集合不用再计算

            for (int j = 0; j < dfa->len_alph; j++)//遍历每条边
            {
                sets[len_sets].n = 0;
                //求type
                for (int k = 0; k < len_type; k++)//遍历sets集合中的每个元素
                {
                    int t = to(dfa, sets[i].t[k], dfa->alphabet[j]);//获取每个集合中元素输入不同的边转向的元素
                    //如 type[k]=0; 表示集合sets[i]的第k个元素,在划分后的第0个集合,也就是初态集
                    type[k] = FindInSets(sets, len_sets, t);//表示集合sets[i]的第k个元素,输入dfa->alphabet[j]后转向状态t所在的集合
                }

                //对集合进行划分
                //以type[0]为基础,若type[k]和type[0]不一样,则将所有type[m]=type[k]的m放在一个新的集合
                //type[k]=-1代表没有映射,-2代表已经被移动过
                for (int k = 1; k < len_type; k++)//遍历集合中的每个元素
                    if (type[k] != -2 && type[k] != type[0])
                    {
                        flag = 1;//运行到此,则sets有所更新,需要重新循环

                        for (int m = k + 1; m < len_type; m++)
                            if (type[m] == type[k])//将所有type[m]=type[k]的m放在一个新的集合
                        {
                            type[m] = -2;
                            set_delete(sets + i, m);//将m从原来集合中删除
                            set_insert(sets + len_sets, m);//将m放入新集合
                        }
                        //k和m是一类要放在一起
                        type[k] = -2;//操作k元素
                        set_delete(sets + i, k);//将k从原来集合中删除
                        set_insert(sets + len_sets, k);//将k放入新集合
                        len_sets++;
                    }
            }
        }
    }

    //在dfa中,将在同一个集合的状态合并
    for (int i = 0; i < len_sets; i++)//遍历所有状态集(相对于划分后的状态集)
    {
        int temp0 = sets[i].t[0];
        for (int j = 1; j < sets[i].n; j++)//遍历所有元素
        {
            int tempj = sets[i].t[j];

            //合并tempj发出的边
            for (int k = 0; k < dfa->len[tempj]; k++)
                insert (dfa, temp0, dfa->data[tempj][k].to, dfa->data[tempj][k].a);

            //合并发向tempj的边
            for (int k = 0; k < dfa->n; k++)
                for (int m = 0; m < dfa->len[k]; m++)
                    if (dfa->data[k][m].to == tempj) dfa->data[k][m].to = temp0;

            //将data[tempj]的长度置为-1,代表tempj被弃用
            dfa->len[tempj] = -1;

            //如果tempj是终态,则在终态集中删除
            if (IsEND(dfa, tempj))
            {
                for (int k = tempj + 1; k < dfa->len_ENDS; k++)
                    dfa->ENDS[k-1] = dfa->ENDS[k];
                dfa->len_ENDS--;
            }
        }
    }
}


/*-----------------------------------------------------(4)利用 DFA 判断输入的字符串是否为符合词法规则 */ 
int RunDFA(FA *dfa, char *s)//判断s是否被dfa接受
{
    int state = dfa->BEGIN;
    while (*s != '\0')
    {
        state = to(dfa, state, *s);
        if (state < 1) return 1;
        s++;
    }
    if (IsEND(dfa, state)) return 0;
    else return 1;
}

void PrintFA(FA *fa) //打印FA
{
    printf("BEGIN:%d, END:%d", fa->BEGIN, fa->ENDS[0]);
    for (int i = 1; i < fa->len_ENDS; i++) printf(", %d", fa->ENDS[i]);
    printf("\n");
    for (int i = 0; i < fa->n; i++)
    {
        if (fa->len[i] < 0) continue;
        printf("%d: ", i);
        for (int j =0; j < fa->len[i]; j++)
        {
            printf("(value:%c, to:%d) ", (fa->data[i][j]).a, (fa->data[i][j]).to);
        }
        printf("\n");
    }
    printf("\n");
}

FA NFA, DFA;//FA所占内存较大,只能为全局变量
void test()
{
    FA *nfa = &NFA, *dfa = &DFA;
    //re记录输入的正则表达式
    char re[100], s[100];

    while(1)
    {
        //(1|2|3|4|5|6|7|8|9)(0|1|2|3|4|5|6|7|8|9)*
        printf("请输入一个正则表达式(符号有(,),*,|,空格,空格代表空串),");
        printf("输入Crtl+Z退出正则表达式测试\n");
        if (!gets_s(re)) break;
        if (ReToNFA(re, nfa))
        {
            printf("error\n\n");
            continue;
        }
//        PrintFA(nfa);
        NFAToDFA(nfa, dfa);
        printf("\nNFA:\n");       PrintFA(nfa);
        printf("DFA:\n");       PrintFA(dfa);
        SimplifyDFA(dfa);
        printf("化简的DFA:\n"); PrintFA(dfa);
        printf("\n");
        while (1)
        {
            printf("输入一个字符串,");
            printf("输入Crtl+Z退出DFA测试\n");
            if (!gets_s(s)) break;
            //将字符串放到dfa中运行一遍看能不能走到终态
            if (RunDFA(dfa, s)) printf("Input does not match!\n");//不能走到终态,不匹配
            else printf("Input match!\n");//能走到终态,匹配
            printf("\n");
        }
    }
}

int main()
{
    //testSimplifyDFA();
    test();
    return 0;
}


        运行结果

使用C语言实现正规表达式到自动机的转换

总结

        对正规表达式到自动机转换的介绍就到这里啦,希望这篇文章能给予你一些帮助,感谢各位人才的:点赞、收藏和评论,我们下次见。

使用C语言实现正规表达式到自动机的转换

上一篇:第10章 字符集,排序规则,Unicode


下一篇:grouping sets的用法