文章目录
字符串基础
字符串的存储
- 使用 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
这里的“值域较小”在不同情况下意义不同。
在 哈希表 中,值域需要小到能够接受线性的空间与时间复杂度。
在字符串哈希中,值域需要小到能够快速比较( 1 0 9 10^9 109、 1 0 18 10^{18} 1018 都是可以快速比较的)。
同时,为了降低哈希冲突率,值域也不能太小。
我们定义一个把字符串映射到整数的函数 f f f,这个 f f f 称为是 Hash 函数。
我们希望这个函数 f f f 可以方便地帮我们判断两个字符串是否相等。
具体来说,哈希函数最重要的性质可以概括为下面两条:
- 在 Hash 函数值不一样的时候,两个字符串一定不一样;
- 在 Hash 函数值一样的时候,两个字符串不一定一样(但有大概率一样,且我们当然希望它们总是一样的)。
Hash 函数值一样时原字符串却不一样的现象我们成为哈希碰撞。
我们需要关注的是什么?
时间复杂度和 Hash 的准确率。
通常我们采用的是多项式 Hash 的方法,对于一个长度为 l l l 的字符串 s来说,我们可以这样定义多项式 Hash 函数: f ( s ) = ∑ i = 1 l s [ i ] × b l − i ( m o d f(s)=\sum^{l}_{i=1}s[i]\times b^{l-i}(mod f(s)=∑i=1ls[i]×bl−i(mod M ) M) M)。例如,对于字符串 x y z xyz xyz ,其哈希函数值为 x b 2 + y b + z xb^2+yb+z xb2+yb+z 。
特别要说明的是,也有很多人使用的是另一种 Hash 函数的定义,即 f ( s ) = ∑ i = 1 l s [ i ] × b i − 1 ( m o d f(s)=\sum^{l}_{i=1}s[i]\times b^{i-1}(mod f(s)=∑i=1ls[i]×bi−1(mod M ) M) M) ,这种定义下,同样的字符串 x y z xyz xyz的哈希值就变为了 x + b y + z b 2 x+by+zb^2 x+by+zb2 了。显然,上面这两种哈希函数的定义函数都是可行的,但二者在之后会讲到的计算子串哈希值时所用的计算式是不同的,因此千万注意 不要弄混了这两种不同的 Hash 方式。由于前者的 Hash 定义计算更简便、使用人数更多、且可以类比为一个 b 进制数来帮助理解,所以本文下面所将要讨论的都是使用 f ( s ) = ∑ i = 1 l s [ i ] × b l − i ( m o d f(s)=\sum^{l}_{i=1}s[i]\times b^{l-i}(mod f(s)=∑i=1