利用后缀树求多个字符串的最长公共连续子串(Longest Common Substring)

问题:

有N个字符串,计算它们公共的最长连续子串。例如,字符串集合{abcd,bcca,aabc},它们公共的最长连续子串是bc。又如{abcfd,bcabca,aabcf}的公共的最长连续子串是abc。


针对本问题,可以用特殊的字符(该字符必须是N个串中没有出现的字符)将N个字符串连起来形成一个新串,然后构建这个新串的后缀树。比如对字符串集合 {abcd,bcca,aabc},可以连成新串abcd$bcca@aabc%,其中子串之间的分隔符为 '$','@','%'。满足条件的最长子串就是最深的分叉结点,而且该分叉结点下面的后缀的suffix(注意是后缀的后缀!)包含了这三个分隔字符:即至少有一个后缀的suffix为xxx$.....,至少有一个后缀的suffix为yyy@....(这里字符串yyy中没有字符$),至少有一个后缀的suffix为zzz%...(其中字符串zzz中没有$和@字符)。以下代码中,判断分隔字符是否在子树中的函数为 containTerminators()。


利用这种方式生成的后缀树有一个特点:带终结字符的结点都是叶子结点,不可能为内部结点,这一点跟前缀树和不带分隔符的单个子串后缀树是不同的(类似于Ukkonen构造法中末尾添加一个特殊字符将隐式后缀树自动转换成真正的后缀树)。利用这个特点,只需要考虑:非叶子结点的情况,因为满足条件的分叉结点不会是叶子结点。

在下面的实现中,首先采用蛮力法insert()构造后缀树;然后调用findLCS()查找最长公共子串。只需要考虑非叶子结点(child.terminal = false)。findLCS函数返回的是currNode的子树中满足条件的LCS。


实现:

import java.util.LinkedList;
import java.util.List;
 
/**
 * 
 * Find LCS(Longest Common Substring) of N strings (N>=2)
 *  
 * Copyright (c) 2011 ljs (http://blog.csdn.net/ljsspace/)
 * Licensed under GPL (http://www.opensource.org/licenses/gpl-license.php) 
 * 
 * @author ljs
 * 2011-06-29
 *
 */
public class LCS {
	private class SuffixNode {	
		private String key;
	    private List<SuffixNode> children = new LinkedList<SuffixNode>();
	    
	    //use "#" for terminal char
	    private boolean terminal; 
	    	    
	    public SuffixNode(){	    	
	    	this.key = "";
	    }
	    public SuffixNode(String key){
	    	this.key = key;	    	
	    }	    
	    public String toString(){	    	
	    	return this.key + (this.terminal?"#":"") + "(" + children.size() +")";
	    }
	   
	}
	private SuffixNode root;
	private String text;
	//terminators should be ordered according to input strings
	private char[] terminators;
	
	public LCS(String text,char[] terminators){
		this.text = text;
		this.terminators = terminators;
	}
	
