【算法与数据结构】KMP算法讲解(C++与python实现)

KMP算法讲解(C++与python实现)

KMP算法讲解

全称:Knuth-Morris-Pratt算法

KMP算法用于在一个文本串中高效地查找一个模式串的出现位置,它利用了模式串内部的重复性质,在匹配失败时避免不必要的比较,从而提高了匹配效率

步骤及具体操作:

  1. 首先,我们要求出一个PM数组
  2. 利用PM数组获取next数组:next[] = [0]+PM[0:-1]
  3. 遍历主串和模式串:
    1. 如果两串其位置元素相等,主串位置++,模式串位置++
    2. 如果位置不等,模式串利用next数组回溯,直到相等或回到初始位,主串位置++
    3. 当模式串遍历完,代表找到匹配位置
    4. 若模式串未遍历完,主串剩余位置不足以匹配,则在主串中不存在。

PM数组

PM 数组:也称部分匹配表或失配函数,用于 KMP 算法中,在匹配失败时决定模式串向右移动的位置。

如何获取PM数组(从字符串表意分析):
  1. 首先我们明确前后缀的含义,前缀指除去最后一个字符的字符串,后缀指除去最前面一个字符的字符转,如:abab的前缀是aba,后缀是bab。
  2. 那么前后缀的子串又是什么呢?前缀从前往后,后缀从后往前。即aba的子串:a,ab,aba,后缀bab的子串:b,ab,bab
  3. 而PM数组就是在字符串该位置上,其前后缀最大公共子串的长度, 那么abab这个位置上就应该是2,因为最大公共子串是ab
  4. 示例:ababa的PM数组就为[0,0,1,2,3]。
采用代码的形式获取PM数组:
  1. 我们使用j, i两个指针,i指针不回溯,j指针回溯
  2. 在求PM数组的过程中,我们同样要使用动态规划(匹配模式)的思想
  3. i指针指向1, j指针指向0
  4. 当str[i]== str[j] 匹配,i++, j++, PM[i] == j+1 (因为数组下标从0开始);
  5. 当str[i]!= str[j] 不匹配:
    1. 如果j > 0 ,利用已求得的PM数组回溯j指针j = PM[j-1],直到j回到起始位,或者str[i]== str[j] ,则PM[i] = j +1, j++ ; 这一步的思想在于:此时的j前面已经包含部分子串和i当前位置匹配,所以我们不用一一进行判断匹配
    2. 最后i++
  6. 当我们i走到最后一个字符,数组获取完成。

Cpp实现

void getNextArray(string pattern, vector<int> &nextVal, int strLen)
    {
        int i = 1, j = 0;
        while (i < strLen)
        {
            if (pattern[i] == pattern[j])
            {
                nextVal[i] = j + 1;
                j++;
            }
            else
            {
                while (j > 0)
                {
                    j = nextVal[j - 1];
                    if (pattern[i] == pattern[j])
                    {
                        nextVal[i] = j + 1;
                        j++;
                        break;
                    }
                }
            }
            i++;
        }
        nextVal.pop_back();
        nextVal.insert(nextVal.begin(), -1);
    }

next 数组

  • 一个字符串的最长公共匹配值,前缀(除去最后一个字符)和后缀(除去第一个字符)相等的最长子串

  • 如abaa的前缀aba 的子串有a、ab、aba,后缀baa的子串:a、aa、baa。

    很明显,两者没有相等的子串,所以为0。

  • 字符串 “ababa” 对应的 next 数组为:[-1, 0, 0, 1, 2]。

一些补充说明

  • next数组是模式串的PM数组右移一位以后的数组(默认初始位为0)
  • PM数组是:前后缀最大相同数
  • 利用代码寻找PM数组就是,设置一前一后两个指针
    • j为初始位0,i是当前位置
    • s[i]==s[j] n[i] == j+1 i++,j++
    • s[i]!=s[j]
      • j = n[j-1] 直到 s[j] == s[i] 然后同上
      • 找到j = 0仍无法等于,n[i] = 0,j不变,i 后移
  • KMP算法:
    • 回溯的是子串,失配时根据next数组不断回溯,直到回无可回
    • 主串永远向后移动,子串匹配或者回无可回就右移
    • 时间复杂度为O(m+n),m,n分别为两字符串的长度
    • 空间复杂度为O(m),构建了next数组

Leetcode实战演习

基础题例:Leetcode 28. 实现strStr()

python实现
  • 代码(kmp算法代码):
class Solution:
    def strStr(self, haystack: str, needle: str) -> int:
        '''
        使用KMP算法实现
        获取子串的next数组,
        当位置失配时,就跳转到数组指定位置重新开始匹配
        当第一位都第一位都失配,next[0] = -1
        就移动主串的指针,重新开始匹配
        '''
        if not needle:
            return 0
        next = self.getNext(needle)
        #主串起点,begin, 子串指针s son
        b ,s = 0,0
        while len(haystack) - b >= len(needle) - s:
            #判断是否匹配,否则移动
            while haystack[b] != needle[s] and s >= 0:
                s = next[s]
            #移动到匹配的地方了,匹配然后+1
            if haystack[b] == needle[s]:
                s += 1
            # 没有匹配上,回到起点
            else:
                s = 0
            if s == len(needle):
                return b - len(needle) + 1
            b += 1
        return -1
  • 代码2(next数组的寻找)
    def getNext(self,s):
        next = [0]*len(s)
        #设置右左指针
        i ,j = 1, 0
        while i < len(s):
            if s[i] == s[j]:
                next[i] = j + 1
                i += 1
                j += 1
            else:
                while j > 0 :
                    j = next[j-1]
                    if s[i] == s[j]:
                        next[i] = j + 1
                        j += 1
                        break
                i += 1

        #现在只是PM数组,现在要右移一位
        next = [-1] + next[:-1]
        return next
