• 数据结构与算法 -- 字符串匹配


    1、BF算法

    public class BF {
         
        public static void main(String[] args) {
            String a = "aaabbaaaccssdd";
            String b = "acc";
            System.out.println(bfFind(a, b));
            String c = "aaab";
            System.out.println(bfFind(a, c));
        }
        
        public static int bfFind(String mainStr, String pattern) {
            char[] mainArray = mainStr.toCharArray();
            char[] patternArray = pattern.toCharArray();
            int i = 0;//主串指针
            int j = 0;//模式串匹配指针
            int p = i;//主串匹配指针
            while(i < mainArray.length-patternArray.length+1 && j < patternArray.length) {
                if(mainArray[p] == patternArray[j]) {//如果主串与模式串对应字符匹配,指针右移
                    p++;
                    j++;
                }else {//如果不匹配,模式串右移一位,重新从头匹配
                    i++;
                    j=0;
                    p=i;
                }
            }
            if(j == patternArray.length) {//如果模式串完全匹配,返回模式串在主串中的起始位置
                return i;
            }else {//否则返回 -1
                return -1;
            }
        }
    }

    2、RK算法

    public class RK {
         
        public static void main(String[] args) {
            String a = "aaabbaaaccssdd";
            String b = "acc";
            System.out.println(rkFind(a, b));
            String c = "aaab";
            System.out.println(rkFind(a, c));
            String d = "";
            System.out.println(rkFind(a, d));
            String e = null;
            System.out.println(rkFind(a, e));
        }
        public static int rkFind(String mainStr, String pattern) {
            if(null == mainStr || null == pattern) {
                return -1;
            }
            char[] mainArray = mainStr.toCharArray();
            char[] patternArray = pattern.toCharArray();
            int mainStrLength = mainStr.length();
            int patternLength = pattern.length();
            int[] charHash = new int[mainStrLength];//主串各个字符对应的hash值
            int[] matchStrHash = new int[mainStrLength-patternLength+1];//主串待比较的字符串对应的hash值
            int patternHash = 0;//模式串hash值
            for(int i=0; i<mainStrLength; i++) {//计算主串各个字符对应的hash值,'a'-0,'b'-1,......依此类推
                charHash[i] = mainArray[i] - 'a';
            }
            for(int i=0; i < mainStrLength-patternLength+1; i++) {//计算主串待比较的字符串对应的hash值
                for(int j=0; j<patternLength; j++) {
                    matchStrHash[i] += charHash[i+j];//使用各个字符的hash值相加得到主串待比较的字符串对应的hash值
                }
            }
            for(int i=0; i<patternLength; i++) {//计算模式串hash值
                patternHash += patternArray[i]-'a';
            }
            for(int i=0; i < mainStrLength-patternLength+1; i++) {//主串和模式串hash值比较
                if(patternHash == matchStrHash[i]) {
                    //如果主串和模式串hash值相等,则继续比较字符串是否完全匹配,避免hash冲突
                    int j=0;
                    for(; j<patternLength; j++) {
                        if(mainArray[i+j] != patternArray[j]) {
                            break;
                        }
                    }
                    if(j == patternLength) {
                        return i;
                    }
                }
            }
            return -1;
        }
    }

    3.1、BM算法【仅使用坏字符规则】

    public class BM {
        private static final int SIZE = 256;//全局变量或成员变量
        private void generateBC(char[] b, int m, int[] bc) {
            for(int i=0; i<SIZE; i++) {
                bc[i] = -1;//初始化bc
            }
            for(int i=0; i<m; i++) {
                int ascii = (int)b[i];//计算b[i]的ASCII值
                bc[ascii] = i;//同一个字符取靠后的位置
            }
        }
        
        public int bm(char[] a, int n, char[] b, int m) {
            int[] bc = new int[SIZE];//记录模式串中每个字符最后出现的位置
            generateBC(b, m, bc);//构建坏字符哈希表
            int i = 0;//i表示主串与模式串对齐的第一个字符
            while(i <= n-m) {
                int j;
                for(j = m-1; j >= 0; j--) {//模式串从后往前匹配
                    if(a[i+j] != b[j]) {
                        break;//坏字符对应模式串中的下标是j
                    }
                }
                if(j < 0) {
                    return i;//匹配成功,返回主串与模式串第一个匹配的字符的位置
                }
                //这里等同于将模式串往后滑动j-bc[(int)a[i+j]]位
                i = i + (j - bc[(int)a[i+j]]);
            }
            return -1;
        }
    }

    3.2、BM算法【坏字符规则 +  好后缀规则】

    public class BM {
        private static final int SIZE = 256;//全局变量或成员变量
        private void generateBC(char[] b, int m, int[] bc) {
            for(int i=0; i<SIZE; i++) {
                bc[i] = -1;//初始化bc
            }
            for(int i=0; i<m; i++) {
                int ascii = (int)b[i];//计算b[i]的ASCII值
                bc[ascii] = i;//同一个字符取靠后的位置
            }
        }
        
        public int bm(char[] a, int n, char[] b, int m) {
            int[] bc = new int[SIZE];//记录模式串中每个字符最后出现的位置
            generateBC(b, m, bc);//构建坏字符哈希表
            int[] suffix = new int[m];
            boolean[] prefix = new boolean[m];
            generateGS(b, m, suffix, prefix);
            int i = 0;//i表示主串与模式串对齐的第一个字符
            while(i <= n-m) {
                int j;
                for(j = m-1; j >= 0; j--) {//模式串从后往前匹配
                    if(a[i+j] != b[j]) {
                        break;//坏字符对应模式串中的下标是j
                    }
                }
                if(j < 0) {
                    return i;//匹配成功,返回主串与模式串第一个匹配的字符的位置
                }
                
                int x = j - bc[(int)a[i+j]];
                int y = 0;
                if(j < m-1) {//如果有好后缀的话
                    y = moveByGS(j, m, suffix, prefix);
                }
                i = i + Math.max(x, y);
            }
            return -1;
        }
        //j表示坏字符对应的模式串中的字符下标; m表示模式出长度
        private int moveByGS(int j, int m, int[] suffix, boolean[] prefix) {
            int k = m-1-j;//好后缀长度
            if(suffix[k] != -1) {
                return j - suffix[k] + 1;
            }
            for(int r = j+2; r <= m-1; r++) {
                if(prefix[m-r]) {
                    return r;
                }
            }
            return m;
        }
        
        //b表示模式串,m表示长度,suffix、prefix数组事先申请好了
        private void generateGS(char[] b, int m, int[] suffix, boolean[] prefix) {
            for(int i=0; i<m; i++) {//初始化
                suffix[i] = -1;
                prefix[i] = false;
            }
            for(int i=0; i<m-1; i++) {//b[0, i]
                int j = i;
                int k = 0;//公共后缀子串长度
                while(j >= 0 && b[j] == b[m-1-k]) {//与b[0, m-1]求公共后缀子串
                    j--;
                    k++;
                    suffix[k] = j+1;//j+1表示公共后缀子串在b[0, i]中的起始下标
                }
                if(j == -1) {
                    prefix[k] = true;//如果公共后缀子串也是模式串的前缀子串
                }
            }
        }
    }

    6、Trie树

      Trie树,也叫字典树,是一种专门用于处理字符串匹配树形数据结构,用来解决在一组字符窜集合中快速查找某个字符串的问题,经典使用场景是实现搜索引擎的搜索关键词提示功能。

      Trie树的本质就是利用字符串之间的公共前缀,将重复的前缀合并在一起。Trie树的根节点不包含任何信息,每个节点表示一个字符串中的字符,从根节点到其它节点的每一条路径都表示一个字符串。

      Trie树主要有两个操作,一个是将字符串集合构造成Trie树,具体就是将字符串插入到Trie树的过程,另一个就是在Trie树中查询一个字符串。

      Trie树是一个多叉树,每个节点包含字符值和子节点引用数组。

    public class TrieNode{
        public char data;
        public TrieNode[] children;
        public TrieNode(char data){
            this.data = data;
            children = new TrieNode[n];//根据实际需要确定n
        }
    }    

      如果要在一组字符串中频繁地查询某些字符串,用Trie树会非常高效。构建Trie树时需要扫描所有字符串的所有字符,时间复杂度是O(n)(n表示所有字符串的长度和),但是一旦构建成功之后,后续的查询操作会非常高效。每次查询时,如果要查询的字符串长度是k,那我们就只需要比对大约k个节点就能完成查询操作,所以说,构建好Trie树后,在其中查找字符串的时间复杂度是O(k)(k表示要查找的字符串的长度)

      Trie树是非常耗内存的,是一种空间换时间的思路。Trie树的每个节点都要存储一个指向其子节点引用的数组,并且即使该节点只有很少的子节点,我们也要维护固定长度的数组,如果字符串包含英文大小写字母,数字,甚至是中文,那需要的存储空间就更多了。

    public class TrieTree {
        private TrieNode root = new TrieNode('/');//存储无意义字符
        
        //往Trie树中插入一个字符串
        public void insert(char[] text) {
            TrieNode p = root;
            for(int i=0; i<text.length; i++) {
                int index = text[i] - 'a';
                if(p.children[index] == null) {
                    TrieNode newNode = new TrieNode(text[i]);
                    p.children[index] = newNode;
                }
                p = p.children[index];
            }
            p.isEndingChar = true;
        }
        
        //在Trie树中查找一个字符串
        public boolean find(char[] pattern) {
            TrieNode p = root;
            for(int i=0; i<pattern.length; i++) {
                int index = pattern[i] - 'a';
                if(p.children[index] == null) {
                    return false;//不存在pattern
                }
                p = p.children[index];
            }
            if(p.isEndingChar) {//完全匹配到pattern
                return true;
            }else {//不能完全匹配,只是前缀
                return false;
            }
        }
        public class TrieNode{
            public char data;
            public TrieNode[] children = new TrieNode[26];
            public boolean isEndingChar = false;
            public TrieNode(char data) {
                this.data = data;
            }
        }
        
        public static void main(String[] args) {
            TrieTree trieTree = new TrieTree();
            trieTree.insert("hello".toCharArray());
            trieTree.insert("world".toCharArray());
            trieTree.insert("word".toCharArray());
            trieTree.insert("teacher".toCharArray());
            trieTree.insert("wild".toCharArray());
            String pattern = "word";
            System.out.println(trieTree.find(pattern.toCharArray()) ? "找到了 " + pattern : "没有完全匹配的字符串 " + pattern);
            pattern = "wor";
            System.out.println(trieTree.find(pattern.toCharArray()) ? "找到了 " + pattern : "没有完全匹配的字符串 " + pattern);
        }
    }

     2、利用Trie树实现搜索引擎的搜索关键词提示功能

    import java.util.ArrayList;
    import java.util.List;
    
    public class TrieTree {
        private TrieNode root = new TrieNode('/');//存储无意义字符
        
        //往Trie树中插入一个字符串
        public void insert(char[] text) {
            TrieNode p = root;
            for(int i=0; i<text.length; i++) {
                int index = text[i] - 'a';
                if(p.children[index] == null) {
                    TrieNode newNode = new TrieNode(text[i]);
                    p.children[index] = newNode;
                }
                p = p.children[index];
            }
            p.isEndingChar = true;
        }
        
        //在Trie树中查找一个字符串
        public List<String> find(char[] pattern) {
            TrieNode p = root;
            for(int i=0; i<pattern.length; i++) {
                int index = pattern[i] - 'a';
                if(p.children[index] == null) {
                    return dfsResult;//不存在pattern
                }
                p = p.children[index];
            }
            if(p.isEndingChar) {//完全匹配到pattern
                dfsResult.add(new String(pattern));
                return dfsResult;
            }else {//不能完全匹配,只是前缀
                String startPath = new String(pattern);
                //模式串 pattern的最后一个字符保存在p中,所以传入的path去掉该字符
                dfs(p, new StringBuffer(startPath.substring(0, startPath.length()-1)));
                return dfsResult;
            }
        }
        
        private List<String> dfsResult = new ArrayList<String>();
        private void dfs(TrieNode p, StringBuffer path) {
            if(p.isEndingChar) {
                dfsResult.add(new String(path.append(p.data).toString()));
            }else {
                for(int j=0; j<26; j++) {
                    if(p.children[j] != null) {
                        StringBuffer pathCopy = new StringBuffer(path.toString());
                        dfs(p.children[j], pathCopy.append(p.data));
                    }
                }
            }
        }
        public class TrieNode{
            public char data;
            public TrieNode[] children = new TrieNode[26];
            public boolean isEndingChar = false;
            public TrieNode(char data) {
                this.data = data;
            }
        }
        
        public static void main(String[] args) {
            TrieTree trieTree = new TrieTree();
            trieTree.insert("hello".toCharArray());
            trieTree.insert("world".toCharArray());
            trieTree.insert("word".toCharArray());
            trieTree.insert("teacher".toCharArray());
            trieTree.insert("wild".toCharArray());
            String pattern = "w";
            List<String> findResult = trieTree.find(pattern.toCharArray());
            for(String item : findResult) {
                System.out.println(item);
            }
            System.out.println("------------------");
            
            trieTree.dfsResult.clear();
            pattern = "wor";
            findResult = trieTree.find(pattern.toCharArray());
            for(String item : findResult) {
                System.out.println(item);
            }
            System.out.println("------------------");
            
            trieTree.dfsResult.clear();
            pattern = "word";
            findResult = trieTree.find(pattern.toCharArray());
            for(String item : findResult) {
                System.out.println(item);
            }
            System.out.println("------------------");
        }
    }
  • 相关阅读:
    Go并发编程实战 第2版 PDF (中文版带书签)
    DirectShow 应用开发过程
    Filter 原理
    DirectShow 常用函数总结
    COM 编程基础
    DirectShow 简介
    C++ 静态库与动态库以及在 Windows上 的创建、使用
    DirectShow 学习方法
    Qt 编译配置相关总结
    环境变量对于 VS 有什么用?
  • 原文地址:https://www.cnblogs.com/jiangwangxiang/p/11106262.html
Copyright © 2020-2023  润新知