	private void insert(SuffixNode currNode,String key,int startIndex) throws Exception{		
		boolean done = false;
		for(int i=0;i<currNode.children.size();i++){
			SuffixNode child = currNode.children.get(i);
			
			//use min(child.key.length, key.length)
			int len = child.key.length()<key.length()?child.key.length():
				key.length();
			int j = 0;
			for(;j<len;j++){
				if(key.charAt(j) != child.key.charAt(j)){
					break;
				}
			}
			if(j==0){//this child doesn't match	any character with the new key			
				//order keys by lexi-order
				if(key.charAt(0)<child.key.charAt(0)){
					//e.g. child="e" (currNode="abc")
					//	   abc                     abc
					//    /  /    =========>      / | /
					//   e    f   insert "c"     c# e  f
				
					SuffixNode node = new SuffixNode(key);
					currNode.children.add(i,node);
					node.terminal = true;	
					done = true;
					break;					
				}else{ //key.charAt(0)>child.key.charAt(0)
					//don't forget to add the largest new key after iterating all children
					continue;
				}
			}else{//current child's key partially matches with the new key; 0<j<=len				
				if(j==len){
					if(key.length()==child.key.length()){
						if(child.terminal){
							throw new Exception("Duplicate Key is found when insertion!");							
						}else{
							//e.g. child="ab"
							//	   ab                    ab#
							//    /  /    =========>    /   /
							//   e    f   insert "ab"  e     f
							child.terminal = true;
						}
					}else if(key.length()>child.key.length()){
						//e.g. child="ab#"
						//	   ab#                    ab#
						//    /  /    ==========>    / | / 							
						//   e    f   insert "abc"  c# e  f		
						String subkey = key.substring(j);
						//recursion
						insert(child,subkey,startIndex+j);
					}else{ //key.length()<child.key.length()
						//e.g. child="abc#"
						//	   abc#                      ab#
						//    /   /      =========>      /   
						//   e     f     insert "ab"    c#    
						//					           /  /
						//                            e    f													
						String childSubkey = child.key.substring(j); //c
						SuffixNode subChildNode = new SuffixNode(childSubkey);
						subChildNode.terminal = child.terminal;
						subChildNode.children = child.children; //inherited from parent
						
						child.key = key;  //ab
						child.terminal = true;  //ab#	
						
						child.children = new LinkedList<SuffixNode>();
						child.children.add(subChildNode);
					}					
				}else{//0<j<len
					//e.g. child="abc#"
					//	   abc#                     ab
					//    /  /     ==========>     / /
					//   e    f   insert "abd"    c#  d# 
					//                           /  /
					//                          e    f					
					//split at j
					String childSubkey = child.key.substring(j);  //c
					String subkey = key.substring(j); //d
					
					SuffixNode subChildNode = new SuffixNode(childSubkey);
					subChildNode.terminal = child.terminal;
					subChildNode.children = child.children; //inherited from parent
					
					//update child's key
					child.key = child.key.substring(0,j);
					//child is not terminal now due to split, it is inherited by subChildNode
					child.terminal = false;
					
					//Note: no need to merge subChildNode					
					
					SuffixNode node = new SuffixNode(subkey);
					node.terminal = true;
					child.children = new LinkedList<SuffixNode>();
					if(subkey.charAt(0)<childSubkey.charAt(0)){
						child.children.add(node);
						child.children.add(subChildNode);
					}else{
						child.children.add(subChildNode);
						child.children.add(node);
					}
				}
				done = true;
				break;
			}
		}
		if(!done){
			SuffixNode node = new SuffixNode(key);		
			node.terminal = true;
			currNode.children.add(node);
		}
	}
	public void insert(String suffix,int startIndex) throws Exception{
		if(suffix == null || suffix.length() == 0) return;
		
		if(root==null){
			root = new SuffixNode();				
		}
		insert(root,suffix,startIndex);		
	}
	
	//build a suffix-tree for a string of text
	public void buildSuffixTree() throws Exception{
		for(int i=0;i<text.length();i++){
			this.insert(text.substring(i), i);
		}		
	}
	
	//for test purpose only
	public void printTree(){
		this.print(0, this.root);
	}
	private void print(int level, SuffixNode node){
		for (int i = 0; i < level; i++) {
            System.out.format(" ");
        }
		System.out.format("|");
        for (int i = 0; i < level; i++) {
        	System.out.format("-");
        }
        if (node.terminal)
        	System.out.format("%s#%n", node.key);
        else
        	System.out.format("%s%n", node.key);
        for (SuffixNode child : node.children) {
        	print(level + 1, child);
        }		
	}
	
