天天看點

有窮自動機的簡單使用

請實作一個函數用來比對包括’.’和’‘的正規表達式。模式中的字元’.’表示任意一個字元,而’‘表示它前面的字元可以出現任意次(包含0次)。 在本題中,比對是指字元串的所有字元比對整個模式。例如,字元串”aaa”與模式”a.a”和”ab*ac*a”比對,但是與”aa.a”和”ab*a”均不比對
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Deque;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Queue;
import java.util.Set;
/**
 * @author huangeryu
 * 實作一個函數用來比對包括'.'和'*'的正規表達式,模式中的字元'.'表示任意一個字元,而'*'表示它前面的字元可以出現任意次(包含0次)
 * 思路:利用模式建立不确定性有窮自動機(NFA),再把NFA轉換為确定性有窮自動機(DFA)
 * */
public class Solution 
{
    private char lookahead;
    private char current;
    private static int indexIterator=0;
    public boolean match(char[] str, char[] pattern)
    {
        if(pattern!=null && pattern.length!=0)current=pattern[0];
        //建構NFA
        NFA nfa=new NFA();
        for(int i=1;i<=pattern.length;i++)
        {
            lookahead=(i==pattern.length)?' ':pattern[i];
            nfa.construct(lookahead, current);
            current=lookahead;
        }
        //轉換為DFA
        DFA dfa=new DFA(nfa);
        dfa.construct();
        return dfa.matchDfa(str);
    }
    /**
     * @param alphabet 輸入條件
     * @param next 滿足輸入條件的下一個節點
     * */
    class Director
    {
        private char alphabet;
        private Node next;
        public Director(char alphabet,Node next)
        {
            this.alphabet=alphabet;
            this.next=next;
        }
    }
    /**
     * @param index 節點的名稱
     * @param lists 節點對應的出度邊@see Director
     * @param isEnd 表示節點是否是接受節點,如果是,為true,否則為false
     * */
    class Node
    {
        private final int index;
        private ArrayList<Director> lists;
        private boolean isEnd;
        public Node(int index)
        {
            this.index=index;
            lists=new ArrayList<Director>();
            isEnd=false;
        }

