<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>idv2 &#187; compiler</title>
	<atom:link href="http://tech.idv2.com/tag/compiler/feed/" rel="self" type="application/rss+xml" />
	<link>http://tech.idv2.com</link>
	<description>关注Web开发技术，关注Internet。</description>
	<lastBuildDate>Tue, 27 Jul 2010 12:54:54 +0000</lastBuildDate>
	<generator>http://wordpress.org/?v=2.8.5</generator>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
			<item>
		<title>利用有限自动机分析正则表达式</title>
		<link>http://tech.idv2.com/2006/05/08/parse-regex-with-dfa/</link>
		<comments>http://tech.idv2.com/2006/05/08/parse-regex-with-dfa/#comments</comments>
		<pubDate>Mon, 08 May 2006 14:21:29 +0000</pubDate>
		<dc:creator>charlee</dc:creator>
				<category><![CDATA[其他]]></category>
		<category><![CDATA[compiler]]></category>
		<category><![CDATA[dfa]]></category>
		<category><![CDATA[nfa]]></category>
		<category><![CDATA[regexp]]></category>

		<guid isPermaLink="false">http://charlee.itbdns.com/tech/archive/99.html</guid>
		<description><![CDATA[<!-- begin Pukiwiki generated code--><p>程序编译的第一个阶段是<strong>词法分析</strong>，即把字节流识别为<strong>记号</strong>（token）流，
提供给下一步的<strong>语法分析</strong>过程。而识别记号的方法就是正则表达式的分析。
本文介绍利用<strong>有限自动机</strong>分析表达式的方法。</p>
<!-- end Pukiwiki generated code--><span id="more-79"></span><!-- begin Pukiwiki generated code--><div class="contents">
<a id="contents_2"></a>
<ul class="list1" style="padding-left:16px;margin-left:16px"><li><a href="#content_2_0">  概念</a></li>
<li><a href="#content_2_1">  将正则表达式转换为NFA(Thompson构造法)</a>
<ul class="list2" style="padding-left:16px;margin-left:16px"><li><a href="#content_2_2">  算法</a></li>
<li><a href="#content_2_3">  性质</a></li>
<li><a href="#content_2_4">  示例</a></li></ul></li>
<li><a href="#content_2_5">  将NFA转化为DFA</a>
<ul class="list2" style="padding-left:16px;margin-left:16px"><li><a href="#content_2_6">  算法</a></li>
<li><a href="#content_2_7">  示例</a></li></ul></li>
<li><a href="#content_2_8">  NFA和DFA的效率</a></li></ul>
</div>

<hr class="full_hr" />
<h2 id="content_2_0">概念</h2>
<dl class="list1" style="padding-left:16px;margin-left:16px"><dt>记号</dt>
<dd>有字母表中的符号组成的有限长度的序列。记号s的<strong>长度</strong>记为<strong>|s|</strong>。
长度为0的记号称为<strong>空记号</strong>，记为<strong>ε</strong>。</dd></dl>
<dl class="list1" style="padding-left:16px;margin-left:16px"><dt>有限自动机(Finite State Automaton)</dt>
<dd>为研究某种计算过程而抽象出的计算模型。
拥有有限个状态，根据不同的输入每个状态可以迁移到其他的状态。</dd></dl>
<dl class="list1" style="padding-left:16px;margin-left:16px"><dt>非确定有限自动机(Nondeterministic Finite Automaton)</dt>
<dd>简称<strong>NFA</strong>，由以下元素组成：</dd>

<dd>1. 有限状态集合<em>S</em>；</dd>

<dd>2. 有限输入符号的字母表<em>Σ</em>；</dd>

<dd>3. 状态转移函数<em>move</em>；</dd>

<dd>4. 开始状态 <em>sSUB{0}</em>；</dd>

<dd>5. 结束状态集合<em>F</em>，<em>F ∈ S</em>。</dd>

<dd>自动机初始状态为<em>sSUB{0}</em>，逐一读入输入字符串中的每一个字母，根据当前状态、读入的字母，
由状态转移函数<em>move</em>控制进入下一个状态。如果输入字符串读入结束时自动机的状态属于结束状态集合<em>F</em>，
则说明该自动机接受该字符串，否则为不接受。</dd></dl>
<dl class="list1" style="padding-left:16px;margin-left:16px"><dt>确定有限自动机(Deterministic Finite Automaton)</dt>
<dd>简称<strong>DFA</strong>，是NFA的一种特例，
有以下两条限制：</dd>

<dd>1. 对于空输入<em>ε</em>，状态不发生迁移；</dd>

<dd>2. 某个状态对于每一种输入最多只有一种状态转移。</dd></dl>

<h2 id="content_2_1">将正则表达式转换为NFA(Thompson构造法)</h2>

<h3 id="content_2_2">算法</h3>
<p><strong>算法1</strong> 将正则表达式转换为NFA(Thompson构造法)</p>
<p><strong>输入</strong> 字母表Σ上的正则表达式r</p>
<p><strong>输出</strong> 能够接受<em>L(r)</em>的NFA <em>N</em></p>
<p><strong>方法</strong> 首先将构成<em>r</em>的各个元素分解，对于每一个元素，按照下述<strong>规则1</strong>和<strong>规则2</strong>生成NFA。
<strong>注意</strong>：如果<em>r</em>中记号<em>a</em>出现了多次，那么对于<em>a</em>的每次出现都需要生成一个单独的NFA。</p>
<p>之后依照正则表达式r的文法规则，将生成的NFA按照下述<strong>规则3</strong>组合在一起。</p>
<p><strong>规则1</strong> 对于空记号<em>ε</em>，生成下面的NFA。</p>
<div class="img_margin" style="text-align:left"><img src="http://tech.idv2.com/wp-content/uploads/2006/09/fig01.png" alt="fig01.png" title="fig01.png" width="207" height="42" /></div>

<p><strong>规则2</strong> 对于<em>Σ</em>的字母表中的元素<em>a</em>，生成下面的NFA。</p>
<div class="img_margin" style="text-align:left"><img src="http://tech.idv2.com/wp-content/uploads/2006/09/fig02.png" alt="fig02.png" title="fig02.png" width="207" height="42" /></div>

<p><strong>规则3</strong> 令正则表达式<em>s</em>和<em>t</em>的NFA分别为<em>N(s)</em>和<em>N(t)</em>。</p>
<p>a) 对于<em>s|t</em>，按照以下的方式生成NFA <em>N(s|t)</em>。</p>
<div class="img_margin" style="text-align:left"><img src="http://tech.idv2.com/wp-content/uploads/2006/09/fig03.png" alt="fig03.png" title="fig03.png" width="314" height="124" /></div>

<p>b) 对于<em>st</em>，按照以下的方式生成NFA <em>N(st)</em>。</p>
<div class="img_margin" style="text-align:left"><img src="http://tech.idv2.com/wp-content/uploads/2006/09/fig04.png" alt="fig04.png" title="fig04.png" width="333" height="46" /></div>