C++实现
class Solution
{
public:
    int strStr(string haystack, string needle)
    {
        // 使用KMP算法来解
        if (needle.size() == 0)
        {
            return -1;
        }
        int ndSize = needle.size();
        vector<int> nextVal(ndSize, 0);
        // 获得nextval数组
        getNextArray(needle, nextVal, ndSize);

        // 初始化遍历位置。
        int hsPos = 0, ndPos = 0;
        int hsSize = haystack.size();
        while (hsSize - hsPos >= ndSize - ndPos && ndPos < ndSize)
        {
            if (haystack[hsPos] == needle[ndPos])
            {
                ndPos++;
            }
            else
            {
                while (ndPos >= 0)
                {
                    if (haystack[hsPos] == needle[ndPos])
                    {
                        ndPos++;
                        break;
                    }
                    else
                    {
                        ndPos = nextVal[ndPos];
                    }
                }
                if (ndPos < 0)
                {
                    ndPos = 0;
                }
            }
            hsPos++;
        }
        if (ndPos == ndSize)
        {
            return hsPos - ndSize;
        }
        return -1;
    }

    void getNextArray(string pattern, vector<int> &nextVal, int strLen)
    {
        int i = 1, j = 0;
        while (i < strLen)
        {
            if (pattern[i] == pattern[j])
            {
                nextVal[i] = j + 1;
                j++;
            }
            else
            {
                while (j > 0)
                {
                    j = nextVal[j - 1];
                    if (pattern[i] == pattern[j])
                    {
                        nextVal[i] = j + 1;
                        j++;
                        break;
                    }
                }
            }
            i++;
        }
        nextVal.pop_back();
        nextVal.insert(nextVal.begin(), -1);
    }
};

<think>好的,我现在需要帮助用户解决关于Dify循环实现的问题。首先,我需要明确用户的具体需求。他们提到“dify 循环实现”或“dify 如何处理循环”,但Dify本身可能没有直接称为“循环”的功能,所以需要结合现有文档中的信息来推断可能的解决方案。 根据用户提供的引用内容,Dify工作流中有迭代节点[^1]。这可能类似于编程中的循环结构,用于重复执行某个任务。例如,在处理列表数据或需要多次执行同一操作时,迭代节点可以派上用场。我需要详细解释迭代节点的配置和使用步骤,确保用户能够理解如何设置循环次数或遍历数据集。 另外,用户可能对如何处理循环中的错误或性能问题感兴趣。引用[2]提到在Dify中处理PEM文件的问题,这可能涉及在循环中正确处理数据格式,避免错误积累。需要提醒用户在循环中注意错误处理和资源管理,确保每次迭代的稳定性。 引用[3]中的例子展示了如何结合FastAPI和Selenium进行实时监控并生成GIF,这可能涉及循环截取浏览器内容。虽然Dify本身可能不直接控制这类循环,但可以通过外部脚本或定时任务触发工作流,模拟循环执行。这部分需要说明如何将外部循环机制与Dify结合使用。 接下来,我需要组织回答结构。首先介绍迭代节点的基本概念和配置步骤,然后讨论循环中的数据处理和错误处理,最后补充外部触发循环的方法。同时,生成相关问题以帮助用户进一步探索,比如优化循环性能或处理复杂数据结构。 需要确保回答符合格式要求,使用正确的中文表达,行内公式用$...$,独立公式用$$...$$,但当前问题不涉及数学公式,所以可能不需要使用。同时,引用标识要自然地添加到相关段落末尾,如、[^2]、[^3]。 最后,检查是否有遗漏点,比如Dify的其他循环相关功能或用户可能遇到的常见问题,确保回答全面且易于理解。</think>在Dify中处理循环逻辑主要通过**迭代节点**实现,以下是具体实现方式和应用场景的解析: ### 一、Dify循环实现机制 Dify通过**工作流设计器**中的迭代节点处理循环需求,其核心原理类似编程中的`for循环`。迭代节点可遍历以下数据类型: - 数组列表:`["A","B","C"]` - 字典集合:`{"key1":"value1", "key2":"value2"}` - 数值范围:通过`range()`函数生成序列 配置示例: ```python # 模拟迭代节点的数据输入 input_data = { "dataset": [1,2,3,4,5], "process_logic": "item * 2" # 对每个元素执行乘以2的操作 } ``` ### 二、迭代节点的关键配置步骤 1. **数据源绑定**:将数组/字典类型变量连接到迭代节点的输入端口 2. **循环变量命名**:设定当前元素的变量名(默认为`item`) 3. **子流程设计**:在迭代节点内部构建需要重复执行的逻辑模块 4. **结果聚合**:通过`outputs`收集所有迭代结果,支持数组或对象格式 $$ \text{总耗时} = \sum_{i=1}^{n}(单次迭代时间_i) + 系统开销 $$ ### 三、循环中的特殊处理 1. **错误中断控制**: - 启用`continueOnError`参数可跳过失败迭代 - 通过`try-catch`模块包裹敏感操作 2. **并行优化**: ```python # 伪代码示例 Parallel.forEach(dataset, lambda item: process(item)) ``` 3. **结果过滤**: ```python filtered = filter(lambda x: x%2==0, processed_results) ``` ### 四、应用场景案例 1. **批量文件处理**:遍历存储桶中的文件列表进行格式转换 2. **数据清洗**:对数据库查询结果集进行逐条校验 3. **API轮询**:定时循环调用第三方接口直到满足特定条件
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值