前綴函數——KMP的本質

前綴函數

我個人覺得 oiwiki 上的學習順序是很合理的,學 KMP 之前先了解前綴函數是非常便于理解的。

前后綴定義

前綴 prefixprefixprefix 指的是從字符串 SSS 的首位到某個位置 iii 的一個子串,這樣的子串寫作 prefix(S,i)prefix(S,i)prefix(S,i)

后綴 suffixsuffixsuffix 指的是從字符串 SSS 的某個位置 iii 到末尾的一個子串,這樣的子串寫作 suffix(S,i)suffix(S,i)suffix(S,i)

SSS真前綴 指的是不等于 SSS 的一個前綴,SSS真后綴 指的是不等于 SSS 的一個后綴。

S=aabS=aabS=aab,那么 aabaabaabSSS 的一個前綴,但不是 SSS 的真前綴,是 SSS 的后綴,但不是 SSS 的真后綴。

前綴函數定義

給定一個長度為 nnn 的字符串 sss,其 前綴函數 寫作 π\piπ ,則 π(i)\pi(i)π(i) 的定義為子串 s[0...i]s[0...i]s[0...i]最長相等真前綴與真后綴 長度。

意思就是

  1. 如果子串 s[0...i]s[0...i]s[0...i] 有一對相等的真前綴與真后綴,那么 π(i)\pi(i)π(i) 就是這個相等的真前綴的長度。
  2. 如果有多對相等的,π(i)\pi(i)π(i) 取最長的一對作為答案。
  3. 如果不存在相等,那么 π(i)=0\pi(i)=0π(i)=0

s=aabbas=aabbas=aabba,則 π(0)=0,π(1)=1,π(2)=0,π(3)=0,π(4)=1\pi(0)=0,\pi(1)=1,\pi(2)=0,\pi(3)=0,\pi(4)=1π(0)=0,π(1)=1,π(2)=0,π(3)=0,π(4)=1

其中 π(0)\pi(0)π(0) 表示字符串 aaa 的最長相等真前綴與真后綴,由于 aaa 長度為 111,所以沒有真前綴,故 π(0)=0\pi(0)=0π(0)=0

其中 π(4)\pi(4)π(4) 表示字符串 aabbaaabbaaabba 的最長相等真前綴與真后綴,答案是 aaa,故 π(4)=1\pi(4)=1π(4)=1

前綴函數的求法

樸素算法

利用雙重循環,第一重循環枚舉 當前子串長度 s[0...i]s[0...i]s[0...i],第二層循環枚舉子串的所有 真前綴的長度,長度從大到小枚舉,并判斷當前真前綴與真后綴是否相同,如果相同的話當前長度就等于 π(i)\pi(i)π(i)

for (int i = 1; i < s.size(); i++) {for (int j = i; j >= 0; j--) {if (s.substr(0, j) == s.substr(i - j + 1, j)) {p[i] = j;break;}}
}

其中 s.substr(pos, len) 是字符串的一個函數,意思是提取出 ssspospospos 位置開始往下數 lenlenlen 個字符的子串,等價于子串 s[pos...pos+len?1]s[pos...pos+len-1]s[pos...pos+len?1],要減 111 是因為從 pospospos 開始,pospospos 也算一個字符。

所以 s.substr(0, j) 表示的是子串 s[0...j?1]s[0...j-1]s[0...j?1]s.substr(i - j + 1, j) 表示的是子串 s[i?j+1,i]s[i-j+1,i]s[i?j+1,i]

該算法的時間復雜度是 O(n3)O(n^3)O(n3)

優化一

當我們求 π(i)\pi(i)π(i) 的時候,我們沒有 充分運用 之前求過的 π\piπ 值。

