算法学习篇3——正则表达式

1.正则表达式概述

正则表达式(Regular Expression)是一种用于描述字符串模式的强大工具,广泛应用于文本搜索、替换、验证等场景。它由普通字符和特殊字符(元字符)组成,能够定义复杂的匹配模式。

2.正则表达式基本语法

2.1 基本元字符

  • . 匹配任意单个字符(除换行符)

  • ^ 匹配字符串开始

  • $ 匹配字符串结束

  • * 匹配前一个字符0次或多次

  • + 匹配前一个字符1次或多次

  • ? 匹配前一个字符0次或1次

  • {n} 匹配前一个字符恰好n次

  • {n,} 匹配前一个字符至少n次

  • {n,m} 匹配前一个字符n到m次

2.2 字符类

  • [abc] 匹配a、b或c中的任意一个

  • [^abc] 匹配除a、b、c外的任意字符

  • [a-z] 匹配任意小写字母

  • \d 匹配数字,等价于[0-9]

  • \w 匹配单词字符,等价于[A-Za-z0-9_]

  • \s 匹配空白字符(空格、制表符、换行符等)

3.正则表达式匹配算法

3.1 非确定性有限自动机(NFA)方法

NFA是最常用的正则表达式实现方式,其特点包括:

  • 每个状态可能有多个转移

  • 允许ε转移(不消耗输入字符的转移)

  • 需要回溯机制

算法步骤:

  1. 将正则表达式转换为NFA

  2. 从初始状态开始,同时跟踪所有可能的状态

  3. 对于每个输入字符,计算可能到达的状态集合

  4. 如果最终状态集合包含接受状态,则匹配成功

3.2 确定性有限自动机(DFA)方法

DFA是另一种实现方式,特点包括:

  • 每个状态对于特定输入只有一个转移

  • 不需要回溯

  • 匹配速度通常比NFA快

算法步骤:

  1. 将正则表达式转换为NFA

  2. 将NFA转换为DFA(使用子集构造法)

  3. 从初始状态开始,根据输入字符进行状态转移

  4. 如果最终到达接受状态,则匹配成功

4.正则表达式引擎实现

4.1 基础递归实现

package main

import (
        "fmt"
)

// 基础正则表达式匹配(递归实现)
func isMatch(text string, pattern string) bool {
        // 如果模式为空,文本也必须为空才匹配
        if len(pattern) == 0 {
                return len(text) == 0
        }

        // 检查第一个字符是否匹配
        firstMatch := len(text) > 0 && (pattern[0] == text[0] || pattern[0] == '.')

        // 处理 '*' 情况
        if len(pattern) >= 2 && pattern[1] == '*' {
                // 两种情况:
                // 1. 忽略 '*' 和它前面的字符(匹配0次)
                // 2. 匹配一个字符后继续尝试匹配
                return isMatch(text, pattern[2:]) || (firstMatch && isMatch(text[1:], pattern))
        }

        // 没有 '*' 的情况,简单递归
        return firstMatch && isMatch(text[1:], pattern[1:])
}

func main() {
        testCases := []struct {
                text    string
                pattern string
                expect  bool
        }{
                {"aa", "a", false},
                {"aa", "a*", true},
                {"ab", ".*", true},
                {"aab", "c*a*b", true},
                {"mississippi", "mis*is*p*.", false},
        }

        for _, tc := range testCases {
                result := isMatch(tc.text, tc.pattern)
                fmt.Printf("text: %q, pattern: %q, result: %v, expect: %v\n", 
                        tc.text, tc.pattern, result, tc.expect)
        }
}

4.2 动态规划实现(更高效)

package main

import (
        "fmt"
)

// 动态规划实现正则表达式匹配
func isMatchDP(text string, pattern string) bool {
        // 创建DP表,dp[i][j]表示text[:i]和pattern[:j]是否匹配
        dp := make([][]bool, len(text)+1)
        for i := range dp {
                dp[i] = make([]bool, len(pattern)+1)
        }

        // 空文本和空模式匹配
        dp[0][0] = true

        // 处理模式如a*, a*b*, a*b*c*等可以与空文本匹配的情况
        for j := 1; j <= len(pattern); j++ {
                if pattern[j-1] == '*' {
                        dp[0][j] = dp[0][j-2]
                }
        }

        for i := 1; i <= len(text); i++ {
                for j := 1; j <= len(pattern); j++ {
                        if pattern[j-1] == '.' || pattern[j-1] == text[i-1] {
                                dp[i][j] = dp[i-1][j-1]
                        } else if pattern[j-1] == '*' {
                                // 两种情况:
                                // 1. 忽略 '*' 和它前面的字符(匹配0次)
                                // 2. 如果前一个字符匹配,则可以匹配多次
                                dp[i][j] = dp[i][j-2] || ((pattern[j-2] == '.' || pattern[j-2] == text[i-1]) && dp[i-1][j])
                        }
                }
        }

        return dp[len(text)][len(pattern)]
}

