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

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

字符串相关

發(fā)布時(shí)間:2023/12/4 编程问答 26 豆豆
生活随笔 收集整理的這篇文章主要介紹了 字符串相关 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

文章目錄

  • 字符串基礎(chǔ)
      • 字符串的存儲(chǔ)
  • 標(biāo)準(zhǔn)庫
  • 字符串匹配
        • 單串匹配
        • 多串匹配
        • 其他類型的字符串匹配問題
  • 字符串哈希
      • Hash 的實(shí)現(xiàn)
      • Hash 的分析與改進(jìn)
        • 錯(cuò)誤率
        • 多次詢問子串哈希
      • Hash 的應(yīng)用
        • 字符串匹配
        • 允許 k次失配的字符串匹配
        • 最長回文子串
        • 最長公共子字符串
        • 確定字符串中不同子字符串的數(shù)量
  • 字典樹 (Trie)
      • 應(yīng)用
        • 檢索字符串
        • AC 自動(dòng)機(jī)
        • 維護(hù)異或極值
  • AC 自動(dòng)機(jī)
  • Manacher

字符串基礎(chǔ)

字符串的存儲(chǔ)

  • 使用 char 數(shù)組存儲(chǔ),用空字符 \0 表示字符串的結(jié)尾。(C 風(fēng)格字符串)
  • 使用 C++ 標(biāo)準(zhǔn)庫提供的 string 類。
  • 字符串常量可以用字符串字面值(用雙引號括起來的字符串)表示。
  • 標(biāo)準(zhǔn)庫

    C 標(biāo)準(zhǔn)庫是在對字符數(shù)組進(jìn)行操作:char[]/const char*

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

    C++ 標(biāo)準(zhǔn)庫是在對字符串對象進(jìn)行操作,同時(shí)也提供對字符數(shù)組的兼容。 std::string

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

    字符串匹配

    單串匹配

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

    多串匹配

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

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

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

    字符串哈希

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

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

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

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

    我們需要關(guān)注的是什么?
    時(shí)間復(fù)雜度和 Hash 的準(zhǔn)確率。

    通常我們采用的是多項(xiàng)式 Hash 的方法,對于一個(gè)長度為 lll 的字符串 s來說,我們可以這樣定義多項(xiàng)式 Hash 函數(shù):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 ,其哈希函數(shù)值為xb2+yb+zxb^2+yb+zxb2+yb+z
    特別要說明的是,也有很多人使用的是另一種 Hash 函數(shù)的定義,即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的哈希值就變?yōu)榱?x+by+zb2x+by+zb^2x+by+zb2 了。顯然,上面這兩種哈希函數(shù)的定義函數(shù)都是可行的,但二者在之后會(huì)講到的計(jì)算子串哈希值時(shí)所用的計(jì)算式是不同的,因此千萬注意 不要弄混了這兩種不同的 Hash 方式。由于前者的 Hash 定義計(jì)算更簡便、使用人數(shù)更多、且可以類比為一個(gè) b 進(jìn)制數(shù)來幫助理解,所以本文下面所將要討論的都是使用 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 函數(shù)。

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

    這里 M 需要選擇一個(gè)素?cái)?shù)(至少要比最大的字符要大),b 可以任意選擇。如果我們用未知數(shù) x 替代b ,那么f(x)f(x)f(x) 實(shí)際上是多項(xiàng)式環(huán)ZM[x]\mathbb{Z}_{M}[x]ZM?[x] 上的一個(gè)多項(xiàng)式。考慮兩個(gè)不同的字符串 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) 。可以發(fā)現(xiàn) h(x) 是一個(gè) l?1l-1l?1 階的非零多項(xiàng)式。如果 s與t 在x=b 的情況下哈希碰撞,則 b是h(x) 的一個(gè)根。由于 h(x) 在ZM\mathbb{Z}_{M}ZM? 是一個(gè)域(等價(jià)于 M 是一個(gè)素?cái)?shù),這也是為什么 M 要選擇素?cái)?shù)的原因)的時(shí)候,最多有l?1l-1l?1 個(gè)根,如果我們保證 b 是從 [0,M) 之間均勻隨機(jī)選取的,那么 f(s)f(s)f(s)f(t)f(t)f(t) 碰撞的概率可以估計(jì)為 l?1M\frac{l-1}{M}Ml?1?。簡單驗(yàn)算一下,可以發(fā)現(xiàn)如果兩個(gè)字符串長度都是 1 的時(shí)候,哈希碰撞的概率為 1?1M\frac{1-1}{M}M1?1?=0,此時(shí)不可能發(fā)生碰撞。

    Hash 的實(shí)現(xiàn)

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

    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 的分析與改進(jìn)

    錯(cuò)誤率

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

    多次詢問子串哈希

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

    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]

    現(xiàn)在,我們想要用類似前綴和的方式快速求出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]

    對比觀察上述兩個(gè)式子,我們發(fā)現(xiàn) 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 成立,因此我們用這個(gè)式子就可以快速得到子串的哈希值。其中br?l+1b^{r-l+1}br?l+1 可以O(shè)(n) 的預(yù)處理出來然后O(1) 的回答每次詢問(當(dāng)然也可以快速冪 O(log n)的回答每次詢問)。

    Hash 的應(yīng)用

    字符串匹配

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

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

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

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

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

    最長回文子串

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

    通過哈希同樣可以O(n)O(n)O(n) 解決這個(gè)問題,具體方法就是記 RiR_{i}Ri? 表示以 iii 作為結(jié)尾的最長回文的長度,那么答案就是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開始遞減,直到找到第一個(gè)回文即可。記變量 zzz 表示當(dāng)前枚舉的 RiR_iRi?,初始時(shí)為0 ,則 zzz 在每次 iii 增大的時(shí)候都會(huì)增大 2,之后每次暴力循環(huán)都會(huì)減少1 ,故暴力循環(huán)最多發(fā)生2n 次,總的時(shí)間復(fù)雜度為 O(n)。

    最長公共子字符串

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

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

    確定字符串中不同子字符串的數(shù)量

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

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

    字典樹 (Trie)

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

    可以發(fā)現(xiàn),這棵字典樹用邊來代表字母,而從根結(jié)點(diǎn)到樹上某一結(jié)點(diǎn)的路徑就代表了一個(gè)字符串。舉個(gè)例子,1→4→8→121\rightarrow4\rightarrow8\rightarrow1214812 表示的就是字符串 caa。

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

    有時(shí)需要標(biāo)記插入進(jìn) trie 的是哪些字符串,每次插入完成時(shí)在這個(gè)字符串所代表的節(jié)點(diǎn)處打上標(biāo)記即可。
    Phone List POJ - 3630(字典樹模板題)

    應(yīng)用

    檢索字符串

    字典樹最基礎(chǔ)的應(yīng)用——查找一個(gè)字符串是否在“字典”中出現(xiàn)過。
    例題:字典樹模板+洛谷P2580 于是他錯(cuò)誤的點(diǎn)名開始了

    AC 自動(dòng)機(jī)

    trie 是 AC 自動(dòng)機(jī) 的一部分。

    維護(hù)異或極值

    將數(shù)的二進(jìn)制表示看做一個(gè)字符串,就可以建出字符集為{0,1} 的 trie 樹。

    前綴函數(shù)與 KMP 算法
    Boyer-Moore算法
    Z 函數(shù)(擴(kuò)展 KMP)
    自動(dòng)機(jī)

    AC 自動(dòng)機(jī)

    Keywords Search HDU - 2222(AC自動(dòng)機(jī)模板)

    后綴數(shù)組 (SA)
    后綴自動(dòng)機(jī) (SAM)
    后綴平衡樹
    廣義后綴自動(dòng)機(jī)
    后綴樹

    Manacher

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

    回文樹
    序列自動(dòng)機(jī)
    最小表示法
    Lyndon 分解

    總結(jié)

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

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。