编译原理Java实现——DFA最小化

DFA的最小化 也称为 确定的有穷状态机的化简。

 

DFA的最小化 = 消除无用状态 + 合并等价状态

  1. 消除无用状态这里是指删掉那些达到不了的状态。这不是我们的重点,DFS+HashSet不难实现。

  2. 其实关键在于合并等价状态。那么,怎样的两个状态是等价的呢?

状态的等价需要满足两个条件:

  1. 一致性条件:它们都是可接受状态不可接受状态(即都是终态非终态
  2. 蔓延性条件:我们用所有的输入符号进行转化,它们都可以转化到等价的状态

 
 

DFA最小化核心算法:分割法

通俗的说

  1. 先根据终态/非终态分为两组
  2. 然后对同一组中的状态,用所有输入符号去试着转化,如果转化不到一组,则再分
  3. 不断进行上述操作,直到每一组各自的状态集通过任何一个字母都不会转移到其他组

 
 
首先定义DFA类

// DFA类(属性与命名与五元式完全一致)(不熟悉五元式就去查课本啊喂)

public class DFA {

    private List<Integer> K;    // 状态集

    private char[] letters;     // 字母表

    private String[][] f;       // 转换函数

    private int S;    			// 唯一初态

    private List<Integer> Z;    // 终态集

	// setter和getter这里省略
}

 
 

下面是分割法的核心逻辑 ⬇️

 

但其实分割法的实现称不上简单,特别是“分组”这一动作,由人来做很容易很直观,但计算机实现起来会遇到困难——需要借助一个Group类

public class Group {
    public int groupID;					// 该组的唯一ID
    public Set<Integer> stateSet;		// 该组所包含的状态集

    public Group(int groupID, Set<Integer> stateSet) {
        this.groupID = groupID;
        this.stateSet = stateSet;
    }
}

 
 

/**
 *【DFA的最小化】
 * 核心思路:
 *  1. 定义一个Group类,作为「分组」。
 *     Group有两个属性:groupID作为唯一标识;StateSet为该分组包含的状态集
 *  2. separate()方法的作用是,根据某个字母(letter)对分组集合(groupSet)进行彻底分裂
 *  3. 对于字母表中的每个字母,进行separate分裂
 *
 * 下面的论述有利于理解这个算法:
 *  1. HashMap做映射,是该算法的一个关键点。
 *     对于某个字母,一个分组(group)的所有状态(state)根据这个字母,用HashMap记录它们分别会被映射到哪个分组里,据此分裂。
 *     举个例子:{group1,[0, 1]}, {group2,[2, 3]}    (key是组,value是转化后指向该组的所有状态)
 *     ⬆ 0,1状态会转化到1组,2,3状态会转化到2组,据此,旧组分裂为了两个新组,然后删掉旧组,新组入队BFS
 *        如果这个哈希表的size==1,说明所有状态只能转化到一个组中,那么它们是等价的,不用删掉旧组,该组直接进入finalGroupSet最终分组
 *   2. groupID的作用是什么?为什么还要专门维护它?
 *      唯一标识。从1中看出,过程中不断进行着"删掉旧组,生成新组"的行为。维护这个ID主要是为了HashMap做映射
 *
 */
public class minDFA {

    private int cnt = 0;		// 维护Group的唯一ID

    public void minDFA(DFA dfa){
        List<Integer> K = dfa.getK();
        List<Integer> Z = dfa.getZ();
        String[][] f = dfa.getF();
        char[] letters = dfa.getLetters();

        K.removeAll(Z);         // 全部状态集K - 终态集Z = 非终态集
        Group groupx = new Group(cnt++, new HashSet<>(K));
        Group groupy = new Group(cnt++, new HashSet<>(Z));
        Set<Group> finalGroupSet = new HashSet<>();             // 最终分组
        Set<Group> curGroupSet = new HashSet<>();               // 此时的分组
        finalGroupSet.add(groupx);
        finalGroupSet.add(groupy);

        for(char letter : letters){                                 // 对于每个字母
            curGroupSet = finalGroupSet;                            // 【最终分组】不断沦为【此时分组】
            finalGroupSet = separate(curGroupSet, letter, f);       // 【此时分组】又分裂成新的【最终分组】
        }                                                           // 所有字母都用了一次后,成为名副其实的【最终分组】

        // 打印最终分组(每个组中的状态等价)
        for(Group group : finalGroupSet){
            System.out.print(group.groupID);
            System.out.print(group.stateSet);
            System.out.println();
        }
    }

    private Set<Group> separate(Set<Group> groupSet, char letter, String[][] f){
        Set<Group> finalGroupSet = new HashSet<>();
        Set<Group> curGroupSet = groupSet;
        Queue<Group> queue = new LinkedList<>();
        for(Group group : groupSet){
            queue.add(group);
        }

        while (!queue.isEmpty()){
            Group oldGroup = queue.poll();
            Map<Group, List<Integer>> map = new HashMap<>();  //根据指向的组,对状态Integer进行分类
            for(Integer state : oldGroup.stateSet){
                Group stateNextBelong = beLong(state, letter, f, curGroupSet);
                if(!map.containsKey(stateNextBelong)){
                    map.put(stateNextBelong, new ArrayList<>());
                }
                map.get(stateNextBelong).add(state);
            }
            if (map.size() == 1){   // 如果这些状态映射到了一个状态集(Group)中,则为最终分组
                finalGroupSet.add(oldGroup);
            }else{                  // 如果这些状态映射到了多个状态集(Group)中,则删除原先分组,创建多个新分组,并将新分组入队
                curGroupSet.remove(oldGroup);
                for(List<Integer> list : map.values()){
                    Group newGroup = new Group(cnt++, new HashSet<>(list));
                    curGroupSet.add(newGroup);
                    queue.add(newGroup);
                }
            }
        }
        return finalGroupSet;
    }

    /**
     * move方法: 返回唯一后继状态(-1表示没有后继状态)
     */
    private int move(int state, char letter, String[][] f){
        for(int nextState = 0; nextState < f.length; nextState++){
            for(char c : f[state][nextState].toCharArray()){
                if(c == letter){
                    return nextState;
                }
            }
        }
        return -1;
    }

    /**
     * beLong方法: 某状态(state)经过字母(letter)一次转化(move)后,所属于的当前分组(group)
     */
    private Group beLong(int state, char letter, String[][] f, Set<Group> groupSet){
        int newState = move(state, letter, f);
        for(Group group : groupSet){
            if(group.stateSet.contains(newState)){
                return group;
            }
        }
        return null;
    }


}

 

 

 

 

最后,完整代码项目可以戳这里:CSDN GitHub

  • 12
    点赞
  • 41
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值