<p>c) 对于<em>s*</em>，按照以下的方式生成NFA <em>N(s*)</em>。</p>
<div class="img_margin" style="text-align:left"><img src="http://tech.idv2.com/wp-content/uploads/2006/09/fig05.png" alt="fig05.png" title="fig05.png" width="330" height="150" /></div>

<p>d) 对于<em>(s)</em>，使用<em>s</em>本身的NFA <em>N(s)</em>。</p>

<h3 id="content_2_3">性质</h3>
<p>算法1生成的NFA能够正确地识别正则表达式，并且具有如下的性质：</p>
<ol class="list1" style="padding-left:16px;margin-left:16px"><li><em>N(r)</em>的状态数最多为<em>r</em>中出现的记号和运算符的个数的2倍。</li>
<li><em>N(r)</em>的开始状态和结束状态有且只有一个。</li>
<li><em>N(r)</em>的各个状态对于<em>Σ</em>中的一个符号，或者拥有一个状态迁移，或者拥有最多两个<em>ε</em>迁移。</li></ol>

<h3 id="content_2_4">示例</h3>
<p>利用算法1，根据正则表达式 <em>r=(a|b)*abb</em> 可以生成以下的NFA。</p>
<div class="img_margin" style="text-align:left"><img src="http://tech.idv2.com/wp-content/uploads/2006/09/fig06.png" alt="fig06.png" title="fig06.png" width="732" height="242" /></div>


