日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

字符串相关

發布時間:2023/12/4 编程问答 23 豆豆
生活随笔 收集整理的這篇文章主要介紹了 字符串相关 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

文章目錄

  • 字符串基礎
      • 字符串的存儲
  • 標準庫
  • 字符串匹配
        • 單串匹配
        • 多串匹配
        • 其他類型的字符串匹配問題
  • 字符串哈希
      • Hash 的實現
      • Hash 的分析與改進
        • 錯誤率
        • 多次詢問子串哈希
      • Hash 的應用
        • 字符串匹配
        • 允許 k次失配的字符串匹配
        • 最長回文子串
        • 最長公共子字符串
        • 確定字符串中不同子字符串的數量
  • 字典樹 (Trie)
      • 應用
        • 檢索字符串
        • AC 自動機
        • 維護異或極值
  • AC 自動機
  • Manacher

字符串基礎

字符串的存儲

  • 使用 char 數組存儲,用空字符 \0 表示字符串的結尾。(C 風格字符串)
  • 使用 C++ 標準庫提供的 string 類。
  • 字符串常量可以用字符串字面值(用雙引號括起來的字符串)表示。
  • 標準庫

    C 標準庫是在對字符數組進行操作:char[]/const char*

    代碼作用
    strlen(const char *str)返回從 str[0] 開始直到 ‘\0’ 的字符數。注意,未開啟 O2 優化時,該操作寫在環條件中復雜度是 O(n)O(n)O(n)的。
    printf("%s", s)用 %s 來輸出一個字符串(字符數組)。
    scanf("%s", &s)用 %s 來讀入一個字符串(字符數組)。
    sscanf(const char *__source, const char *__format, …)從字符串 __source 里讀取變量,比如 sscanf(str,"%d",&a)。
    sprintf(char *__stream, const char *__format, …)將 __format 字符串里的內容輸出到 __stream 中,比如 sprintf(str,"%d",i)。
    strcmp(const char *str1, const char *str2)按照字典序比較 str1 str2 若 str1 字典序小返回負值,兩者一樣返回 0,str1 字典序更大則返回正值。請注意,不要簡單的認為返回值只有0 ,1,-1 三種,在不同平臺下的返回值都遵循正負,但并非都是 0,-1,1。
    strcpy(char *str, const char *src)把 src 中的字符復制到 str 中,str src 均為字符數組頭指針,返回值為 str 包含空終止符號 ‘\0’。
    strncpy(char *str, const char *src, int cnt)復制至多 cnt 個字符到 str 中,若 src 終止而數量未達 cnt 則寫入空字符到 str 直至寫入總共 cnt 個字符。
    strcat(char *str1, const char *str2):將 str2 接到 str1 的結尾,用 *str2 替換 str1 末尾的 ‘\0’ 返回 str1。
    strstr(char *str1, const char *str2)若 str2 是 str1 的子串,則返回 str2 在 str1 的首次出現的地址;如果 str2 不是 str1 的子串,則返回 NULL。
    strchr(const char *str, int c)找到在字符串 str 中第一次出現字符 c 的位置,并返回這個位置的地址。如果未找到該字符則返回 NULL。
    strrchr(const char *str, char c)找到在字符串 str 中最后一次出現字符 c 的位置,并返回這個位置的地址。如果未找到該字符則返回 NULL。

    C++ 標準庫是在對字符串對象進行操作,同時也提供對字符數組的兼容。 std::string

    代碼作用
    重載了賦值運算符 +當 + 兩邊是 string/char/char[]/const char* 類型時,可以將這兩個變量連接,返回連接后的字符串(string)。
    賦值運算符 =右側可以是 const string/string/const char*/char*。
    訪問運算符 [cur]返回 cur 位置的引用。
    訪問函數 data()/c_str()返回一個 const char* 指針,內容與該 string 相同。
    容量函數 size()返回字符串字符個數。
    find(ch, start = 0)查找并返回從 start 開始的字符 ch 的位置;rfind(ch) 從末尾開始,查找并返回第一個找到的字符 ch 的位置(皆從 0開始)(如果查找不到,返回 -1)。
    substr(start, len)可以從字符串的 start(從 0開始)截取一個長度為 len 的字符串(缺省 len 時代碼截取到字符串末尾)。
    append(s)將 s 添加到字符串末尾。
    append(s, pos, n)將字符串 s 中,從 pos 開始的 n 個字符連接到當前字符串結尾。
    replace(pos, n, s)刪除從 pos 開始的 n 個字符,然后在 pos 處插入串 s。
    erase(pos, n)刪除從 pos 開始的 n 個字符。
    insert(pos, s)在 pos 位置插入字符串 s。
    std::string重載了比較邏輯運算符,復雜度是 O(n)的。

    字符串匹配

    單串匹配

    一個模式串 (pattern),一個待匹配串,找出前者在后者中的所有出現位置
    舉例:Oulipo HDU - 1686(哈希或KMP)匹配字符串

    多串匹配

    多個模式串,一個待匹配串(多個待匹配串可以直接連起來)。
    直接當做單串匹配肯定是可以的,但是效率不夠高。
    舉例:Keywords Search HDU - 2222(AC自動機模板)

    其他類型的字符串匹配問題

    例如匹配一個串的任意后綴、匹配多個串的任意后綴等。

    字符串哈希

    Hash 的核心思想在于,將輸入映射到一個值域較小、可以方便比較的范圍。

    Warning
    這里的“值域較小”在不同情況下意義不同。
    在 哈希表 中,值域需要小到能夠接受線性的空間與時間復雜度。
    在字符串哈希中,值域需要小到能夠快速比較(10910^9109、101810^{18}1018 都是可以快速比較的)。
    同時,為了降低哈希沖突率,值域也不能太小。

    我們定義一個把字符串映射到整數的函數 fff,這個fff 稱為是 Hash 函數。
    我們希望這個函數 fff 可以方便地幫我們判斷兩個字符串是否相等。
    具體來說,哈希函數最重要的性質可以概括為下面兩條:

  • 在 Hash 函數值不一樣的時候,兩個字符串一定不一樣;
  • 在 Hash 函數值一樣的時候,兩個字符串不一定一樣(但有大概率一樣,且我們當然希望它們總是一樣的)。
  • Hash 函數值一樣時原字符串卻不一樣的現象我們成為哈希碰撞。

    我們需要關注的是什么?
    時間復雜度和 Hash 的準確率。

    通常我們采用的是多項式 Hash 的方法,對于一個長度為 lll 的字符串 s來說,我們可以這樣定義多項式 Hash 函數:f(s)=∑i=1ls[i]×bl?i(modf(s)=\sum^{l}_{i=1}s[i]\times b^{l-i}(modf(s)=i=1l?s[i]×bl?i(mod M)M)M)。例如,對于字符串xyzxyzxyz ,其哈希函數值為xb2+yb+zxb^2+yb+zxb2+yb+z 。
    特別要說明的是,也有很多人使用的是另一種 Hash 函數的定義,即f(s)=∑i=1ls[i]×bi?1(modf(s)=\sum^{l}_{i=1}s[i]\times b^{i-1}(modf(s)=i=1l?s[i]×bi?1(mod M)M)M) ,這種定義下,同樣的字符串 xyzxyzxyz的哈希值就變為了 x+by+zb2x+by+zb^2x+by+zb2 了。顯然,上面這兩種哈希函數的定義函數都是可行的,但二者在之后會講到的計算子串哈希值時所用的計算式是不同的,因此千萬注意 不要弄混了這兩種不同的 Hash 方式。由于前者的 Hash 定義計算更簡便、使用人數更多、且可以類比為一個 b 進制數來幫助理解,所以本文下面所將要討論的都是使用 f(s)=∑i=1ls[i]×bl?i(modf(s)=\sum^{l}_{i=1}s[i]\times b^{l-i}(modf(s)=i=1l?s[i]×bl?i(mod M)M)M) 來定義的 Hash 函數。

    下面講一下如何選擇 M和計算哈希碰撞的概率。

    這里 M 需要選擇一個素數(至少要比最大的字符要大),b 可以任意選擇。如果我們用未知數 x 替代b ,那么f(x)f(x)f(x) 實際上是多項式環ZM[x]\mathbb{Z}_{M}[x]ZM?[x] 上的一個多項式。考慮兩個不同的字符串 s,t有f(s)=f(t)f(s)=f(t)f(s)=f(t) 。我們記h(x)=f(s)?f(t)=∑i=1l(s[i]?t[i])xl?i(modh(x)=f(s)-f(t)=\sum^{l}_{i=1}(s[i]-t[i])x^{l-i}(modh(x)=f(s)?f(t)=i=1l?(s[i]?t[i])xl?i(mod M)M)M) ,其中l=max(∣s∣,∣t∣)l=max(|s|,|t|)l=max(s,t) ??梢园l現 h(x) 是一個 l?1l-1l?1 階的非零多項式。如果 s與t 在x=b 的情況下哈希碰撞,則 b是h(x) 的一個根。由于 h(x) 在ZM\mathbb{Z}_{M}ZM? 是一個域(等價于 M 是一個素數,這也是為什么 M 要選擇素數的原因)的時候,最多有l?1l-1l?1 個根,如果我們保證 b 是從 [0,M) 之間均勻隨機選取的,那么 f(s)f(s)f(s)f(t)f(t)f(t) 碰撞的概率可以估計為 l?1M\frac{l-1}{M}Ml?1?。簡單驗算一下,可以發現如果兩個字符串長度都是 1 的時候,哈希碰撞的概率為 1?1M\frac{1-1}{M}M1?1?=0,此時不可能發生碰撞。

    Hash 的實現

    參考代碼:(效率低下的版本,實際使用時一般不會這么寫)

    using std::string;const int M = 1e9 + 7; const int B = 233;typedef long long ll;int get_hash(const string& s) {int res = 0;for (int i = 0; i < s.size(); ++i) {res = (ll)(res * B + s[i]) % M;}return res; }bool cmp(const string& s, const string& t) {return get_hash(s) == get_hash(t); }

    Hash 的分析與改進

    錯誤率

    若進行 n次比較,每次錯誤率 1M\frac{1}M{}M1?,那么總錯誤率是1?(1?1M)n1-(1-\frac{1}{M})^{n}1?(1?M1?)n。在隨機數據下,若M=109+7M=10^{9}+7M=109+7n=106n=10^6n=106,錯誤率約為 11000\frac{1}{1000}10001?,并不是能夠完全忽略不計的。
    所以,進行字符串哈希時,經常會對兩個大質數分別取模,這樣的話哈希函數的值域就能擴大到兩者之積,錯誤率就非常小了。

    多次詢問子串哈希

    單次計算一個字符串的哈希值復雜度是O(n) ,其中 n為串長,與暴力匹配沒有區別,如果需要多次詢問一個字符串的子串的哈希值,每次重新計算效率非常低下。
    一般采取的方法是對整個字符串先預處理出每個前綴的哈希值,將哈希值看成一個b 進制的數對M 取模的結果,這樣的話每次就能快速求出子串的哈希了:

    fi(s)f_{i}(s)fi?(s) 表示 f(s[1...i])f(s[1...i])f(s[1...i]),即原串長度為 iii 的前綴的哈希值,那么按照定義有 fi(s)=s[1]×bi?1+s[2]×bi?2+...+s[i?1]×b+s[i]f_i(s)=s[1]\times b^{i-1}+s[2]\times b^{i-2}+...+s[i-1]\times b+s[i]fi?(s)=s[1]×bi?1+s[2]×bi?2+...+s[i?1]×b+s[i]

    現在,我們想要用類似前綴和的方式快速求出f(s[l...r])f(s[l...r])f(s[l...r]) ,按照定義有字符串 s[l...r]s[l...r]s[l...r]的哈希值為 f(s[l...r])=s[l]×br?l+s[l+1]×br?l?1+...+s[r?l]×b+s[r]f(s[l...r])=s[l]\times b^{r-l}+s[l+1]\times b^{r-l-1}+...+s[r-l]\times b+s[r]f(s[l...r])=s[l]×br?l+s[l+1]×br?l?1+...+s[r?l]×b+s[r]

    對比觀察上述兩個式子,我們發現 f(s[l...r])=fr(s)?fl?1(s)×br?l+1f(s[l...r])=f_r(s)-f_{l-1}(s)\times b^{r-l+1}f(s[l...r])=fr?(s)?fl?1?(s)×br?l+1 成立,因此我們用這個式子就可以快速得到子串的哈希值。其中br?l+1b^{r-l+1}br?l+1 可以O(n) 的預處理出來然后O(1) 的回答每次詢問(當然也可以快速冪 O(log n)的回答每次詢問)。

    Hash 的應用

    字符串匹配

    求出模式串的哈希值后,求出文本串每個長度為模式串長度的子串的哈希值,分別與模式串的哈希值比較即可。

    允許 k次失配的字符串匹配

    問題:給定長為 n 的源串 ,以及長度為m 的模式串 ,要求查找源串中有多少子串與模式串匹配。s′s's 與s 匹配,當且僅當 s′s's與s 長度相同,且最多有 k 個位置字符不同。其中 1≤n,m≤1061\leq n,m\leq 10^61n,m1060≤k≤50\leq k\leq 50k5

    這道題無法使用 KMP 解決,但是可以通過哈希 + 二分來解決。

    枚舉所有可能匹配的子串,假設現在枚舉的子串為s′s's ,通過哈希 + 二分可以快速找到 s′s's 與p 第一個不同的位置。之后將 s′s's 與 p 在這個失配位置及之前的部分刪除掉,繼續查找下一個失配位置。這樣的過程最多發生 k 次。總的時間復雜度為O(m+knO(m+knO(m+kn log2log_2log2? m)m)m) 。

    最長回文子串

    二分答案,判斷是否可行時枚舉回文中心(對稱軸),哈希判斷兩側是否相等。需要分別預處理正著和倒著的哈希值。時間復雜度O(nO(nO(n logloglog n)n)n)
    這個問題可以使用 manacher 算法 在 O(n)O(n)O(n) 的時間內解決。

    通過哈希同樣可以O(n)O(n)O(n) 解決這個問題,具體方法就是記 RiR_{i}Ri? 表示以 iii 作為結尾的最長回文的長度,那么答案就是maxi=1nRimax^{n}_{i=1}R_{i}maxi=1n?Ri? 。考慮到 Ri≤Ri?1+2R_i\leq R_{i-1}+2Ri?Ri?1?+2,因此我們只需要暴力從 Ri?1+2R_{i-1}+2Ri?1?+2開始遞減,直到找到第一個回文即可。記變量 zzz 表示當前枚舉的 RiR_iRi?,初始時為0 ,則 zzz 在每次 iii 增大的時候都會增大 2,之后每次暴力循環都會減少1 ,故暴力循環最多發生2n 次,總的時間復雜度為 O(n)。

    最長公共子字符串

    問題:給定 m 個總長不超過n 的非空字符串,查找所有字符串的最長公共子字符串,如果有多個,任意輸出其中一個。其中1≤m,n≤1061\leq m,n\leq10^61m,n106 。

    很顯然如果存在長度為k 的最長公共子字符串,那么 k-1 的公共子字符串也必定存在。因此我們可以二分最長公共子字符串的長度。假設現在的長度為k ,check(k) 的邏輯為我們將所有所有字符串的長度為k 的子串分別進行哈希,將哈希值放入 n 個哈希表中存儲。之后求交集即可。
    時間復雜度為O(nO(nO(n log2log_2log2? nm)\frac{n}{m})mn?) 。

    確定字符串中不同子字符串的數量

    問題:給定長為n 的字符串,僅由小寫英文字母組成,查找該字符串中不同子串的數量。

    為了解決這個問題,我們遍歷了所有長度為 l=1,...,nl=1,...,nl=1,...,n 的子串。對于每個長度為 lll,我們將其 Hash 值乘以相同的 b 的冪次方,并存入一個數組中。數組中不同元素的數量等于字符串中長度不同的子串的數量,并此數字將添加到最終答案中。
    為了方便起見,我們將使用 h[i]h[i]h[i] 作為 Hash 的前綴字符,并定義h[0]=0h[0]=0h[0]=0 。

    字典樹 (Trie)

    字典樹,英文名 trie。顧名思義,就是一個像字典一樣的樹。
    先放一張圖:

    可以發現,這棵字典樹用邊來代表字母,而從根結點到樹上某一結點的路徑就代表了一個字符串。舉個例子,1→4→8→121\rightarrow4\rightarrow8\rightarrow1214812 表示的就是字符串 caa。

    trie 的結構非常好懂,我們用 δ(u,c)\delta(u,c)δ(u,c) 表示結點uuuccc 字符指向的下一個結點,或著說是結點 uuu 代表的字符串后面添加一個字符 ccc 形成的字符串的結點。( ccc 的取值范圍和字符集大小有關,不一定是 000~26。)

    有時需要標記插入進 trie 的是哪些字符串,每次插入完成時在這個字符串所代表的節點處打上標記即可。
    Phone List POJ - 3630(字典樹模板題)

    應用

    檢索字符串

    字典樹最基礎的應用——查找一個字符串是否在“字典”中出現過。
    例題:字典樹模板+洛谷P2580 于是他錯誤的點名開始了

    AC 自動機

    trie 是 AC 自動機 的一部分。

    維護異或極值

    將數的二進制表示看做一個字符串,就可以建出字符集為{0,1} 的 trie 樹。

    前綴函數與 KMP 算法
    Boyer-Moore算法
    Z 函數(擴展 KMP)
    自動機

    AC 自動機

    Keywords Search HDU - 2222(AC自動機模板)

    后綴數組 (SA)
    后綴自動機 (SAM)
    后綴平衡樹
    廣義后綴自動機
    后綴樹

    Manacher

    最長回文 HDU - 3068(求最長回文串的長度【馬拉車算法Manacher】)

    回文樹
    序列自動機
    最小表示法
    Lyndon 分解

    總結

    以上是生活随笔為你收集整理的字符串相关的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。