美文网首页
字符串匹配算法

字符串匹配算法

作者: 云飞扬1 | 来源:发表于2020-02-29 21:38 被阅读0次

    1. BF算法

    英文全称:Brute Force,也即暴力匹配算法。主要思路是:遍历主串,逐个子串与模式串进行比较。代码如下(kotlin编写):

    /**
     * 暴力匹配算法
     *
     * @param main 主串
     * @param pattern 模式串
     */
    fun stringMatchBF(main: String, pattern: String): Int {
        var n = main.length
        var m = pattern.length
        if (n < m)
            return -1
        if (n == m) {
            if (main == pattern)
                return 0
            return -1
        }
        //遍历匹配
        var flag: Boolean
        for (i in 0..n - m) {
            flag = true
            for (j in pattern.indices) {
                if (main[i + j] != pattern[j]) {
                    flag = false
                    break
                }
            }
            if (flag) return i
        }
        return -1
    }
    

    时间复杂度:O(n*m),其中 n 为主串长度,m 为模式串长度

    2. RK算法

    根据两位发明者 Rabin 和 Karp 的名字来命名。与 BF 算法其实类似,BF 算法在比较子串与模式串的时候,是遍历比较的,而如果我们算出子串的 hash 值,最后只需要比较子串与模式串的 hash 值是否一致,如果相等则说明找到了,与遍历相比只需要一个 hash 值比较判断就可完成。

    这里的关键是 hash 算法,要非常高效,这样其性能才能好过 BF 算法。

    /**
     * RK算法。为了方便,这里假设字符串只包含英文字符 [a-z]
     * 实际上字符串是任意的,这时我们需要找到一个高效的 hash 算法,并且 hash 冲突相对比较少
     * 
     * @param main 主串
     * @param pattern 模式串
     */
    fun stringMatchRK(main: String, pattern: String): Int {
        var n = main.length
        var m = pattern.length
        if (n < m)
            return -1
        if (n == m) {
            if (main == pattern)
                return 0
            return -1
        }
        //模式串的 hash 值
        var patternHash = 0
        //我们将字符 a-z 分别对应 0-25,每位分别相加
        for (ch in pattern) {
            patternHash += ch - 'a'
        }
        //遍历匹配
        var subStrHash = -1
        for (i in 0..n - m) {
            if (subStrHash == -1) {
                subStrHash = 0
                //计算第一个子串的 hash 值
                for (j in 0 until m) {
                    subStrHash += (main[j] - 'a')
                }
            } else {
                //主串的相邻2个子串,只是头尾字符不一样,这样计算非常节省时间
                subStrHash = subStrHash - (main[i - 1] - 'a') + (main[i + m - 1] - 'a')
            }
            if (subStrHash == patternHash) {
                //这里需要注意,如果产生 hash 冲突,实际上还需要将子串与模式串逐一匹配
                return i
            }
        }
    
        return -1
    }
    

    3. BM算法

    fun findSubStr(mainString: String, patternString: String?): Int {
        if (patternString.isNullOrEmpty()) {
            return -1
        }
        if (mainString.length < patternString.length)
            return -1
        val n = mainString.length
        val m = patternString.length
    
        //构造坏字符hash表,模式串中的每个字符,在模式串中的位置。数组下标对应 ASCII 码值,初始值都为 -1
        //现在只考虑字符集比较少的情况,如果字符串字符集比较大的情况,则不是很适用
        val badCharHashTable = IntArray(256) { -1 }
        //数组下标对应的是好后缀子串 {u} 的长度,对应的值是模式串中与 {u} 相匹配的子串 {u*} 的起始下标值,初始值都为 -1
        val suffix = IntArray(m) { -1 }
        //用来记录模式串的后缀子串是否能够匹配模式串的前缀子串,数组下标对应的是后缀子串 {u} 的长度
        val prefix = BooleanArray(m) { false }
        for (i in patternString.indices) {
            badCharHashTable[patternString[i].toInt()] = i
        }
        //计算 suffix、 prefix 数组的值,逐步将模式串的子串 [0, i] 与模式串进行比较,求得公共后缀子串,i 取值范围[0, m - 1]
        for (i in 0 until m - 1) {
            var j = i
            var k = 0   //公共后缀子串的长度
            //计算公共后缀子串
            while (j >= 0 && patternString[j] == mainString[m - k - 1]) {
                j--
                k++
                suffix[k] = j + 1
            }
            //此时说明该子串既是前缀子串也是后缀子串
            if (j == -1) {
                prefix[k] = true
            }
        }
    
        var i = 0
        //从主串的位置 0 开始匹配,从左往右开始匹配,最多到位置 n-m 处结束
        while (i <= n - m) {
            //从后往前逐一匹配
            var j = m - 1
            while (j >= 0) {
                if (mainString[i + j] != patternString[j]) {
                    //找到坏字符,j 就是主串中坏字符在模式串中对应的位置
                    break
                }
                j--
            }
            //说明整个字符串都匹配成功了,直接找到模式串了
            if (j < 0) {
                return i
            }
    
            //计算模式串匹配往后挪动的距离,坏字符在主串中的位置是 i + j
            //需要找到主串中,是否还有另一个可匹配的字符,没有则为 -1
            var step1 = j - badCharHashTable[mainString[i + j].toInt()]
    
            //如果有好后缀,则可计算通过好后缀可移动多少步
            var step2 = 0
            if (j < m - 1) {
                //好后缀长度
                val goodSuffixLength = m - j - 1
                //说明该好后缀在模式串中能够匹配到其他子串
                if (suffix[goodSuffixLength] != -1) {
                    step2 = j - suffix[goodSuffixLength] + 1
                } else {
                    //好后缀的后缀子串起始位置从 j + 2 开始
                    var r = j + 2
                    while (r <= m -1) {
                        if (prefix[m - r]) {
                            step2 = r
                            break
                        }
                        r++
                    }
                }
                if (step2 == 0)
                    step2 = m
            }
            i += max(step1, step2)
        }
        return -1
    }
    

    4. KMP算法

    fun kmp(mainString: String, patternString: String): Int {
        if (patternString.isNullOrEmpty()) {
            return -1
        }
        if (mainString.length < patternString.length)
            return -1
        val m = patternString.length
        var next = getNext(patternString)
    
        var j = 0
        for (i in mainString.indices) {
            while (j > 0 && mainString[i] != patternString[j]) {
                j = next[j - 1] + 1
            }
            if (mainString[i] == patternString[j]) {
                j++
            }
            if (j == m) {   //找到模式串了
                return i - m + 1
            }
        }
        return -1
    }
    
    //计算 next 数组
    fun getNext(str: String): Array<Int> {
        var m = str.length
        var next = Array(str.length) { -1 }
        next[0] = -1
        var k = -1
        for (i in 1 until m) {
            while (k != -1 && str[k + 1] != str[i]) {
                k = next[k]
            }
            if (str[k + 1] == str[i]) {
                k++
            }
            next[i] = k
        }
        return next
    }
    

    5. AC自动机

    private class AcNode(var data: Char) {
        var children = TreeMap<Char, AcNode>()
        var isEndChar = false
        var length = -1
        var fail: AcNode? = null
    }
    
    class AcTree() {
    
        class MatchInfo(val position: Int, val length: Int)
    
        private val root: AcNode = AcNode('/')
    
        //构建trie树
        fun insert(str: String?) {
            if (str.isNullOrEmpty())
                return
            var p = root
            for (i in str.indices) {
                var childNode = p.children[str[i]]
                if (childNode == null) {
                    childNode = AcNode(str[i])
                    p.children[str[i]] = childNode
                }
                p = childNode
            }
            p.isEndChar = true
            p.length = str.length
        }
    
        //构建失败指针
        fun buildFailPointer() {
            var queue: Queue<AcNode> = LinkedList()
            root.fail = null
            queue.add(root)
            while (queue.isNotEmpty()) {
                var p = queue.remove()
                for (pc in p.children.values) {
                    if (pc == null)
                        continue
                    if (p == root) {
                        pc.fail = root
                    } else {
                        var q = p.fail
                        while (q != null) {
                            var qc = q.children[pc.data]
                            if (qc != null) {
                                pc.fail = qc
                                break
                            }
                            q = q.fail
                        }
                        if (q == null) {
                            pc.fail = root
                        }
                    }
                    queue.add(pc)
                }
            }
        }
    
        //多模式传匹配
        fun match(str: String?): List<MatchInfo>? {
            if (str.isNullOrEmpty())
                return null
            val list = mutableListOf<MatchInfo>()
            var p: AcNode? = root
            for (i in str.indices) {
                while (p!!.children[str[i]] == null && p != root) {
                    p = p.fail
                }
                p = p.children[str[i]]
                if (p == null) {
                    p = root
                }
                var tmp = p
                while (tmp != root) {
                    if (tmp!!.isEndChar) {
                        var pos = i - tmp!!.length + 1
                        list.add(MatchInfo(pos, tmp.length))
                    }
                    tmp = tmp.fail
                }
            }
            return list
        }
    
    }
    

    相关文章

      网友评论

          本文标题:字符串匹配算法

          本文链接:https://www.haomeiwen.com/subject/avvbhhtx.html