<h2 id="content_2_5">将NFA转化为DFA</h2>

<h3 id="content_2_6">算法</h3>
<p>使用以下的算法可以将NFA转换成等价的DFA。</p>
<p><strong>算法2</strong> 将NFA转化为DFA</p>
<p><strong>输入</strong> NFA <em>N</em></p>
<p><strong>输出</strong> 能够接受与N相同语言的DFA <em>D</em></p>
<p><strong>方法</strong> 本算法生成D对应的状态迁移表<em>Dtran</em>。DFA的各个状态为NFA的状态集合，
对于每一个输入符号，<em>D</em>模拟<em>N</em>中可能的状态迁移。</p>
<p>定义以下的操作。</p>
<div class="ie5"><table class="style_table" cellspacing="1" border="0"><thead><tr><td class="style_td">操作</td><td class="style_td">说明</td></tr></thead><tbody><tr><td class="style_td"><em>ε-closure(s)</em></td><td class="style_td">从NFA的状态<em>s</em>出发，仅通过<em>ε</em>迁移能够到达的NFA的状态集合</td></tr><tr><td class="style_td"><em>ε-closure(T)</em></td><td class="style_td">从<em>T</em>中包含的某个NFA的状态<em>s</em>出发，仅通过<em>ε</em>迁移能够到达的NFA的状态集合</td></tr><tr><td class="style_td"><em>move(T, a)</em></td><td class="style_td">从<em>T</em>中包含的某个NFA的状态<em>s</em>出发，通过输入符号<em>a</em>迁移能够到达的NFA的状态集合</td></tr></tbody></table></div>
<pre>令 Dstates 中仅包含ε-closure(s), 并设置状态为未标记;
while Dstates中包含未标记的状态T do
begin
  标记T;
  for 各输入记号a do
  begin
    U := ε-closure(move(T, a));
    if U不在Dstates中 then
      将 U 追加到 Dstates 中，设置状态为未标记;
    Dtrans[T, a] := U;
  end
end</pre>
<p>ε-closure(T)的计算方法如下：</p>
<pre>将T中的所有状态入栈;
设置ε-closure(T)的初始值为T;
while 栈非空 do
begin
  从栈顶取出元素t;
  for 从t出发以ε为边能够到达的各个状态u do
    if u不在ε-closure(T)中 then
    begin
      将u追加到ε-closure(T)中;
      将u入栈;
    end
end</pre>

<h3 id="content_2_7">示例</h3>
<p>将上面生成的NFA转化为DFA。</p>
<p>最初，<em>Dstates</em>内仅有ε-closure(0) = A = {0, 1, 2, 4, 7}。然后对于状态<em>A</em>，对于输入记号<em>a</em>，计算
<em>ε-closure(move(A, a))</em> = <em>ε-closure</em>(<em>move</em>({0, 1, 2, 4, 7}, a)) = <em>ε-closure</em>({3, 8}) = {1, 2, 3, 4, 6, 7, 8}，
即 B={1, 2, 3, 4, 6, 7, 8}, <em>Dtran[A, a]=B</em>。
对于状态<em>A</em>，由输入记号<em>b</em>能够到达的仅有4-&gt;5，因此 C = <em>ε-closure</em>({5}) = {1, 2, 4, 5, 6, 7}，
即 <em>Dtran[A, b] = C</em>。</p>
<p>以此类推，可得到以下的状态和<em>Dtran</em>。</p>
<pre>A = {0, 1, 2, 4, 7}          D = {1, 2, 4, 5, 6, 7, 9}
B = {1, 2, 3, 4, 6, 7, 8}    E = {1, 2, 4, 5, 6, 7, 10}
C = {1, 2, 4, 5, 6, 7}</pre>
<div class="ie5"><table class="style_table" cellspacing="1" border="0"><tbody><tr><td class="style_td" rowspan="2">状态</td><td class="style_td" colspan="2">输入符号</td></tr><tr><td class="style_td">a</td><td class="style_td">b</td></tr><tr><td class="style_td">A</td><td class="style_td">B</td><td class="style_td">C</td></tr><tr><td class="style_td">B</td><td class="style_td">B</td><td class="style_td">D</td></tr><tr><td class="style_td">C</td><td class="style_td">B</td><td class="style_td">C</td></tr><tr><td class="style_td">D</td><td class="style_td">B</td><td class="style_td">E</td></tr><tr><td class="style_td">E</td><td class="style_td">B</td><td class="style_td">C</td></tr></tbody></table></div>
<p>由此得出DFA如下图所示。</p>
<div class="img_margin" style="text-align:left"><img src="http://tech.idv2.com/wp-content/uploads/2006/09/fig07.png" alt="fig07.png" title="fig07.png" width="456" height="257" /></div>


