Epsilon闭包计算

本文详细介绍了Epsilon闭包计算的过程,通过一个具体的文法例子展示了如何从NFA转换为DFA,并利用YACC和WACC的原理进行计算。文中还提供了测试函数,用于计算特定文法的闭包项,并展示了生成的分析表,讨论了LR(0)、LALR(1)的相关冲突问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.1.1 Epsilon闭包计算

同样使用文法

S’ - > S

S – > ( S ) S

S - > 

4.2.1节给出了8个项目状态,构成的NFA也有8个状态。如下图:



图表 46

通过NFA的eposilon转换,我们可以得到该文法的DFA。如下图:



图表 47

我们可以看到,在NFA向DFA进行eposilon转换的过程中,DFA中的状态实际上是将NFA的状态中的项作eposilon闭包后合并入同一状态中得到的。因此产生epsilon闭包的项称为核心项(Kernel Item),而被产生的就是闭包项(closure Item),而且闭包项还可以产生新的闭包项。如S’ - > •S是状态0的核心项,S->•(S)S 和S->• 就是闭包项。依据这个特点,实际上生成DFA可以直接从文法中生成,而不必先构造NFA而后在构造DFA,YACC是这样做的,WACC也是这样做的。

在WACC的代码中,见4.2.1节CItem定义的15行给出的函数void ComputerEClosure(CLanguage &L, vector<CItem> &Ivec),该函数就是计算Item的闭包项。下面给出一个测试函数,这个测试函数中的Item项是根据4.2.1节(1)所示的方法手工构造出来的。后面的章节会介绍如何自动生产Item哦。计算文法

 A1->A 

 A->( A ) 

A->a

的闭包项。

void test4()

