‘壹’ !!编译原理DFA和NFA
DFA或NFA是对计算机程序的行为的抽象模型。你编写的程序其实就对应了一个自动机。简单举例来说,如果a,b可以取值0或1; 程序: if(a==1) b=1; 这个程序对应了一个自动机。
对应的自动机就有状态 (0,0), (0,1), (1,1), (1, 0)
比如你自动机的初始状态是 (1,0)即a=1,b=0时,运行程序的下一个状态就是(1,1)。
画图出来就是 这4个状态作为顶点,并且有下面几条边
(0,0) --> (0,0)(自环), (1,0)-->(1,1), (1,1)-->(1,1)(自环), (0,1)-->(0,1)自环
存在的意义就是一种理论模型,也可以认为是一种编程思想。 词法分析系也离不开 if else, 这一系列的if else和条件也就组成自动机。。。
最经典体现自动机思想的算法就是KMP算法,你肯定学过,字符串子串匹配的算法。 回忆这个算法的过程:算法第一步构造的next表(数据结构教材的说法)其实就是根据子串的内容构造了一个自动机! 算法第二步将原串作为自动机输入,自动机的输出就是匹配到的子串位置或者无匹配。
‘贰’ 编译原理NFA转DFA ,请问DFA的初始状态如何确定
NFA确定化的时候,包含NFA初态的那个DFA状态就是确定后的DFA的初态。
DFA的终态就是所有包含了NFA终态的DFA的状态。
对于DFA来说,他的初态就是包含了NFA唯一初态1的那个状态,就是左边的1,2右边的1了。
脱氧核糖-磷酸链在螺旋结构的外面,碱基朝向里面。两条多脱氧核苷酸链反向互补,通过碱基间的氢键形成的碱基配对相连,形成相当稳定的组合。
(2)dfa算法的工作原理扩展阅读:
将DNA或RNA序列以三个核苷酸为一组的密码子转译为蛋白质的氨基酸序列,以用于蛋白质合成。密码子由mRNA上的三个核苷酸(例如ACU,CAG,UUU)的序列组成,每三个核苷酸与特定氨基酸相关。
例如,三个重复的胸腺嘧啶(UUU)编码苯丙氨酸。使用三个字母,可以拥有多达64种不同的组合。由于有64种可能的三联体和仅20种氨基酸,因此认为遗传密码是多余的(或简并的):一些氨基酸确实可以由几种不同的三联体编码。
但每个三联体将对应于单个氨基酸。最后,有三个三联体不编码任何氨基酸,它们代表停止(或无意义)密码子,分别是UAA,UGA和UAG 。
‘叁’ 编译原理由正规式构造DFA
先画出NFA,如图:(我就是传说当中的灵魂画师)
这个DFA本身就已经是最简的了,无法再简化,最简化过程我就直接省了
‘肆’ DFA的最小化算法
首先划分终态集和非终态集,之后不断进行划分,直到不再发生变化。
每轮划分对所有子集进行。对一个子集的划分中,若每个输入符号都能把状态转换到等价的状态,则两个状态等价。
划分完成后,从每个子集选出一个代表,若DFA中存在两个子集内状态之间的转换,则MFA中两个子集的代表之间也存在对应的转换。简便方法:对每个子集删去除代表以外的状态,并把指向它们的箭弧改为指向代表。
MFA的初态是含有DFA初态的子集的代表。MFA的终态集是DFA终态集划分出来子集的代表。
最后,从MFA中删除从初态无法到达的状态和死状态(只有入射弧或指向自身的出射弧的非终止状态)。
去除不可达状态。建表,行列为不同状态,未标记的格子行列状态等价。首先标记行列一个非终止状态一个终止状态的格子。对未标记的格子(q,q'),若存在一个输入符号a,使q经a到达状态和q'经a到达状态不等价,则标记(q,q')。重复直到表格不再变化。
对于所有未标记的(q,q'),把与q'有关的转换都改到q上,删除q'。
‘伍’ dfa算法的关键点是什么
起因: 从网页中爬去的页面,需要判断是否跟预设的关键词匹配(是否包含预设的关键词),并返回所有匹配到的关键词 。
目前pypi 上两个实现
但是其实包都是基于DFA 实现的
这里提供源码如下:
#!/usr/bin/python2.6
# -*- coding: utf-8 -*-
import time
class Node(object):
def __init__(self):
self.children = None
# 标记匹配到了关键词
self.flag = False
# The encode of word is UTF-8
def add_word(root,word):
if len(word) <= 0:
return
node = root
for i in range(len(word)):
if node.children == None:
node.children = {}
node.children[word[i]] = Node()
elif word[i] not in node.children:
node.children[word[i]] = Node()
node = node.children[word[i]]
node.flag = True
def init(word_list):
root = Node()
for line in word_list:
add_word(root,line)
return root
# The encode of word is UTF-8
# The encode of message is UTF-8
def key_contain(message, root):
res = set()
for i in range(len(message)):
p = root
j = i
while (j<len(message) and p.children!=None and message[j] in p.children):
if p.flag == True:
res.add(message[i:j])
p = p.children[message[j]]
j = j + 1
if p.children==None:
res.add(message[i:j])
#print '---word---',message[i:j]
return res
def dfa():
print '----------------dfa-----------'
word_list = ['hello', '民警', '朋友','女儿','派出所', '派出所民警']
root = init(word_list)
message = '四处乱咬乱吠,吓得家中11岁的女儿躲在屋里不敢出来,直到辖区派出所民警赶到后,才将孩子从屋中救出。最后在征得主人同意后,民警和村民合力将这只发疯的狗打死'
x = key_contain(message, root)
for item in x:
print item
if __name__ == '__main__':
dfa()
‘陆’ 如题,编译原理中为什么要将NFA转化为DFA
对DFA来说,一个输入必然对应唯一的路径与结果,而这正是我们设计编译器所需要的。
如果从一个状态经过同样的一个输入可以通过两条或更多路径达到不同的状态,我们的编译器就会迷惑(不知道怎么办),只能通过穷举测试每个状态是否可行,而穷举算法的效率通常都很低下。
DFA的最简化是有固定算法的,NFA有没有我不知道,通常最简化之后的DFA要比NFA简单得多
‘柒’ 基本排序算法原理
算法原理:每次对相邻的两个元素进行比较,若前者大于后者则进行交换,如此一趟下来最后一趟的就是最大元素,重复以上的步骤,除了已经确定的元素 。
算法原理:每次对相邻的两个元素进行比较,若前者大于后者则进行交换,如此一趟下来最后一趟的就是最大元素,重复以上的步骤,除了已经确定的元素
算法步骤
1) 设置两个变量i、j,排序开始的时候:i=0,j=n-1;
2)第一个数组值作为比较值,首先保存到temp中,即temp=A[0];
3)然后j-- ,向前搜索,找到小于temp后,因为s[i]的值保存在temp中,所以直接赋值,s[i]=s[j]
4)然后i++,向后搜索,找到大于temp后,因为s[j]的值保存在第2步的s[i]中,所以直接赋值,s[j]=s[i],然后j--,避免死循环
5)重复第3、4步,直到i=j,最后将temp值返回s[i]中
6) 然后采用“二分”的思想,以i为分界线,拆分成两个数组 s[0,i-1]、s[i+1,n-1]又开始排序
排序图解
算法原理:从第一个元素开始,左边视为已排序数组,右边视为待排序数组,从左往右依次取元素,插入左侧已排序数组,对插入新元素的左侧数组重新生成有序数组 。需要注意的是,在往有序数组插入一个新元素的过程中,我们可以采用按 顺序循环 比较,也可以通过 折半查找法 来找到新元素的位置,两种方式的效率 取决于数组的数据量
算法原理:希尔排序也是利用插入排序的思想来排序。希尔排序通过将比较的全部元素分为几个区域来提升插入排序的性能。这样可以让一个元素可以一次性地朝最终位置前进一大步。然后算法再取越来越小的步长进行排序,算法的最后一步就是普通的插入排序,但是到了这步,需排序的数据几乎是已排好的了,插入效率比较高。
排序图解
选择排序(Selection sort)是一种简单直观的排序算法。它的工作原理如下。首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。选择排序的主要优点与数据移动有关。如果某个元素位于正确的最终位置上,则它不会被移动。选择排序每次交换一对元素,它们当中至少有一个将被移到其最终位置上,因此对n个元素的表进行排序总共进行至多n-1次交换。在所有的完全依靠交换去移动元素的排序方法中,选择排序属于非常好的一种。
归并排序,顾名思义就是一种 “递归合并” 的排序方法(这个理解很重要)。对于一个数列,我们把它进行二分处理,依次递归下去,然后将小范围的数进行排序,最后将其合并在一起。就实现了归并排序。
这实际上是运用了 分治思想 ,显然,想要把一个数列排好序,最终达到的目的就是它的任何一部分都是有序的。这样的话,我们可以考虑分别把数列分成N多个部分,让每个部分分别有序,然后再将其统一,变成所有的东西都有序。这样就实现了排序。这个想法就叫分治思想。
排序图解
排序图解
‘捌’ 编译原理正规式转DFA代码(C#),用窗体的形式显示。谢谢
NFA/DFA算法涉及到词法分析,有穷状态自动机算法,属于计算机领域难度较高的编译原理部分,你还真敢问呀
‘玖’ 正则表达式原理
首先先讲解下正则表达式的基础知识:
1.字符串的组成
对于字符串”123“而言,包括三个字符四个位置。如下图所示:
2.占有字符和零宽度
正则表达式匹配过程中,如果子表达式匹配到东西,而并非是一个位置,并最终保存到匹配的结果当中。这样的就称为占有字符,而只匹配一个位置,或者是匹配的内容并不保存到匹配结果中,这种就称作零宽度,后续会讲到的零宽度断言等。占有字符是互斥的,零宽度是非互斥的。也就是一个字符,同一时间只能由一个子表达式匹配,而一个位置,却可以同时由多个零宽度的子表达式匹配。
3.控制权和传动
正则表达式由左到右依次进行匹配,通常情况下是由一个表达式取得控制权,从字符串的的某个位置进行匹配,一个子表达式开始尝试匹配的位置,是从前一子表达匹配成功的结束位置开始的(例如:(表达式一)(表达式二)意思就是表达式一匹配完成后才能匹配表达式二,而匹配表达式二的位置是从表达式一的位置匹配结束后的位置开始)。如果表达式一是零宽度,那表达式一匹配完成后,表达式二匹配的位置还是原来表达式以匹配的位置。也就是说它匹配开始和结束的位置是同一个。
举一个简单的例子进行说明:正则表达式:123
源数据:123
讲解:首先正则表达式是从最左侧开始进行匹配,也就是位置0处进行匹配,首先得到控制权的是正则表达式中的“1”,而不是源数据中的“1”,匹配源数据中的“1”,匹配成功,将源数据的“1”进行保存到匹配的结果当中,这就表明它占有了一个字符,接下来就将控制权传给正则表达式中的“2”,匹配的位置变成了位置1,匹配源数据中的“2”,匹配成功,将控制权又传动给了正则表达式的“3”,这时候匹配的位置变成了位置2,这时候就会将源数据中的“3”进行匹配。又有正则表达式“3”进行传动控制权,发现已经到了正则表达式的末尾,正则表达式结束。