<h2 id="content_2_8">NFA和DFA的效率</h2>
<p>给定正则表达式<em>r</em>和输入记号序列<em>x</em>，判断<em>r</em>是否能够接受<em>x</em>。</p>
<p>使用NFA的情况下，
由正则表达式生成NFA的时间复杂度为<em>O(|r|)</em>，另外由于NFA的状态数最多为r的2倍，因此空间复杂度为<em>O(|r|)</em>。
由NFA判断是否接受<em>x</em>时，时间复杂度为<em>O(|r|×|x|)</em>。因此，总体上处理时间与 r、x的长度之积成比例。
这种处理方法在x不是很长时十分有效。</p>
<p>如果使用DFA，由于利用DFA判断是否接受<em>x</em>与状态数无关，因此时间复杂度为<em>O(|x|)</em>。但是DFA的状态数
与正则表达式的长度呈指数关系。例如，正则表达式 <em>(a|b)*a(a|b)(a|b)...(a|b)</em>，尾部有 n-1 个 <em>(a-b)</em>的话，
DFA最小状态数也会超过 2SUP{n}。</p>
<!-- end Pukiwiki generated code-->

]]></description>
			<content:encoded><![CDATA[<!-- begin Pukiwiki generated code--><p>程序编译的第一个阶段是<strong>词法分析</strong>，即把字节流识别为<strong>记号</strong>（token）流，
提供给下一步的<strong>语法分析</strong>过程。而识别记号的方法就是正则表达式的分析。
本文介绍利用<strong>有限自动机</strong>分析表达式的方法。</p>
<!-- end Pukiwiki generated code--><span id="more-79"></span><!-- begin Pukiwiki generated code--><div class="contents">
<a id="contents_4"></a>
<ul class="list1" style="padding-left:16px;margin-left:16px"><li><a href="#content_4_0">  概念</a></li>
<li><a href="#content_4_1">  将正则表达式转换为NFA(Thompson构造法)</a>
<ul class="list2" style="padding-left:16px;margin-left:16px"><li><a href="#content_4_2">  算法</a></li>
<li><a href="#content_4_3">  性质</a></li>
<li><a href="#content_4_4">  示例</a></li></ul></li>
<li><a href="#content_4_5">  将NFA转化为DFA</a>
<ul class="list2" style="padding-left:16px;margin-left:16px"><li><a href="#content_4_6">  算法</a></li>
<li><a href="#content_4_7">  示例</a></li></ul></li>
<li><a href="#content_4_8">  NFA和DFA的效率</a></li></ul>
</div>

<hr class="full_hr" />
<h2 id="content_4_0">概念</h2>
<dl class="list1" style="padding-left:16px;margin-left:16px"><dt>记号</dt>
<dd>有字母表中的符号组成的有限长度的序列。记号s的<strong>长度</strong>记为<strong>|s|</strong>。
长度为0的记号称为<strong>空记号</strong>，记为<strong>ε</strong>。</dd></dl>
<dl class="list1" style="padding-left:16px;margin-left:16px"><dt>有限自动机(Finite State Automaton)</dt>
<dd>为研究某种计算过程而抽象出的计算模型。
拥有有限个状态，根据不同的输入每个状态可以迁移到其他的状态。</dd></dl>
<dl class="list1" style="padding-left:16px;margin-left:16px"><dt>非确定有限自动机(Nondeterministic Finite Automaton)</dt>
<dd>简称<strong>NFA</strong>，由以下元素组成：</dd>

<dd>1. 有限状态集合<em>S</em>；</dd>

<dd>2. 有限输入符号的字母表<em>Σ</em>；</dd>

<dd>3. 状态转移函数<em>move</em>；</dd>

<dd>4. 开始状态 <em>sSUB{0}</em>；</dd>

<dd>5. 结束状态集合<em>F</em>，<em>F ∈ S</em>。</dd>

<dd>自动机初始状态为<em>sSUB{0}</em>，逐一读入输入字符串中的每一个字母，根据当前状态、读入的字母，
由状态转移函数<em>move</em>控制进入下一个状态。如果输入字符串读入结束时自动机的状态属于结束状态集合<em>F</em>，
则说明该自动机接受该字符串，否则为不接受。</dd></dl>
<dl class="list1" style="padding-left:16px;margin-left:16px"><dt>确定有限自动机(Deterministic Finite Automaton)</dt>
<dd>简称<strong>DFA</strong>，是NFA的一种特例，
有以下两条限制：</dd>

<dd>1. 对于空输入<em>ε</em>，状态不发生迁移；</dd>

<dd>2. 某个状态对于每一种输入最多只有一种状态转移。</dd></dl>

<h2 id="content_4_1">将正则表达式转换为NFA(Thompson构造法)</h2>

<h3 id="content_4_2">算法</h3>
<p><strong>算法1</strong> 将正则表达式转换为NFA(Thompson构造法)</p>
<p><strong>输入</strong> 字母表Σ上的正则表达式r</p>
<p><strong>输出</strong> 能够接受<em>L(r)</em>的NFA <em>N</em></p>
<p><strong>方法</strong> 首先将构成<em>r</em>的各个元素分解，对于每一个元素，按照下述<strong>规则1</strong>和<strong>规则2</strong>生成NFA。
<strong>注意</strong>：如果<em>r</em>中记号<em>a</em>出现了多次，那么对于<em>a</em>的每次出现都需要生成一个单独的NFA。</p>
<p>之后依照正则表达式r的文法规则，将生成的NFA按照下述<strong>规则3</strong>组合在一起。</p>
<p><strong>规则1</strong> 对于空记号<em>ε</em>，生成下面的NFA。</p>
<div class="img_margin" style="text-align:left"><img src="http://tech.idv2.com/wp-content/uploads/2006/09/fig01.png" alt="fig01.png" title="fig01.png" width="207" height="42" /></div>

<p><strong>规则2</strong> 对于<em>Σ</em>的字母表中的元素<em>a</em>，生成下面的NFA。</p>
<div class="img_margin" style="text-align:left"><img src="http://tech.idv2.com/wp-content/uploads/2006/09/fig02.png" alt="fig02.png" title="fig02.png" width="207" height="42" /></div>

<p><strong>规则3</strong> 令正则表达式<em>s</em>和<em>t</em>的NFA分别为<em>N(s)</em>和<em>N(t)</em>。</p>
<p>a) 对于<em>s|t</em>，按照以下的方式生成NFA <em>N(s|t)</em>。</p>
<div class="img_margin" style="text-align:left"><img src="http://tech.idv2.com/wp-content/uploads/2006/09/fig03.png" alt="fig03.png" title="fig03.png" width="314" height="124" /></div>

<p>b) 对于<em>st</em>，按照以下的方式生成NFA <em>N(st)</em>。</p>
<div class="img_margin" style="text-align:left"><img src="http://tech.idv2.com/wp-content/uploads/2006/09/fig04.png" alt="fig04.png" title="fig04.png" width="333" height="46" /></div>