對于 s[0...i]s[0...i]s[0...i],考慮如何充分利用 π(i?1)\pi(i-1)π(i?1)

  1. π(i?1)=0\pi(i-1)=0π(i?1)=0,說明 π(i)\pi(i)π(i) 的值 至多111。如果 π(i)\pi(i)π(i) 的值大于 111,說明 s[0...i?1]s[0...i-1]s[0...i?1] 的最長相等真前綴與后綴的長度至少為 111,與 π(i?1)=0\pi(i-1)=0π(i?1)=0 矛盾。
  2. π(i?1)≠0\pi(i-1)\neq 0π(i?1)=0,如果 s[i]==s[π(i?1)]s[i]==s[\pi(i-1)]s[i]==s[π(i?1)],那么 π(i)=π(i?1)+1\pi(i)=\pi(i-1)+1π(i)=π(i?1)+1。否則 π(i)\pi(i)π(i) 的大小必然小于等于 π(i?1)\pi(i-1)π(i?1)

不難發現,π(i)\pi(i)π(i)上限至多π(i?1)\pi(i-1)π(i?1)111,所以第二重循環只需要從 π(i?1)+1\pi(i-1)+1π(i?1)+1 枚舉即可。

for (int i = 1; i < s.size(); i++) {for (int j = p[i - 1] + 1; j >= 0; j --) {if (s.substr(0, j) == s.substr(i - j + 1, j)) {p[i] = j;break;}}
}

關于時間復雜度的計算,當我們計算 π(i)\pi(i)π(i) 的時候 多枚舉xxx 次,說明 π(i)\pi(i)π(i) 的值相對于 π(i?1)\pi(i-1)π(i?1) 減少xxx。也就是說 π(i+1)\pi(i+1)π(i+1) 的第二重循環的上限也就 減少xxx

也就是說,多增加的次數,在后續的求解中會被抵消,那么就只剩下了最終至少需要枚舉的第一次。

所以第二重循環的時間就主要在 substr 函數的 O(n)O(n)O(n) 上,故總時間復雜度為 O(n2)O(n^2)O(n2)

優化二

第二重循環從 π(i?1)+1\pi(i-1)+1π(i?1)+1 開始遍歷,每次判定還是依靠了 substr,有沒有不用 substr 的方法?

如果想不用 substr 就能判斷前綴后綴是否相等,說明我們就得跳到 前綴后綴一定相等 的位置。

也就是說當 s[π(i?1)]≠s[i]s[\pi(i-1)]\neq s[i]s[π(i?1)]=s[i] 的時候,我們就得找到一個僅次于 π(i?1)\pi(i-1)π(i?1) 的長度 jjj,使得 s[0...j?1]=s[i?j...i?1]s[0...j-1]=s[i-j...i-1]s[0...j?1]=s[i?j...i?1],如果找到了這樣的 jjj,我們再判斷 s[j]s[j]s[j]s[i]s[i]s[i] 是否相等就行了。

如果相等,說明 π(i)=j\pi(i)=jπ(i)=j,否則我們就找下一個僅次于 jjj 的長度 … 直到 jjj 削減為 000,此時 π(i)=0\pi(i)=0π(i)=0

在這里插入圖片描述

我們可以看到這張圖,當 s[π(i?1)]s[\pi(i-1)]s[π(i?1)]s[i]s[i]s[i] 匹配失敗,我們就要找一個僅次于 π(i?1)\pi(i-1)π(i?1) 的長度 jjj,使之滿足s[0...j?1]=s[i?j...i?1]s[0...j-1]=s[i-j...i-1]s[0...j?1]=s[i?j...i?1],在圖上就是深紅色的兩個位置。

又因為一定有 s[0...p[i?1]?1]=s[i?p[i?1]...i?1]s[0...p[i-1]-1]=s[i-p[i-1]...i-1]s[0...p[i?1]?1]=s[i?p[i?1]...i?1] 成立,這是 π(i?1)\pi(i-1)π(i?1) 的定義,所以可以認為 s[0...p[i?1]?1]s[0...p[i-1]-1]s[0...p[i?1]?1]后綴必然有一個長度為 jjj 的子串 等于 s[i?j...i?1]s[i-j...i-1]s[i?j...i?1]