	public String findLCS(){
		return findLCS(root);
	}
	//return the longest substring starting with current node (but not including currNode.key)
	private String findLCS(SuffixNode currNode){		
		int maxDepth = currNode.key.length();
		int currDepth = currNode.key.length();
		
		String longestSubstrSuffix = "";
		
		for(int i=0;i<currNode.children.size();i++){
			SuffixNode child = currNode.children.get(i);
			if(!child.terminal){				
				int depth = currDepth + child.key.length();
				
				//terminators are unique, so terminal child is excluded
				boolean containsTerminators = containTerminators(child);
				if(containsTerminators){						
					if(depth > maxDepth){
						maxDepth = depth;
						longestSubstrSuffix =  child.key;
					}
					
					String longestChildSubstrSuffix = findLCS(child);
					if(longestChildSubstrSuffix.length()>0){ //not a part of LCS if longestChildSubstrSuffix's lenght is 0
						int maxChildDepth = longestChildSubstrSuffix.length() + depth;
						if(maxChildDepth > maxDepth){
							maxDepth = maxChildDepth;
							//the substring is relative to currNode
							longestSubstrSuffix = child.key + longestChildSubstrSuffix;
						}
					}
				}				
			}
		}
		
		
		return longestSubstrSuffix;
	}
	private boolean containTerminators(SuffixNode currNode){
		boolean[] done = new boolean[terminators.length];
		return containTerminators(currNode,done);
	}
	private boolean containTerminators(SuffixNode currNode,boolean[] done){
		boolean allDone = false;
		
		for(int i=0;i<currNode.children.size();i++){
			SuffixNode child = currNode.children.get(i);
			if(child.terminal){
				//Note: here the order of terminator is important 
				for(int j=0;j<terminators.length;j++){					
					int pos = child.key.indexOf(terminators[j]);
					if(pos>=0){
						done[j]=true;
						break;
					}
				}
			}else{
				containTerminators(child,done);
			}
			
			allDone = true;
			for(int j=0;j<done.length;j++){
				if(!done[j]){
					allDone = false;
					break;
				}					
			}
			if(allDone)
				break;
		}
		
		return allDone;
	}
	
	public static void main(String[] args) throws Exception {
		System.out.println("****************************");	
		System.out.format("LCS for 3 strings:{abc,bcabca,aabcf}%n");
		String text = "abc$bcabca@aabcf%";
		LCS strie = new LCS(text,new char[]{'

,'@','%'});strie.buildSuffixTree();//strie.printTree();String longestSubstr = strie.findLCS();System.out.format("%s%n", longestSubstr);System.out.println("****************************");System.out.format("LCS for 3 strings:{abcd,bcca,aabc}%n");text = "abcd$bcca@aabc%";strie = new LCS(text,new char[]{'
,'@','%'});strie.buildSuffixTree();//strie.printTree();longestSubstr = strie.findLCS();System.out.format("%s%n", longestSubstr);System.out.println("****************************");System.out.format("LCS for 2 strings:{asdfldkjxlfjax123456789abckljddfdfe123456789ees, xsdldkjalfla123456789abcfleur123456789ljafa}%n");text = "asdfldkjxlfjax123456789abckljddfdfe123456789ees$xsdldkjalfla123456789abcfleur123456789ljafa@";strie = new LCS(text,new char[]{'
,'@'});strie.buildSuffixTree();//strie.printTree();longestSubstr = strie.findLCS();System.out.format("%s%n", longestSubstr);System.out.println("****************************");System.out.format("LCS for 4 strings:{abcd,abce,abd,bdess}%n");text = "abcd$abce@abd%bdess&";strie = new LCS(text,new char[]{'
,'@','%','&'});strie.buildSuffixTree();//strie.printTree();longestSubstr = strie.findLCS();System.out.format("%s%n", longestSubstr);}}

测试输出:

****************************
LCS for 3 strings:{abc,bcabca,aabcf}
abc
****************************
LCS for 3 strings:{abcd,bcca,aabc}
bc
****************************
LCS for 2 strings:{asdfldkjxlfjax123456789abckljddfdfe123456789ees, xsdldkjalfla123456789abcfleur123456789ljafa}
123456789abc
****************************
LCS for 4 strings:{abcd,abce,abd,bdess}
b

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值