<p>c) 对于<em>s*</em>，按照以下的方式生成NFA <em>N(s*)</em>。</p>
<div class="img_margin" style="text-align:left"><img src="http://tech.idv2.com/wp-content/uploads/2006/09/fig05.png" alt="fig05.png" title="fig05.png" width="330" height="150" /></div>

<p>d) 对于<em>(s)</em>，使用<em>s</em>本身的NFA <em>N(s)</em>。</p>

<h3 id="content_4_3">性质</h3>
<p>算法1生成的NFA能够正确地识别正则表达式，并且具有如下的性质：</p>
<ol class="list1" style="padding-left:16px;margin-left:16px"><li><em>N(r)</em>的状态数最多为<em>r</em>中出现的记号和运算符的个数的2倍。</li>
<li><em>N(r)</em>的开始状态和结束状态有且只有一个。</li>
<li><em>N(r)</em>的各个状态对于<em>Σ</em>中的一个符号，或者拥有一个状态迁移，或者拥有最多两个<em>ε</em>迁移。</li></ol>

<h3 id="content_4_4">示例</h3>
<p>利用算法1，根据正则表达式 <em>r=(a|b)*abb</em> 可以生成以下的NFA。</p>
<div class="img_margin" style="text-align:left"><img src="http://tech.idv2.com/wp-content/uploads/2006/09/fig06.png" alt="fig06.png" title="fig06.png" width="732" height="242" /></div>