又因為 s[0...p[i?1]?1]s[0...p[i-1]-1]s[0...p[i?1]?1]前綴必然有一個長度為 jjj 的子串 等于 s[i?j...i?1]s[i-j...i-1]s[i?j...i?1],所以 s[0...p[i?1]?1]s[0...p[i-1]-1]s[0...p[i?1]?1]一對相等的前后綴,其長度為 jjj

所以我們可以得出,下一個長度僅次于 π(i?1)\pi(i-1)π(i?1) 的長度 jjj 等于 π(π(i?1)?1)\pi(\pi(i-1)-1)π(π(i?1)?1)

于是,我們就可以省略掉 substrO(n)O(n)O(n),只需要每次去比較 s[j]s[j]s[j]s[i]s[i]s[i] 是否相等即可。

經過兩次優化,求前綴函數的算法的時間復雜度為 O(n)O(n)O(n)

void getPrifixFunction () {p[0] = 0;for (int i = 1; i < n; i++) {int j = p[i - 1];while (j && s[j] != s[i]) {j = p[j - 1];}if (s[j] == s[i]) j ++;p[i] = j;}}

這串代碼似乎和上面描述的有一些出入,所以這里解釋一下每一句話。

首先 getPrifixFunction 是求前綴函數的函數,前綴函數的第一個值 p[0]=0p[0]=0p[0]=0

然后枚舉所有長度的子串 s[0...i]s[0...i]s[0...i],最初 jjj 是滿足 s[0...j?1]=s[i?j...i?1]s[0...j-1]=s[i-j...i-1]s[0...j?1]=s[i?j...i?1] 的最大長度 π(i?1)\pi(i-1)π(i?1)

然后循環判斷是否 s[j]==s[i]s[j]==s[i]s[j]==s[i],如果不等于那么就往下跳到下一個長度 j=π(j?1)j=\pi(j-1)j=π(j?1)

最后特判一下長度為 111 的情況,因為長度為 111 的時候是 s[0]==s[i]s[0]==s[i]s[0]==s[i],所以 jjj 已經削減到 000 了。

#include <bits/stdc++.h>
using namespace std;
//#pragma GCC optimize(2)
#define int long long
#define endl '\n'
#define PII pair<int,int>
#define INF 1e18
const int N = 1e6 + 7;struct PrifixFunction {int n;string s;vector <int> p;PrifixFunction (int _n, string _s) : s(_s), n(_n), p(_n + 1){}void getPrifixFunction () {p[0] = 0;for (int i = 1; i < n; i++) {int j = p[i - 1];while (j && s[j] != s[i]) {j = p[j - 1];}if (s[j] == s[i]) j ++;p[i] = j;}}};signed main() {}

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/92545.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/92545.shtml
英文地址,請注明出處:http://en.pswp.cn/web/92545.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

解決chrome下載crx文件被自動刪除,加載未打包的擴展程序時提示“無法安裝擴展程序,因為它使用了不受支持的清單版本解決方案”

解決chrome下載crx文件被自動刪除 【chrome設置-隱私與安全-安全瀏覽】&#xff0c;選擇 不保護 【chrome設置-下載內容】&#xff0c;勾選 下載前詢問每個文件的保存位置 下載crx文件時&#xff0c;選擇保存文件夾&#xff0c;將 .crx后綴 改為 .zip后綴&#xff0c;再確定。 …

嵌入式學習day23-shell命令

linux軟件編程學習大綱&#xff1a;1.IO操作文件2.多任務編程3.網絡編程4.數據庫編程5.硬件設備管理學習目標&#xff1a;1.學習接口調用&#xff08;第一層&#xff09;2.軟件操作流程和思想&#xff08;第二層&#xff09;3.軟件設計思想和流程架構&#xff08;第三層&#x…