func main() {
        testCases := []struct {
                text    string
                pattern string
                expect  bool
        }{
                {"aa", "a", false},
                {"aa", "a*", true},
                {"ab", ".*", true},
                {"aab", "c*a*b", true},
                {"mississippi", "mis*is*p*.", false},
                {"aaa", "a*a", true},
                {"ab", ".*c", false},
        }

        for _, tc := range testCases {
                result := isMatchDP(tc.text, tc.pattern)
                fmt.Printf("text: %q, pattern: %q, result: %v, expect: %v\n", 
                        tc.text, tc.pattern, result, tc.expect)
        }
}

4.3 更完整的正则表达式实现(支持更多特性)

package main

import (
        "fmt"
        "unicode/utf8"
)

type state struct {
        out     rune       // 匹配的字符,0表示ε转移
        out1    *state     // 第一个转移状态
        out2    *state     // 第二个转移状态(用于分支)
        lastlist int       // 用于跟踪是否已经处理过
}

type matcher struct {
        start *state
}

// 将正则表达式编译为NFA
func compile(pattern string) *matcher {
        // 这里简化处理,实际实现需要更复杂的解析
        // 仅支持 . * | 和普通字符
        // 实际的正则表达式编译器会更复杂
        
        // 简化实现:假设pattern是"a*b"这样的简单形式
        start := &state{}
        s1 := &state{out: 'a', out1: start} // a*
        s2 := &state{out: 'b'}              // b
        start.out1 = s1
        start.out2 = s2
        
        return &matcher{start: start}
}

// 匹配函数
func (m *matcher) match(text string) bool {
        current := []*state{}
        next := []*state{}
        listid := 0
        
        addState := func(s *state) {
                if s == nil || s.lastlist == listid {
                        return
                }
                s.lastlist = listid
                if s.out == 0 {
                        // ε转移
                        addState(s.out1)
                        addState(s.out2)
                } else {
                        next = append(next, s)
                }
        }
        
        // 从起始状态开始
        addState(m.start)
        current, next = next, current
        
        for _, r := range text {
                listid++
                next = next[:0]
                for _, s := range current {
                        if s.out == '.' || s.out == r {
                                addState(s.out1)
                        }
                }
                current, next = next, current
                if len(current) == 0 {
                        return false
                }
        }
        
        // 检查是否有到达接受状态
        for _, s := range current {
                if s.out1 == nil && s.out2 == nil {
                        return true
                }
        }
        
        return false
}

func main() {
        // 简化测试
        re := compile("a*b")
        fmt.Println("Match 'aab':", re.match("aab")) // true
        fmt.Println("Match 'b':", re.match("b"))     // true
        fmt.Println("Match 'ac':", re.match("ac"))   // false
}

4.4 使用Go标准库regexp

package main

import (
        "fmt"
        "regexp"
)

func main() {
        // 编译正则表达式
        re := regexp.MustCompile(`^a.*b$`)
        
        // 测试匹配
        fmt.Println(re.MatchString("aabb"))  // true
        fmt.Println(re.MatchString("acb"))   // true
        fmt.Println(re.MatchString("ab"))    // true
        fmt.Println(re.MatchString("ba"))    // false
        
        // 查找匹配项
        re = regexp.MustCompile(`\d+`)
        fmt.Println(re.FindAllString("123 abc 456 def", -1)) // [123 456]
        
        // 替换
        re = regexp.MustCompile(`(\d+)-(\d+)`)
        fmt.Println(re.ReplaceAllString("123-456", "$2-$1")) // "456-123"
}

5.正则表达式优化技术

5.1 编译优化

  • 预编译正则表达式

  • 缓存已编译的正则表达式对象

5.2 匹配优化

  • 惰性量词(在量词后加?)

  • 原子分组(减少回溯)

  • 占有量词(避免回溯)

5.3 性能考虑

  • 避免过度使用回溯

  • 使用更具体的字符类

  • 避免嵌套量词

6.总结

正则表达式是文本处理中不可或缺的工具,理解其底层算法有助于编写更高效、更准确的正则表达式。未来随着正则表达式引擎的不断优化,以及与其他文本处理技术(如自然语言处理)的结合,正则表达式将继续在数据处理领域发挥重要作用。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值