计算理论导引实验二:构造下推自动机
关于下推自动机的概念,可以从书上或者搜索一下了解的很详细,这里就不再多说,直接转入实验内容。
实验描述
实验的具体要求如下:
形式化定义
根据实验描述,可以将下推自动机进行如下设计,得到此PDA的形式化定义:
1. 状态集为{q1,q2,q3,q4,q5,q6,q7}
2. 输入字母表为{a,b,c}
3. 栈字母表为{b,c,$}
4. 起始状态为q1
5. 接受状态集为{q7}
6. 转移函数如下表所示,表中的空白项表示空集
状态图描述
PS:状态图还是当时实验时画的图片,现在就没有重新再画。
使用draw.io
对状态图进行重画
问题解决
在对PDA进行了解之后,实现过程中需要借助到栈,同时考虑到本实验和实验一中的结构上有些类似,因此在实现过程中,便在实验一的基础上进行了调整。将实现过程中,分为了状态转移类和键盘录入及逻辑处理类。
状态转移关系类
该类主要用于表示转移函数中的各项,代码如下:
public class Old2NewRelation {
private String oldState;//当前状态
private char trans;//读取的字符
private String newState;//新状态
private char top;//栈顶字符
private char putInStack;//要压入栈中的字符
public String getOldState() {
return oldState;
}
public void setOldState(String oldState) {
this.oldState = oldState;
}
public char getTrans() {
return trans;
}
public void setTrans(char trans) {
this.trans = trans;
}
public String getNewState() {
return newState;
}
public void setNewState(String newState) {
this.newState = newState;
}
public Old2NewRelation(String oldState, char trans, String newState, char top, char putInStack) {
super();
this.oldState = oldState;
this.trans = trans;
this.newState = newState;
this.top = top;
this.putInStack = putInStack;
}
/* (非 Javadoc)
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return "状态转移关系 [当前状态:" + oldState + ", 识别的字符:" + trans + ", 转移到的状态:" + newState + ", 对栈的操作是用:" + putInStack
+ "替换栈顶元素:" + top + "]";
}
public char getPutInStack() {
return putInStack;
}
public void setPutInStack(char putInStack) {
this.putInStack = putInStack;
}
public char getTop() {
return top;
}
public void setTop(char top) {
this.top = top;
}
}
键盘录入及逻辑处理类
-
键盘录入
将输入的字符串特殊处理后存储到字符数组中//将字符存入字符数组中 Scanner sc = new Scanner(System.in); String str=sc.next(); str="#"+str;//特殊处理,用来使栈中刚开始识别输入串时栈中栈顶符号为$ char cin[] = str.toCharArray();//利用toCharArray方法转换 sc.close();
-
初始化状态转移关系集合
private static ArrayList<Old2NewRelation> initRelation() { //用‘#’表示空字符 //分别表示 当前状态-识别字符-转移状态-栈顶元素-替换栈顶的元素 ArrayList<Old2NewRelation> list = new ArrayList<Old2NewRelation>(); list.add(new Old2NewRelation("q1",'#',"q2",'#','$')); list.add(new Old2NewRelation("q2",'a',"q4",'#','#')); list.add(new Old2NewRelation("q2",'b',"q2",'#','b')); list.add(new Old2NewRelation("q2",'c',"q3",'b','#')); list.add(new Old2NewRelation("q2",'c',"q6",'#','c')); list.add(new Old2NewRelation("q2",'#',"q7",'$','#')); list.add(new Old2NewRelation("q3",'a',"q4",'#','#')); list.add(new Old2NewRelation("q3",'b',"q2",'#','b')); list.add(new Old2NewRelation("q3",'c',"q3",'b','#')); list.add(new Old2NewRelation("q3",'c',"q6",'#','c')); list.add(new Old2NewRelation("q3",'#',"q7",'$','#')); list.add(new Old2NewRelation("q4",'a',"q4",'#','#')); list.add(new Old2NewRelation("q4",'b',"q5",'c','#')); list.add(new Old2NewRelation("q4",'b',"q2",'#','b')); list.add(new Old2NewRelation("q4",'c',"q3",'b','#')); list.add(new Old2NewRelation("q4",'c',"q6",'#','c')); list.add(new Old2NewRelation("q4",'#',"q7",'$','#')); list.add(new Old2NewRelation("q5",'a',"q4",'#','#')); list.add(new Old2NewRelation("q5",'b',"q5",'c','#')); list.add(new Old2NewRelation("q5",'b',"q2",'#','b')); list.add(new Old2NewRelation("q5",'c',"q6",'#','c')); list.add(new Old2NewRelation("q5",'#',"q7",'$','#')); list.add(new Old2NewRelation("q6",'a',"q4",'#','#')); list.add(new Old2NewRelation("q6",'b',"q5",'c','#')); list.add(new Old2NewRelation("q6",'c',"q6",'#','c')); list.add(new Old2NewRelation("q6",'#',"q7",'$','#')); return list; }
-
从前到后依次遍历读取字符
对于每一个字符都进行下列操作,根据不同的条件,转移到新的状态,并根据实际情况对栈进行出栈或入栈的操作.//开始处理逐个识别字符,bc中需要选择一个进行压栈,此处选择对b进行操作 for (int i = 0; i < cin.length; i++) { char cNow = cin[i]; if (cNow!='#') { System.out.printf("识别第%d个字符%s",i,cNow); System.out.println(); }else{ System.out.println("预制特殊标志$"); System.out.println("------------------------------------"); } isTrave=false; for (Old2NewRelation stateTr : list) { if (isTrave) {//状态发生转移之后,就不再遍历,而是对下一个字符重新开始判断 break; } if(strState.equals(stateTr.getOldState())&&(cNow==stateTr.getTrans())) { //如果栈空或栈顶字符和当前识别的字符一致(stack.empty()栈的特殊处理也归到这一逻辑中) if (stack.empty()||((char)stack.peek())=='$'||((char)stack.peek())==cNow) { if (stateTr.getTop()=='#') { strState=stateTr.getNewState();//状态转移到新状态 System.out.println(stateTr.toString()); isTrave=true; if (stateTr.getPutInStack()!='#') {//不管栈是否为空,只要是非空替换空,就入栈 //非空替换空的状态,也就是要压入栈中一个字符 System.out.println("将其压入栈中"+stateTr.getPutInStack()); System.out.println("------------------------------------"); stack.push(stateTr.getPutInStack()); }else { System.out.println("用空替换空,不对栈进行操作"); System.out.println("------------------------------------"); } } }else {//栈中有元素,且和输入字符不同 strState=stateTr.getNewState();//状态转移到新状态 System.out.println(stateTr.toString()); isTrave=true; if (stateTr.getTop()!='#') { System.out.println("出栈元素为:"+stack.peek()); System.out.println("------------------------------------"); stack.pop(); }else { System.out.println("栈顶从空到空,不对栈进行操作"); System.out.println("------------------------------------"); } } } } }
-
全部输入字符串识别完成后判断处理特殊符号$
isTrave=false; if (!stack.empty()&&((char)stack.peek())=='$') { //根据状态转移函数得到最终处理的状态 for (Old2NewRelation stateTr : list) { if (isTrave) { break; } if(stateTr.getTop()=='$'&&strState.equals(stateTr.getOldState())&&('#'==stateTr.getTrans())) { isTrave=true; strState=stateTr.getNewState();//状态转移到新状态 System.out.println("当前栈顶字符为:"+stack.peek()+"对该标志处理,进行状态转移"); System.out.println(stateTr.toString()); System.out.println("------------------------------------"); } } }else { System.out.println("字符串全部识别后,栈顶字符为:"+stack.peek()+"不是预制的栈标志$"); System.out.println("------------------------------------"); } if (strState==endState) { System.out.println("字符b和c在字符串中出现的次数相同,能被该PDA正确接受,字符串全部识别完成后在最终状态"+strState); } else { System.out.println("字符b和c在字符串中出现的次数不同,被该PDA拒绝,字符串全部识别结束后当前状态为"+strState); }
-
所有处理完成之后,判断当前状态和PDA中的接受状态是否相同,如果相同,表明字符串识别完成后栈中为空并且PDA已经到达了接受状态,这个字符串中b和c出现的次数相同,能够被设计的PDA正确的接受;否则,表明字符串识别完成后栈中非空,并且没有到达设计的PDA的接受状态,这个字符串的b和c出现的次数不同,被设计的PDA拒绝。
测试运行
构造的PDA能够识别的语言L为字符串中b和c出现的次数相同。
输入ccabcbbbca时
当输入字符串为ccabcbbbca时,bc出现的次数相同,符合语言L的定义,此时应该被本次设计的PDA接受。
输入字符串【仅可包含abc】,判断b的数量是否和c的数量相同,相同接受,不同拒绝
ccabcbbbca
PDA的初始状态为:q1结束接收状态为:q7
------------------------------------
预制特殊标志$
------------------------------------
状态转移关系 [当前状态:q1, 识别的字符:#, 转移到的状态:q2, 对栈的操作是用:$替换栈顶元素:#]
将其压入栈中$
------------------------------------
识别第1个字符c
状态转移关系 [当前状态:q2, 识别的字符:c, 转移到的状态:q6, 对栈的操作是用:c替换栈顶元素:#]
将其压入栈中c
------------------------------------
识别第2个字符c
状态转移关系 [当前状态:q6, 识别的字符:c, 转移到的状态:q6, 对栈的操作是用:c替换栈顶元素:#]
将其压入栈中c
------------------------------------
识别第3个字符a
状态转移关系 [当前状态:q6, 识别的字符:a, 转移到的状态:q4, 对栈的操作是用:#替换栈顶元素:#]
栈顶从空到空,不对栈进行操作
------------------------------------
识别第4个字符b
状态转移关系 [当前状态:q4, 识别的字符:b, 转移到的状态:q5, 对栈的操作是用:#替换栈顶元素:c]
出栈元素为:c
------------------------------------
识别第5个字符c
状态转移关系 [当前状态:q5, 识别的字符:c, 转移到的状态:q6, 对栈的操作是用:c替换栈顶元素:#]
将其压入栈中c
------------------------------------
识别第6个字符b
状态转移关系 [当前状态:q6, 识别的字符:b, 转移到的状态:q5, 对栈的操作是用:#替换栈顶元素:c]
出栈元素为:c
------------------------------------
识别第7个字符b
状态转移关系 [当前状态:q5, 识别的字符:b, 转移到的状态:q5, 对栈的操作是用:#替换栈顶元素:c]
出栈元素为:c
------------------------------------
识别第8个字符b
状态转移关系 [当前状态:q5, 识别的字符:b, 转移到的状态:q2, 对栈的操作是用:b替换栈顶元素:#]
将其压入栈中b
------------------------------------
识别第9个字符c
状态转移关系 [当前状态:q2, 识别的字符:c, 转移到的状态:q3, 对栈的操作是用:#替换栈顶元素:b]
出栈元素为:b
------------------------------------
识别第10个字符a
状态转移关系 [当前状态:q3, 识别的字符:a, 转移到的状态:q4, 对栈的操作是用:#替换栈顶元素:#]
用空替换空,不对栈进行操作
------------------------------------
当前栈顶字符为:$对该标志处理,进行状态转移
状态转移关系 [当前状态:q4, 识别的字符:#, 转移到的状态:q7, 对栈的操作是用:#替换栈顶元素:$]
------------------------------------
字符b和c在字符串中出现的次数相同,能被该PDA正确接受,字符串全部识别完成后在最终状态q7
Process finished with exit code 0
输入为cbabcac时
当输入字符串为cbabcac时,bc出现的次数不同,不符合语言L的定义,此时应该被本次设计的PDA拒绝。
输入字符串【仅可包含abc】,判断b的数量是否和c的数量相同,相同接受,不同拒绝
cbabcac
PDA的初始状态为:q1结束接收状态为:q7
------------------------------------
预制特殊标志$
------------------------------------
状态转移关系 [当前状态:q1, 识别的字符:#, 转移到的状态:q2, 对栈的操作是用:$替换栈顶元素:#]
将其压入栈中$
------------------------------------
识别第1个字符c
状态转移关系 [当前状态:q2, 识别的字符:c, 转移到的状态:q6, 对栈的操作是用:c替换栈顶元素:#]
将其压入栈中c
------------------------------------
识别第2个字符b
状态转移关系 [当前状态:q6, 识别的字符:b, 转移到的状态:q5, 对栈的操作是用:#替换栈顶元素:c]
出栈元素为:c
------------------------------------
识别第3个字符a
状态转移关系 [当前状态:q5, 识别的字符:a, 转移到的状态:q4, 对栈的操作是用:#替换栈顶元素:#]
用空替换空,不对栈进行操作
------------------------------------
识别第4个字符b
状态转移关系 [当前状态:q4, 识别的字符:b, 转移到的状态:q2, 对栈的操作是用:b替换栈顶元素:#]
将其压入栈中b
------------------------------------
识别第5个字符c
状态转移关系 [当前状态:q2, 识别的字符:c, 转移到的状态:q3, 对栈的操作是用:#替换栈顶元素:b]
出栈元素为:b
------------------------------------
识别第6个字符a
状态转移关系 [当前状态:q3, 识别的字符:a, 转移到的状态:q4, 对栈的操作是用:#替换栈顶元素:#]
用空替换空,不对栈进行操作
------------------------------------
识别第7个字符c
状态转移关系 [当前状态:q4, 识别的字符:c, 转移到的状态:q6, 对栈的操作是用:c替换栈顶元素:#]
将其压入栈中c
------------------------------------
字符串全部识别后,栈顶字符为:c不是预制的栈标志$
------------------------------------
字符b和c在字符串中出现的次数不同,被该PDA拒绝,字符串全部识别结束后当前状态为q6
Process finished with exit code 0
代码下载及说明
代码下载
该实验的代码已上传到github上,可以 点我下载
说明
该代码针对这一实验完成。遇到其他PDA问题时,需要考虑实际,并进行相应的修改。
PS:代码只是简单实现了逻辑,简单构造解决这一实验描述问题的PDA,只是当时解决问题的一种思路。