GPT-5 系列深度詳解:第1章-引言(目錄)

1 引言2 模型數據與訓練3 觀察到的安全挑戰與評估 3.1 從強制拒絕到安全完成 3.2 禁?內容 3.3 拍?屁 3.4 越獄 3.5 指令層級 3.6 幻覺 3.7 欺騙 3.7.1 欺騙思維鏈監控 3.8 圖像輸入 3.9 健康 3.10 多語言性能 3.1.1公平性與偏見&#xff1a; BBQ評估4 紅隊測試與外部評估…

NineData 新增支持 AWS ElastiCache 復制鏈路

2025 年&#xff0c;絕大多數企業已完成業務上云&#xff0c;以獲取更高的彈性、可擴展性和成本效益。AWS ElastiCache 作為 AWS 提供的全托管式內存數據庫服務&#xff0c;已成為許多企業在云上構建高并發、低延遲應用的理想選擇。NineData 數據復制現已全面支持從自建 Redis …

人工智能-python-特征選擇-皮爾遜相關系數

以下是關于特征選擇中常用方法的表格總結&#xff0c;并且詳細闡述了皮爾遜相關系數的原理、計算方法、步驟以及示例。 常用特征選擇方法總結方法原理優點缺點使用場景過濾法&#xff08;Filter Method&#xff09;基于特征的統計信息&#xff08;如相關性、方差等&#xff09;…

LabVIEW多循環架構

?LabVIEW的多循環架構是一種常見的架構&#xff0c;本文Temperature Monitoring.vi 采用 LabVIEW 典型的多循環并行架構&#xff0c;通過功能模塊化設計實現溫度監測全流程&#xff0c;各循環獨立運行又協同工作&#xff0c;構成完整的監測系統。1. 事件處理循環&#xff08;E…

深入理解Maven BOM

一、什么是Maven BOM&#xff1f; 1.1 BOM的基本概念 Maven BOM&#xff08;Bill of Materials&#xff0c;材料清單&#xff09;是一種特殊的POM文件&#xff0c;它主要用于集中管理多個相關依賴的版本。BOM本身不包含任何實際代碼&#xff0c;而是作為一個 版本管理的"參…

Mysql分頁:高效處理海量數據的核心技術

Mysql分頁&#xff1a;高效處理海量數據的核心技術01 引言 在Web應用、移動應用或數據分析場景中&#xff0c;數據庫常常需要處理百萬甚至千萬級的數據記錄。一次性加載所有數據不僅效率低下&#xff0c;還會消耗大量網絡帶寬和內存資源。數據庫分頁技術正是解決這一挑戰的關鍵…

通過 Docker 運行 Prometheus 入門

Promethues 組件 prometheus serverexporteralertmanager 環境準備 Docker 拉取鏡像備用 # https://hub.docker.com/r/prom/prometheus docker pull m.daocloud.io/docker.io/prom/prometheus:main# https://hub.docker.com/r/prom/node-exporter docker pull m.daocloud.io/do…

Java 8特性(一)

目錄 一、Lambda表達式 1、語法格式&#xff1a; &#xff08;1&#xff09;接口名 對象名(參數類型1參數名1,....參數類型n 參數名n)->{方法體;} &#xff08;2&#xff09;參數類型h 參數名n:接口中抽象方法的參數項 &#xff08;3&#xff09;->:表示連接操作 &a…

【代碼隨想錄|232.用棧實現隊列、225.用隊列實現棧、20.有效的括號、1047.刪除字符串中的所有相鄰重復項】

232.用棧實現隊列 timutimtit232. 用棧實現隊列 - 力扣&#xff08;LeetCode&#xff09; class MyQueue { public:stack<int> Sin;stack<int> Sout;MyQueue() {}void push(int x) {Sin.push(x);}int pop() {if (Sout.empty()) { // 出棧為空就把入棧的數導出來w…

