一、定义及基本操作
1.定义
由零个或多个字符组成的有限序列,记为S=‘a1a2a3...an'(n>=0)
n为串的长度,n=0时称为空串,用Phi表示;
最后不含\n,这和代码里的字符串不同喔;
子串:串中任意个连续的字符组成的子序列;
主串:包含子串的串;
字符在主串的位置:即序号,从1开始数
子串在主串的位置:即第一个字符的序号,从1开始数
空串是什么也没有 ; 而空格串是指串里全是空格字符,每个空格字符占1B空间。
ps:串是特殊的线性表,限定了串中的存储对象,而不是外部结构(如顺序表、链表、队列等)
2.串的基本操作
(1)与顺序表不同,串的基本操作通常以子串为操作对象,而非某一个字符;
Unicode相当于对ASCII进行拓展,把除了英文和标点符号外的其他文字进行了收集和编码。
(2)串的顺序存储实现【包括静态数组和动态数组】
/*1.静态数组实现*/
#define MaxLen 255
typedef struct {
char ch[MaxLen];
}SString;//static string
/*2.动态数组(堆分配存储)实现*/
typedef struct {
char* ch;
int length;
}HString;//heap string
int main() {
HString S;//declaration,用完需要释放free
S.ch = new char[MaxLen];
S.length = 0;//Initialization
}
静态数组采用方案四,既可以保证序号与数组下标一样,还可以在O(1)时间内找到元素个数
(3)串的链式存储
注:char在任何计算机都只占1个字节,但是int、float等按照32、64计算机,分别占4、8个字节。所以需要提高存储密度,每个结点存储的数据不要单单存一个字符,而是一个4B的字符串
(3)串的基本操作
#include <iostream>
using namespace std;
#define MaxLen 255
typedef struct {
char ch[MaxLen];
int length;
}SString;//static string
int StrLen(SString S)
{
return sizeof(S.ch) / sizeof(S.ch[1]);
}//Find the number of element,may "false"
/*1.复制字符串*/
void StrCopy(SString &s1, SString s2)
{
for(int i=1;i<=s2.length;i++)//从1开始是因为下标为0的空间不存东西
s1.ch[i] = s2.ch[i];
s1.length = s2.length;
}
/*2.取出主串中某个位序后的子串*/
bool SubString(SString& Sub, SString S, int pos, int len)//pos即是序号也是数组下标
{
//子串范围越界;
if (pos- 1 + len > S.length)//这里减一,画图理解一下
return false;
for (int i = pos; i < pos + len; i++)
Sub.ch[i - pos + 1] = S.ch[i];//一开始i=pos,Sub从1开始放
Sub.length = len;
return true;
}
/*3. 比较字符串大小*/
int StrCompare(SString S, SString T)
{
for (int i = 1; i <= S.length && i <= T.length; i++)
{
if (S.ch[i] != T.ch[i])
return S.ch[i] - T.ch[i];
}//compare one by one;
return S.length - T.length;//one string used up
}
/*4.判断s中是否有和t相等的子串,有则返回位序,无则返回0*/
int index(SString S,SString T)
{
int i = 1, n = StrLen(S), m = StrLen(T);
SString Sub;//for storing the substring
while (i <= n - m + 1)
{
SubString(Sub, S, i, m);//get m elements from the i.th of S to Sub
if (StrCompare(Sub, T) != 0)
i++;
return i;
}
return 0;
}
int main()
{
SString s1, s2;
s1.length = 0;
s2.length = 0;
cout << "Please input a string for s1 to store:\n";
cin >> s1.ch;
s1.length = sizeof(s1);
cout << "the length of s1 is "<<StrLen(s1);
cout << "s1 is " << s1.ch << endl;
cout << "Please input a string for s1 to refresh:\n";
cin >> s2.ch;
s2.length = sizeof(s2);
StrCopy(s1, s2);
cout << "s1 is " << s1.ch<<endl;
cout << "the length of s1 is " << s1.length;
return 0;
}
SubString:取出子串为什么会越界
也可以理解为pos-1,这样会指向目标结点前面,再加上len,这样len刚好表示需要的个数
(4)不用基本操作的算法:
朴素模式匹配算法(暴力求解):当前串不行,让主串挪一个位置重新匹配,i=i-j+1
int Index(SString S, SString T) { int i=1, j=1; while (i <= S.length && j <= T.length) { if (S.ch[i] == T.ch[j]) i++, j++; else { i = i-j + 2; //i-j表示主串指针回到S的当前子串前一个位置,+1为当前子串的第一个位置 //+2表示当前子串的第二个位置,实现主串回溯 j = 1; } } if (j > T.length) return i - T.length; else return 0; }
匹配模式的朴素算法最坏时间复杂度:O((n-m+1)m)=O(nm-m^2+m)=O(nm)
KMP算法(主串指针不回溯,先摸清模式串特点来简化算法)
以 abaabc为例:
next[ x ] 表示第 x 位对不上时,主串位序 i 不变,但是模式串的 j 变换到第几位
对比失败的步骤:第 x 位失败,证明该子串的前 x-1 已知且和模式串相同。写出子串的前x-1位再去掉第一位,和模式串依次比较看看 j 能到第几位。
next[1]无脑写0 ;next[2]无脑写1;
KMP优化算法:【相同则更新,不同则保留】
再次利用模式串特点,在已知next数组基础上,将next数组优化。
虽然你模式串回到从前,但是你没曾想,如果你回到的同样的字母,还是会匹配失败!
所以在这里可以从x=2开始,看看回到序号的字母和自己是否相同。