<h2 id="content_4_5">将NFA转化为DFA</h2>

<h3 id="content_4_6">算法</h3>
<p>使用以下的算法可以将NFA转换成等价的DFA。</p>
<p><strong>算法2</strong> 将NFA转化为DFA</p>
<p><strong>输入</strong> NFA <em>N</em></p>
<p><strong>输出</strong> 能够接受与N相同语言的DFA <em>D</em></p>
<p><strong>方法</strong> 本算法生成D对应的状态迁移表<em>Dtran</em>。DFA的各个状态为NFA的状态集合，
对于每一个输入符号，<em>D</em>模拟<em>N</em>中可能的状态迁移。</p>
<p>定义以下的操作。</p>
<div class="ie5"><table class="style_table" cellspacing="1" border="0"><thead><tr><td class="style_td">操作</td><td class="style_td">说明</td></tr></thead><tbody><tr><td class="style_td"><em>ε-closure(s)</em></td><td class="style_td">从NFA的状态<em>s</em>出发，仅通过<em>ε</em>迁移能够到达的NFA的状态集合</td></tr><tr><td class="style_td"><em>ε-closure(T)</em></td><td class="style_td">从<em>T</em>中包含的某个NFA的状态<em>s</em>出发，仅通过<em>ε</em>迁移能够到达的NFA的状态集合</td></tr><tr><td class="style_td"><em>move(T, a)</em></td><td class="style_td">从<em>T</em>中包含的某个NFA的状态<em>s</em>出发，通过输入符号<em>a</em>迁移能够到达的NFA的状态集合</td></tr></tbody></table></div>
<pre>令 Dstates 中仅包含ε-closure(s), 并设置状态为未标记;
while Dstates中包含未标记的状态T do
begin
  标记T;
  for 各输入记号a do
  begin
    U := ε-closure(move(T, a));
    if U不在Dstates中 then
      将 U 追加到 Dstates 中，设置状态为未标记;
    Dtrans[T, a] := U;
  end
end</pre>
<p>ε-closure(T)的计算方法如下：</p>
<pre>将T中的所有状态入栈;
设置ε-closure(T)的初始值为T;
while 栈非空 do
begin
  从栈顶取出元素t;
  for 从t出发以ε为边能够到达的各个状态u do
    if u不在ε-closure(T)中 then
    begin
      将u追加到ε-closure(T)中;
      将u入栈;
    end
end</pre>

<h3 id="content_4_7">示例</h3>
<p>将上面生成的NFA转化为DFA。</p>
<p>最初，<em>Dstates</em>内仅有ε-closure(0) = A = {0, 1, 2, 4, 7}。然后对于状态<em>A</em>，对于输入记号<em>a</em>，计算
<em>ε-closure(move(A, a))</em> = <em>ε-closure</em>(<em>move</em>({0, 1, 2, 4, 7}, a)) = <em>ε-closure</em>({3, 8}) = {1, 2, 3, 4, 6, 7, 8}，
即 B={1, 2, 3, 4, 6, 7, 8}, <em>Dtran[A, a]=B</em>。
对于状态<em>A</em>，由输入记号<em>b</em>能够到达的仅有4-&gt;5，因此 C = <em>ε-closure</em>({5}) = {1, 2, 4, 5, 6, 7}，
即 <em>Dtran[A, b] = C</em>。</p>
<p>以此类推，可得到以下的状态和<em>Dtran</em>。</p>
<pre>A = {0, 1, 2, 4, 7}          D = {1, 2, 4, 5, 6, 7, 9}
B = {1, 2, 3, 4, 6, 7, 8}    E = {1, 2, 4, 5, 6, 7, 10}
C = {1, 2, 4, 5, 6, 7}</pre>
<div class="ie5"><table class="style_table" cellspacing="1" border="0"><tbody><tr><td class="style_td" rowspan="2">状态</td><td class="style_td" colspan="2">输入符号</td></tr><tr><td class="style_td">a</td><td class="style_td">b</td></tr><tr><td class="style_td">A</td><td class="style_td">B</td><td class="style_td">C</td></tr><tr><td class="style_td">B</td><td class="style_td">B</td><td class="style_td">D</td></tr><tr><td class="style_td">C</td><td class="style_td">B</td><td class="style_td">C</td></tr><tr><td class="style_td">D</td><td class="style_td">B</td><td class="style_td">E</td></tr><tr><td class="style_td">E</td><td class="style_td">B</td><td class="style_td">C</td></tr></tbody></table></div>
<p>由此得出DFA如下图所示。</p>
<div class="img_margin" style="text-align:left"><img src="http://tech.idv2.com/wp-content/uploads/2006/09/fig07.png" alt="fig07.png" title="fig07.png" width="456" height="257" /></div>