{

    CLanguage lang;

//定义一个符号集合V

    CSymbol A1(1,"A1");//v1

    CSymbol A(2, "A");  //v2

    CSymbol LP(3,"("); //v3 

    CSymbol RP(4,")");//v4

    CSymbol a  (5,"a");//v5

    //一.获得CLanguage之vector<CSymbol>

    lang.InsertSymbol(A1);         //1

    lang.InsertSymbol(A);

    lang.InsertSymbol(LP);         //3 

    lang.InsertSymbol(RP);         //4

    lang.InsertSymbol(a);         //5

    showsym(lang.symbol);                 //显示之

    //二.获得CLanguage之vector<Rules>

    //1.获得rightpart之Vector<CSymbol*>

#define vlang(x) (&lang.symbol[x])

   //rule1

    vector<CSymbol*> rpart1;

    rpart1.push_back(vlang( 2 ));

    //2。获得leftpart--取得rules

    CRules r1(vlang(1),rpart1);

    //3.插入vector<Rules> 

    showrule( r1 );

    //r2

    vector<CSymbol*> rpart2;

    rpart2.push_back(vlang(3));

    rpart2.push_back(vlang(2));

    rpart2.push_back(vlang(4));

    CRules r2(vlang(2),rpart2);

    showrule( r2 );

    //r3

    vector<CSymbol*> rpart3;

    rpart3.push_back(vlang(5));

    CRules r3(vlang(2),rpart3);

    showrule( r3 );

    

    cout<<" \nThe language's symbol is "<<endl;

   

    showsym( lang.symbol );

    lang.InsertRules(r1);

    lang.InsertRules(r2);

    lang.InsertRules(r3);

    

    cout << "\nThe languge's rules is"<<endl; 

    for ( int i = 0; i<lang.rules.size(); i++)

       showrule(lang.rules[i]);

#undef vlang 

//三.获得开始符号

lang.SetStartSymbol(lang.symbol[1]);

cout<<"\nlanguage's startsymbol is "<<lang.startsym->name

<<endl;

//language初始化结束

//计算first集合

//1.计算所有终结符的first集合

//lang.FirstAllTerm();

    //2.从rules中计算first集合

    lang.ComputerFirstSet();

    for (  i = 0; i<lang.symbol.size(); i++)

       showFirst(lang.symbol[i],lang);

    

    //设置一个item 并计算其closure

    CItem item;

    vector < CItem >  Ivec;

    //I0

    item.setDot( 0 );

    item.setRuleInd( 0 );

    item.ComputerEClosure(lang ,Ivec);

    //其中Ivec[0] 是核心项目,切不可直接使用item 计算

    Ivec[0].precs.insert(0);//插入"$"

    Ivec[0].ComputerEPrecs(lang,  Ivec);

    cout<<"\nI0:"<<endl;

    for (  i = 0; i < Ivec.size(); i++)

    {

        dispItem( lang,  Ivec[i]);

    }

    //I1

    Ivec[0].Go(item,lang);

    Ivec.clear();

    item.ComputerEClosure(lang,Ivec);

    Ivec[0].ComputerEPrecs(lang,  Ivec);

    cout<<"\nI1:"<<endl;

    for (  i = 0; i < Ivec.size(); i++)

    {

        dispItem( lang,  Ivec[i]);

    }

    //I2

    Ivec.clear();

    item.setDot(1);

    item.setRuleInd(2);

    item.ComputerEClosure(lang,Ivec);

    Ivec[0].precs.insert(0);//插入"$"

    Ivec[0].precs.insert(4);//插入)

    Ivec[0].ComputerEPrecs(lang,  Ivec);

    cout<<"\nI2:"<<endl;

    for (  i = 0; i < Ivec.size(); i++)

    {

        dispItem( lang,  Ivec[i]);

    }

    //I3

    Ivec.clear();

    item.setDot(1);

    item.setRuleInd(1);

    item.ComputerEClosure(lang,Ivec);

    Ivec[0].precs.insert(0);//插入"$"

    Ivec[0].precs.insert(4);//插入)

    Ivec[0].ComputerEPrecs(lang,  Ivec);

    cout<<"\nI3:"<<endl;

    for (  i = 0; i < Ivec.size(); i++)

    {

        dispItem( lang,  Ivec[i]);

    }

    //I4

    Ivec.clear();

    item.setDot(2);

    item.setRuleInd(1);

    item.ComputerEClosure(lang,Ivec);

    Ivec[0].precs.insert(0);//插入"$"

    Ivec[0].precs.insert(4);//插入)

    Ivec[0].ComputerEPrecs(lang,  Ivec);

    cout<<"\nI4:"<<endl;

    for (  i = 0; i < Ivec.size(); i++)

    {

        dispItem( lang,  Ivec[i]);

    }

    //I5

    Ivec.clear();

    item.setDot(3);

    item.setRuleInd(1);

    item.ComputerEClosure(lang,Ivec);

    Ivec[0].precs.insert(0);//插入"$"

    Ivec[0].precs.insert(4);//插入)

    Ivec[0].ComputerEPrecs(lang,  Ivec);

    cout<<"\nI5:"<<endl;

    for (  i = 0; i < Ivec.size(); i++)

    {

        dispItem( lang,  Ivec[i]);

    }

}

计算结果如下

*******===Test4====***

$: eposilon is  0 nt is 0 value is 0

A1: eposilon is  0 nt is 0 value is 1

A: eposilon is  0 nt is 0 value is 2

(: eposilon is  0 nt is 0 value is 3

): eposilon is  0 nt is 0 value is 4

a: eposilon is  0 nt is 0 value is 5

Rule: A1->A 

Rule: A->( A ) 

Rule: A->a 

 

The language's symbol is 

$: eposilon is  0 nt is 0 value is 0

A1: eposilon is  0 nt is 1 value is 1

A: eposilon is  0 nt is 1 value is 2

(: eposilon is  0 nt is 0 value is 3

): eposilon is  0 nt is 0 value is 4

a: eposilon is  0 nt is 0 value is 5

The languge's rules is

Rule: A1->A 

Rule: A->( A ) 

Rule: A->a 

language's startsymbol is A1

$:firstset value is $ 

A1:firstset value is ( a 

A:firstset value is ( a 

(:firstset value is ( 

):firstset value is ) 

a:firstset value is a 

I0:

A1->@A 

The precs is :$ 

A->@( A ) 

The precs is :$ 

A->@a 

The precs is :$ 

I1:

A1->A @

The precs is :$ 

I2:

A->a @

The precs is :$ ) 

I3:

A->( @A ) 

The precs is :$ ) 

A->@( A ) 

The precs is :) 

A->@a 

The precs is :) 

I4:

A->( A @) 

The precs is :$ ) 

