github.com/qiuhoude/go-web@v0.0.0-20220223060959-ab545e78f20d/algorithm/datastructures/kmp/kmp.go (about)

     1  package kmp
     2  
     3  /*
     4  
     5  kmp算法步骤
     6  1.  生成前缀表 (此处kmp算法是根据阮一峰讲解的kmp算法实现的,可能会与其他kmp算法的求前缀表有所区别)
     7  	阮一峰的方式 是指针指向主串,主串指针可以移动一大段,每次比较都是从子串第一个开始 与 主串指针的位置进行比较
     8  	其他人讲解的 是指针指向模式串, 通过变动模式串的指针,每次从模式串指针的位置开始(不必每次从字串第一个开始),与 主串指针(每次for都只+1)进行比较
     9  2.  统计已匹配字符数
    10  3.  查表,根据公式往下一点:移动位数 = 已匹配的字符数 - 对应的部分匹配值(查表获得)
    11  
    12  
    13  时间复杂度 O(m+n) m是主串的长度, n是匹配串的长度
    14  */
    15  // 匹配主中有多少个子串
    16  func MarchSubstr(mainStr, pattern []rune) int {
    17  	if len(mainStr) < len(pattern) { // 子串大于主肯定没有匹配的
    18  		return 0
    19  	}
    20  	preTab := prefixTable2(pattern) //前缀表
    21  	var (
    22  		mp       = 0 // 主串的下标
    23  		mLen     = len(mainStr)
    24  		sLen     = len(pattern)
    25  		marchCnt = 0 // 已匹配数量
    26  	)
    27  	for mp+sLen <= mLen {
    28  		mCnt := 0 //已匹配的字符个数
    29  		for sp := 0; sp < sLen; sp++ {
    30  			if pattern[sp] != mainStr[mp+sp] {
    31  				break
    32  			}
    33  			mCnt++
    34  		}
    35  		if mCnt == sLen { //说明匹配成功
    36  			//fmt.Printf("匹配成功位置: %d, str:%s \n", mp, string(mainStr[mp:mp+sLen]))
    37  			marchCnt++
    38  			mp++
    39  		} else if mCnt == 0 { // 没有匹配往后移一格
    40  			mp++
    41  		} else {
    42  			// 移动位数 = 已匹配的字符数 - 对应的部分匹配值
    43  			mp += mCnt - preTab[mCnt-1]
    44  		}
    45  	}
    46  	return marchCnt
    47  }
    48  
    49  // 生成前缀表 PrefixTable
    50  func prefixTable(s []rune) []int {
    51  	sLen := len(s)
    52  	table := make([]int, sLen)
    53  	table[0] = 0
    54  	for i := 1; i < sLen; i++ {
    55  		// 表示 [0,i]的子串 中最长公共前后缀的的长度
    56  		table[i] = findMaxCommonPreSufNum(s[:i+1])
    57  		//fmt.Printf("%s=%d\n", string(s[:i+1]), table[i])
    58  	}
    59  	return table
    60  }
    61  
    62  func prefixTable2(s []rune) []int {
    63  	m := len(s)
    64  	nexts := make([]int, m)
    65  	for index := range nexts {
    66  		nexts[index] = -1
    67  	}
    68  
    69  	for i := 1; i < m-1; i++ {
    70  		j := nexts[i-1] // 上一次匹配前缀与后缀匹配最大长度,前缀末尾位置
    71  		// j+1 前缀的末尾位置的后面一位  比如 ababacd 模式串, 这时 i=4时s[i]='a',j=1, j+1的位置就是第2个a
    72  		for s[j+1] != s[i] && j >= 0 {
    73  			// 如果 目s[j+1] 表示最大前缀后一位字符 不等于 当前字符,就进去查找次匹配长度的位置(相当于查找了已经求解过的子问)
    74  			// 看次长度的后面一位 是否相等 当前字符
    75  			j = nexts[j]
    76  		}
    77  
    78  		if s[j+1] == s[i] {
    79  			j += 1
    80  		}
    81  
    82  		nexts[i] = j
    83  	}
    84  	// 将下标 改为长度
    85  	table := make([]int, m)
    86  	for i := range nexts {
    87  		table[i] = 1 + nexts[i]
    88  	}
    89  	//fmt.Println(table)
    90  	return table
    91  }
    92  
    93  // 查找前缀与后缀的共有最大长度
    94  func findMaxCommonPreSufNum(s []rune) int {
    95  	// 此处的思路,每次从最长的进行比较,
    96  	// 可以通过动态规划, 从最短的开始比较进行递推
    97  	length := len(s)
    98  	maxLen := length - 1
    99  	for maxLen > 0 {
   100  		if eqRuneSlice(s[:maxLen], s[length-maxLen:]) {
   101  			return maxLen
   102  		}
   103  		maxLen--
   104  	}
   105  	return 0
   106  }
   107  
   108  // 比较rune切片元素相等情况
   109  func eqRuneSlice(a, b []rune) bool {
   110  	if len(a) != len(b) {
   111  		return false
   112  	}
   113  	for i := 0; i < len(a); i++ {
   114  		if a[i] != b[i] {
   115  			return false
   116  		}
   117  	}
   118  	return true
   119  }