字符串匹配算法

2020-02-29  本文已影响0人  云飞扬1

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
    }

}
上一篇下一篇

猜你喜欢

热点阅读