        public int getIndex()
        {
            return this.index;
        }
        @Override
        public int hashCode()
        {
            return this.index;
        }
        @Override
        public boolean equals(Object node)
        {
            if(node instanceof Node)
            {
                Node temp=(Node)node;
                if(temp.index==this.index)return true;
            }
            return false;
        }
    }
    /**
     * @param head 确定有窮自動機的頭節點{@link Node}
     * @param nfa 不确定性有窮自動機{@link NFA}
     * */
    class DFA
    {
        private Node head;
        private NFA nfa;
        public DFA(NFA nfa)
        {
            this.nfa=nfa;
        }
        /**
         * 利用不确定性有窮自動機{@link NFA}構造确定性有窮自動機
         * */
        public void construct()
        {
            Deque<Set<Node>> queue=new ArrayDeque<Set<Node>>();
            Set<Node> start=new HashSet<Node>();
            Map<Set<Node>,Node> map=new HashMap<>();
            start.add(nfa.head);
            Boolean[] bln={false};
            Set<Node> s=nfa.closure_alpha(start, (char)0,bln);
            this.head=new Node(indexIterator++);
            this.head.isEnd=bln[0];
            map.put(s, this.head);
            queue.add(s);
            while(!queue.isEmpty())
            {
                Set<Node> t=queue.poll();
                Node p=map.get(t);
                for(Character c:nfa.set)
                {
                    bln[0]=false;
                    Set<Node> sn=nfa.closure_alpha(t, c.charValue(),bln);
                    if(sn.isEmpty())continue;
                    if(map.containsKey(sn))
                    {
                        p.lists.add(new Director(c.charValue(),map.get(sn)));
//                      System.out.println(p.index+"-->"+map.get(sn).index+" "+c);
                    }
                    else
                    {
                        Node node=new Node(indexIterator++);
                        node.isEnd=bln[0];
                        map.put(sn, node);
                        p.lists.add(new Director(c.charValue(),node));
//                      System.out.println(p.index+"-->"+node.index+" "+c+" "+bln[0]);
                        queue.offer(sn);
                    }
                }
            }
        }
        /**
         * @param str 和模式進行比對的字元串
         * @return 如果比對成功,傳回true,否則,傳回false
         * */
        public boolean matchDfa(char[] str)
        {
            Set<Node>result=new HashSet<Node>();
            result.add(head);
            Set<Node>temp=new HashSet<Node>();
            for(int i=0;i<str.length;i++)
            {
                char c=str[i];
                for(Node np:result)
                {
                    for(Director d:np.lists)
                    {
                        if(d.alphabet==c || d.alphabet=='.')temp.add(d.next);
                    }
                }
                result.clear();
                Set<Node>t=temp;
                temp=result;
                result=t;
            }
            for(Node n:result)
            {
//              System.out.print(n.index+" ");
                if(n.isEnd)return true;
            }
            return false;
        }
    }
    /**
     * @param head 不确定性有窮自動機的頭節點{@link Node}
     * @param last 總是指向尾部節點,為計算友善而引進
     * @param set 節點之間轉移條件的集合{@link Director}
     * */
    class NFA
    {
        private Node head;
        private Node last;
        private HashSet<Character> set;
        public NFA()
        {
            head=new Node(indexIterator++);
            last=head;
            set=new HashSet<Character>();
        }
        /**
         * 利用兩字元來識别正則法則中的s*模式和s.模式
         * @param lookahead 向前看字元
         * @param current 目前字元
         * */
        public void construct(char lookahead,char current)
        {
            if(current!='*')set.add(current);
            if(lookahead==' ')
            {
                if(current=='*')return;
                Node temp=new Node(indexIterator++);
                temp.isEnd=true;
                last.lists.add(new Director(current,temp));
//              System.out.println(last.index+"-->"+temp.index+" "+current);
                last=temp;
            }
            else if(lookahead=='*')
            {
                Node temp1=new Node(indexIterator++);
                Node temp2=new Node(indexIterator++);
                Node temp3=new Node(indexIterator++);
                last.lists.add(new Director((char)0,temp1));
//              System.out.println(last.index+"-->"+temp1.index+" "+0);
                last.lists.add(new Director((char)0,temp2));
//              System.out.println(last.index+"-->"+temp2.index+" "+0);
                temp1.lists.add(new Director(current,temp3));
//              System.out.println(temp1.index+"-->"+temp3.index+" "+current);
                temp3.lists.add(new Director((char) 0,temp1));
//              System.out.println(temp3.index+"-->"+temp1.index+" "+0);
                temp3.lists.add(new Director((char)0,temp2));
//              System.out.println(temp3.index+"-->"+temp2.index+" "+0);
                last=temp2;
            }
            else if(current!='*')
            {
                Node temp1=new Node(indexIterator++);
                last.lists.add(new Director(current,temp1));
//              System.out.println(last.index+"-->"+temp1.index+" "+current);
                last=temp1;
            }
        }
        /**
         * @param set 輸入集合
         * @param alpha 輸入條件{@link NFA}
         * @return 輸入集合在輸入條件下轉移的閉包
         * @param re 如果傳回的閉包中包含終止節點,則re[0]為true,否則false {@link Node}
         * */
        public Set<Node> closure_alpha(Set<Node> set,char alpha,Boolean[] re)
        {
            Set<Node> reSet=new HashSet<Node>();
            if(alpha!=0)
            {
                for(Node n:set)
                {
                    for(Director d:n.lists)
                    {
                        if(d.alphabet==alpha) 
                        {
                            reSet.add(d.next);
                            if(d.next.isEnd && re!=null)re[0]=true;
                        }
                    }
                }
            }
            else
            {
                reSet.addAll(set);
            }
            Queue<Node> queue=new ArrayDeque<Node>(reSet);
            while(!queue.isEmpty())
            {
                Node node=queue.poll();
                for(Director d:node.lists)
                {
                    if(d.alphabet==0)
                    {
                        reSet.add(d.next);
                        if(d.next.isEnd&& re!=null)re[0]=true;
                        queue.offer(d.next);
                    }
                }
            }
            return reSet;
        }
    }

}