<h2 id="content_4_8">NFA和DFA的效率</h2>
<p>给定正则表达式<em>r</em>和输入记号序列<em>x</em>，判断<em>r</em>是否能够接受<em>x</em>。</p>
<p>使用NFA的情况下，
由正则表达式生成NFA的时间复杂度为<em>O(|r|)</em>，另外由于NFA的状态数最多为r的2倍，因此空间复杂度为<em>O(|r|)</em>。
由NFA判断是否接受<em>x</em>时，时间复杂度为<em>O(|r|×|x|)</em>。因此，总体上处理时间与 r、x的长度之积成比例。
这种处理方法在x不是很长时十分有效。</p>
<p>如果使用DFA，由于利用DFA判断是否接受<em>x</em>与状态数无关，因此时间复杂度为<em>O(|x|)</em>。但是DFA的状态数
与正则表达式的长度呈指数关系。例如，正则表达式 <em>(a|b)*a(a|b)(a|b)...(a|b)</em>，尾部有 n-1 个 <em>(a-b)</em>的话，
DFA最小状态数也会超过 2SUP{n}。</p>
<!-- end Pukiwiki generated code-->

]]></content:encoded>
			<wfw:commentRss>http://tech.idv2.com/2006/05/08/parse-regex-with-dfa/feed/</wfw:commentRss>
		<slash:comments>6</slash:comments>
		</item>
		<item>
		<title>编译器对unsigned数值运算进行的优化</title>
		<link>http://tech.idv2.com/2005/03/01/unsigned-number-optimization/</link>
		<comments>http://tech.idv2.com/2005/03/01/unsigned-number-optimization/#comments</comments>
		<pubDate>Tue, 01 Mar 2005 04:03:09 +0000</pubDate>
		<dc:creator>charlee</dc:creator>
				<category><![CDATA[编程开发]]></category>
		<category><![CDATA[compiler]]></category>

		<guid isPermaLink="false">http://charlee.itbdns.com/tech/archive/20.html</guid>
		<description><![CDATA[<!-- begin Pukiwiki generated code--><p>昨天在讨论文档中的某个函数的实现方法的问题，我提议把某个循环计数变量unsigned int改成int，以便使用负值来表示出错信息。结果被人指出，改成int会影响代码效率，因为对这个变量有如下的操作：a = (a+1)%64，如果a为unsigned型，那么编译器会自动进行优化，而对于int型则不会。后来验证了一下果然如此。</p>
<!-- end Pukiwiki generated code--><span id="more-12"></span><!-- begin Pukiwiki generated code--><p>测试环境为cygwin+gcc3.2。测试程序如下：</p>
<pre>1 int main()
2 {
3     int a;
4     unsigned int b;
5     a = 20;
6     b = 21;
7     a = (a + 1) % 64;
8     b = (b + 1) % 64;
9     return 0;
0 }</pre>
<p>编译并查看汇编代码：</p>
<pre>$ gcc test.c
$ gdb a.exe           # 调试执行
(gdb) b main          # 在main函数处设置断点
(gdb) r               # 运行
(gdb) disass          # 反汇编</pre>
<p>结果如下所示：</p>
<pre>; a = 20
movl   $0x14,0xfffffffc(%ebp)
; b = 21
movl   $0x15,0xfffffff8(%ebp)
; a = a + 1
mov    0xfffffffc(%ebp),%edx
inc    %edx
mov    %edx,0xfffffff0(%ebp)
; a = a % 64
mov    0xfffffff0(%ebp),%eax
mov    %eax,0xffffffec(%ebp)
cmpl   $0x0,0xffffffec(%ebp)
jns    0x4010e3 &lt;main+67&gt;
addl   $0x3f,0xffffffec(%ebp)
mov    0xffffffec(%ebp),%eax      ; &lt;&lt; here is &lt;main+67&gt;
sar    $0x6,%eax
mov    %eax,0xfffffffc(%ebp)
mov    0xfffffffc(%ebp),%eax
shl    $0x6,%eax
mov    0xfffffff0(%ebp),%edx
sub    %eax,%edx
mov    %edx,%eax
mov    %eax,0xfffffffc(%ebp)
; a = a + 1
mov    0xfffffff8(%ebp),%eax
inc    %eax
; a = a % 64
and    $0x3f,%eax
mov    %eax,0xfffffff8(%ebp)</pre>
<p>可以看出，对于无符号型整数，编译器会自动把 % 64的运算转换成 and 0x3f，而对于符号整数则不会。因此，在进行位运算或者是对2的幂取模时，应当尽量使用无符号整数，或者是在运算之前进行强制类型转换。</p>
<!-- end Pukiwiki generated code-->
]]></description>
			<content:encoded><![CDATA[<!-- begin Pukiwiki generated code--><p>昨天在讨论文档中的某个函数的实现方法的问题，我提议把某个循环计数变量unsigned int改成int，以便使用负值来表示出错信息。结果被人指出，改成int会影响代码效率，因为对这个变量有如下的操作：a = (a+1)%64，如果a为unsigned型，那么编译器会自动进行优化，而对于int型则不会。后来验证了一下果然如此。</p>
<!-- end Pukiwiki generated code--><span id="more-12"></span><!-- begin Pukiwiki generated code--><p>测试环境为cygwin+gcc3.2。测试程序如下：</p>
<pre>1 int main()
2 {
3     int a;
4     unsigned int b;
5     a = 20;
6     b = 21;
7     a = (a + 1) % 64;
8     b = (b + 1) % 64;
9     return 0;
0 }</pre>
<p>编译并查看汇编代码：</p>
<pre>$ gcc test.c
$ gdb a.exe           # 调试执行
(gdb) b main          # 在main函数处设置断点
(gdb) r               # 运行
(gdb) disass          # 反汇编</pre>
<p>结果如下所示：</p>
<pre>; a = 20
movl   $0x14,0xfffffffc(%ebp)
; b = 21
movl   $0x15,0xfffffff8(%ebp)
; a = a + 1
mov    0xfffffffc(%ebp),%edx
inc    %edx
mov    %edx,0xfffffff0(%ebp)
; a = a % 64
mov    0xfffffff0(%ebp),%eax
mov    %eax,0xffffffec(%ebp)
cmpl   $0x0,0xffffffec(%ebp)
jns    0x4010e3 &lt;main+67&gt;
addl   $0x3f,0xffffffec(%ebp)
mov    0xffffffec(%ebp),%eax      ; &lt;&lt; here is &lt;main+67&gt;
sar    $0x6,%eax
mov    %eax,0xfffffffc(%ebp)
mov    0xfffffffc(%ebp),%eax
shl    $0x6,%eax
mov    0xfffffff0(%ebp),%edx
sub    %eax,%edx
mov    %edx,%eax
mov    %eax,0xfffffffc(%ebp)
; a = a + 1
mov    0xfffffff8(%ebp),%eax
inc    %eax
; a = a % 64
and    $0x3f,%eax
mov    %eax,0xfffffff8(%ebp)</pre>
<p>可以看出，对于无符号型整数，编译器会自动把 % 64的运算转换成 and 0x3f，而对于符号整数则不会。因此，在进行位运算或者是对2的幂取模时，应当尽量使用无符号整数，或者是在运算之前进行强制类型转换。</p>
<!-- end Pukiwiki generated code-->
]]></content:encoded>
			<wfw:commentRss>http://tech.idv2.com/2005/03/01/unsigned-number-optimization/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>