碼上爬第三題【協程+瀏覽器調試檢測】

前言&#xff1a;圖靈第三題就是對用戶瀏覽器調試檢測&#xff0c;檢測鼠標右擊打開控制臺&#xff0c;檢測鍵盤按鍵ctrlshifti&#xff0c;從瀏覽器設置打開開發者工具也不行&#xff0c;應該是有瀏覽器寬高檢測的&#xff0c;所以我們保證瀏覽器頁面寬高不變即可。你如果想右…

windows、linux應急響應入侵排查

windows入侵排查 1.1檢查賬號 1.查看服務器是否有弱口令&#xff0c;遠程管理端口是否對公網開放 2.查看服務器是否存在可疑賬號、新增賬號 檢查方法&#xff1a;打開 cmd 窗口&#xff0c;輸入 lusrmgr.msc 命令&#xff0c;查看是否有新增/可疑的賬號&#xff0c;如有管…

11. 為什么要用static關鍵字

11. 為什么要用static關鍵字 static&#xff1a;通常來說&#xff1a;在new一個對象的時候&#xff0c;數據存儲空間才會被分配&#xff0c;方法才能被外界使用。但是有時只想單獨分配一個存儲空間&#xff0c;不考慮需要創建對象或不創建對象&#xff0c;在沒有對象的情況下也…

[Oracle] MAX()和MIN()函數

MAX() 和 MIN() 是 Oracle 常用的聚合函數&#xff0c;用于從一組值中找出最大值和最小值1.MAX()函數MAX()函數返回指定列或表達式中的最大值語法格式MAX(expression)參數說明expression&#xff1a;可以是列名、計算列或表達式示例-- 返回employees表中salary列的最大值 SELEC…

網絡資源模板--基于Android Studio 實現的麻雀筆記App

目錄 一、測試環境說明 二、項目簡介 三、項目演示 四、部設計詳情&#xff08;部分) 添加頁面 五、項目源碼 一、測試環境說明 電腦環境 Windows 11 編寫語言 JAVA 開發軟件 Android Studio (2020) 開發軟件只要大于等于測試版本即可(近幾年官網直接下載也可以)&…

96-基于Flask的酷狗音樂數據可視化分析系統

基于Flask的酷狗音樂數據可視化分析系統 &#x1f4cb; 目錄 項目概述技術棧系統架構功能特性數據庫設計核心代碼實現數據可視化部署指南項目總結 &#x1f3af; 項目概述 本項目是一個基于Flask框架開發的酷狗音樂數據可視化分析系統&#xff0c;旨在為用戶提供音樂數據的…

Java基礎-紅包雨游戲-多線程

目錄 案例要求&#xff1a; 實現思路&#xff1a; 代碼&#xff1a; Employee RedPacket RedPacketRain 總結&#xff1a; 案例要求&#xff1a; 實現思路&#xff1a; 創建一個員工類,id和搶到的金額&#xff0c;創建一個紅包類&#xff0c;里面就是金額&#xff0c;創…

[激光原理與應用-203]:光學器件 - 增益晶體 - 增益晶體的使用方法

增益晶體是激光器的核心元件&#xff0c;其作用是通過受激輻射放大光信號。正確使用增益晶體需綜合考慮晶體選型、光路設計、熱管理、泵浦方式及安全防護等關鍵環節。以下是增益晶體的詳細使用方法及注意事項&#xff1a;一、晶體選型&#xff1a;根據需求匹配參數材料選擇Nd:Y…

?什么是抽象主義人工智能??

什么是抽象主義人工智能&#xff1f; 傳統的人工智能分為符號主義和連接主義兩個派別&#xff0c;后來又增加了行為主義。 我發現符號主義和連接主義處理的都是文本&#xff0c;而不是語義。原來的專家系統是符號主義的產物。現在的大語言模型是連接主義的產物。它們處理的都…