I5:

A->( A ) @

The precs is :$ )

需要指出的是,precs是先行符号集,这里的先行符号是自己构造出来的,这个测试函数除了调用ComputerEClosure函数之外,还调用了ComputerEPrecs,这个函数是将核心集的先行符号集扩散到闭包项中,也是LALR(1)的一个重要函数。计算结果中的@相当于圆点。现在我们看看ComputerEClosure函数是怎么工作的。

void CItem::ComputerEClosure(CLanguage & L, vector < CItem > & Ivec)

{

    int changes = 1;

/*/-------------------算法--------*/

//a) I 中项目在closesure 中

    Ivec.push_back(*this);// I 即为核心项目 A->α.Bβ

//b)如果A->α.Bβ属于closure(I),则B->.γ项目也属于closure(I)

//c)重复b)直到不出现新项目为止

    while (changes)

    {

        changes = 0;

        changes += UnionClosure(Ivec,L);//直到现有的没有变化

    }

}

注意底纹为灰色的注释,请将注释与4.2.1节(2)所指出的ε状态图对应起来,在看看代码的实现,明白了吧。其中产生的核心项和闭包项均填充在Ivec中。

进入UnionClosure函数,可以知道如何从核心项目生产闭包项目,以及再由闭包项目生成新的闭包项目。这段代码的注释很清楚,不再逐条解释了。

int CItem::UnionClosure(vector < CItem > & Ivec, CLanguage &L)

{

     int changes = 0;

     int section = 0;

     int ruleInd = -1;

     CItem aItem;

     //如果找到一条item如果新鲜,插入,并且changes自加1

     //否则继续

     //直到再也找不到

     for ( int i = 0; i < Ivec.size(); i++)

     {

         if ( Ivec[i].Dot >= L.rules[Ivec[i].RuleInd].RightPart.size()) //Dot是规约句子

         { continue;}

          //或rules所在的dot 位置不是非终结符    

         else if ( !(L.rules[Ivec[i].RuleInd].RightPart[Ivec[i].Dot]->isNT))

         {continue;}

         else //其它情况均要针对i项插入新鲜item

         {

             while(-1!= (ruleInd=getRule(L.rules[Ivec[i].RuleInd].RightPart[Ivec[i].Dot],L,

                 section)))

             {

                   aItem.setRuleInd(ruleInd);

                   aItem.setDot( 0 );

                   if (checkFresh(aItem,Ivec))//检查是否为新鲜玩意

                   {

                       changes++;

                       Ivec.push_back(aItem);

                   }

             }

         }

     }

     return changes;

}

其中,灰色底纹包含getRule的那一行比较长,它实际上就是取A->α.Bβ中以B为左部的规则,section是一个控制开关,初始值必须为0,让getRule每次取出来的规则均不相同。

如果将上面文法给出的Item计算的闭包项结果绘成状态图的话,就是下面这个图了。


图表 48

根据这个图,可以生成该文法的分析表,如下:

状 态

动 作

规 则

输  入

Goto

a

A

0

移进

3

2

1

1

归约

A’->A

2

归约

A->a

3

移进

3

2

4

4

移进

5

5

归约

A->(A)

图表 49

 

在图表 48与图表 49中我们可以看到,在状态1或状态2中只能有一个规约式,如果出现多个规约式,LR(0)分析就不知道应该选择哪个规约式进行规约,此时LR(0)的文法出现了二义性,这种情况被称为规约-规约冲突(reduce-reduce conflict)。如果是无法知道应该是移进还是规约,那么就成为移进-规约冲突(shift-reduce conflict)。实际上,我们通常使用的语言,均不是LR(0)文法,因此,LR(0)文法的分析能力是很弱的。但是,使用反向先行传播算法,可以直接从LR(0)状态生成LALR(1)的状态,远比从LR(1)合并简单。因此,在这里花了很长的篇幅来讲LR(0)。

如果在LR(0)上添加先行符号的指导,那么就可以解决规约-规约和移进-规约冲突。LALR(1)就是这种思想的产物。根据LR(1)文法会将LR(0)状态分裂出很多状态来。而LALR(1)就是LR(1)的改进,它保持了LR(0)的小规模优点,又保持了LR(1)的先行符号所带来的能力。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值