Trie树

相关概念

主串

模式串

模式串<=主串

常用算法

BF

原理

我们在主串中,分别检查从0,1,2,....,到n-m位置的,n-m+1个字符串是否匹配。

特点

  1. 暴力匹配算法(最简单,最暴力的匹配算法),也叫朴素匹配算法。
  2. 因为简单,不容易出错,容易实现,所以比较常用,而且现实场景字符串长度并不会很长,而且比较过程中发现不匹配就可以推到主串下一个字符继续匹配了,所以算法的时间复杂度远高于最坏O(m*n)的情况。
  3. 在实际的开发过程中,绝大多数场景下,尤其针对小规模的字符串匹配,朴素的字符串匹配算法就够用了。

时间复杂度

O(m*n)

代码实现

package main

func Bf(ss, s string) (b bool, at int) {
	n := len(ss)
	m := len(s)
	if m == 0 || n == 0 || m > n {
		return b, at
	}
	for i := 0; i < n-m+1; i++ {
		for j := i; j < i+m-1; j++ {
			if ss[j] != s[j-i] {
				break
			}
			b = true
			at = j
			return
		}
	}

	return b, at
}

func Bf2(ss, s string) (b bool, at int) {
	n := len(ss)
	m := len(s)
	if m == 0 || n == 0 || m > n {
		return b, at
	}
	for i := 0; i < n-m+1; i++ {
		if ss[i:i+m] == s {
			b = true
			at = i
			return
		}
	}

	return
}

func main() {
	ss := "abcdjsfjsr"
	s := "jsf"

	b, at := Bf(ss, s)
	println(b)
	println(at)

	b, at = Bf2(ss, s)
	println(b)
	println(at)

}

RK

原理

是对BF算法的一种优化,把每个子串用hash值来代替,模式串也计算出hash值,仅对hash值进行比较,当模式串与子串的hash值相等时,为避免hash冲突而相等时,在针对此子串跟模式串逐个对比一下即可。

难点

hash算法的设计,减少hash冲突。

HASH算法设计

  1. a-z转化为26进制,转化为数字值,然后针对数字比较,这样比较快速。
  2. 把每个元素的26进制,相加,这样比较可以有效避免计算机整数越界问题,但会出现大量hash冲突,比较模式串和子串hash相同后,要在详细逐一对比子串和模式串的每个元素,这样来解决hash冲突的问题。

时间复杂度

整个 RK 算法包含两部分,计算子串哈希值和模式串哈希值与子串哈希值之间的比较。第一部分,我们前面也分析了,可以通过设计特殊的哈希算法(复杂部分可以略过,就用简单的hash字符即可),只需要扫描一遍主串就能计算出所有子串的哈希值了,所以这部分的时间复杂度是 O(n)。(个人觉得此处缺少了计算模式串hash的时间。如果模式串很长的话,也会是一个O(n),但不影响整体时间复杂度的统计。)

模式串哈希值与每个子串哈希值之间的比较的时间复杂度是 O(1),总共需要比较 n-m+1 个子串的哈希值,所以,这部分的时间复杂度也是 O(n)。所以,RK 算法整体的时间复杂度就是 O(n)。

BM

算法思想

我们把模式串与主串匹配的过程,看成是模式串在主串上向后滑动比较的过程,当遇到不匹配的字符,bf和rk的做法是向后滑动一位,然后在从模式串头部开始重新比较,而BM主要是利用一些策略,计算出肯定不匹配的位数,让模式串尽最大位数的向后滑动,这样比较次数大幅减小,效率提高。

如:

主串:g t c d g t v y u 

模式串:g t v

bf和rk做法

第一次比较:c,v不等,向后滑动一位

g t c d g t v y u 

g t v

第二次比较:t,g不等,向后滑动一位

g t c d g t v y u 

   g t v

第三次比较:c,gt,g不等,向后滑动一位

g t c d g t v y u 

     g t v

BM做法:

第一次比较:c,v不等,而且c不在模式串中出现,所以c的位置,跟子串都不匹配,子串移动到c后一位

g t c d g t v y u 

g t v

第二次比较:

g t c d g t v y u 

        g t v

BM算法解析

坏字符规则

  1. 比较顺序,从后往前比较。
  2. 没有好后缀的情况,最后一位就不匹配si,而坏字符在子串的前面xi(如果出现多个去最靠后的那个)。
  3. 模式串相对主串的滑动位数为:si-xi 当前面没有匹配时候,xi取-1,(如果出现多个去最靠后的那个,及xi最大的那个,这样往后移动的位数最小,可以防止错过应有的匹配)。
  4. 但是当有好后缀时候,xi反而比si数值还要大,si-xi 为负值,所以,单有坏字符策略还不够,还要有好后缀策略。

好后缀规则

  1. 在模式串中,查找跟好后缀匹配的另一个子串;
  2. 在好后缀的后缀子串中,查找最长的、能跟模式串前缀子串匹配的后缀子串;

当既有坏字符,也有好后缀时候,取二者最大值,来向后滑动模式串,这就避免了si-xi为负数的场景。

KMP

原理

todo

代码实现

package main

import "fmt"

func KMP(ss, s string) (b bool, at int) {
	n := len(ss)
	m := len(s)

	nexts := Nexts(s)

	j := 0 // 模式串指针
	for i:=0; i<n; i++ {
		for j > 0 && ss[i] != s[j] {
			j = nexts[j-1]+1
		}

		if ss[i] == s[j] {
			if j == m-1 {
				b = true
				at = i-m+1
				return
			}
			j = j+1
		}
	}

	return b, at
}

func Nexts(s string) []int {
	l := len(s)
	next := make([]int, l)
	for k := range next {
		next[k] = -1
	}

	for i:=1;i<l;i++ {
		j := next[i-1]

		for j > -1 && s[j+1] != s[i] {
			j = next[j]
		}

		if s[j+1] == s[i] {
			j = j+1
		}

		next[i] = j
	}

	return next
}


func main() {
	ss := "aacabdfghaacabcdefg"
	s := "aacabc"

	b, at := KMP(ss, s)
	fmt.Println(b)
	fmt.Println(at)
}

复杂度

空间复杂度

O(m) m为模式串长度

时间复杂度

O(m+n) 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值