文章目录
1.串类型的定义
- 串是由零个或多个字符组成的有限序列
- 串中任意个连续的字符组成的子序列称为该串的子串。包含子串的串相应地称为主串。子串在主串中的位置则以子串的第一个字符在主串中的位置来表示
- 串赋值、串比较、求串长和求子串、串连接以及求子串五种操作构成串类型的最小操作子集,即这些操作不可能利用其它串操作来实现。
2.串的表示和实现
(1)定长顺序存储表示
- 定义:
#define MAXSTRLEN 255
typedef unsigned char SString[MAXSTRLEN+1]; //0号单元存放串的长度
- 串连接的实现
bool Concat(SString& T, const SString& S1, const SString& S2)
{
//如果未截断
if (S1[0] + S2[0]<=MAXSTRLEN)
{
int k = 1;
for (int i = 1 ;i <= len(S1) ; ++i)
{
T[k++] = S1[i];
}
for (int i = 1; i <= len(S2); ++i)
{
T[k++] = S2[i];
}
T[0] = S1[0] + S2[0];
}
//截断1
if (S1[0] < MAXSTRLEN)
{
int k = 1;
for (int i = 1; i <= len(S1); ++i)
{
T[k++] = S1[i];
}
for (int i = 1; i <= MAXSTRLEN-len(S1); ++i)
{
T[k++] = S2[i];
}
T[0] = S1[0] + S2[0];
return false;
}
else
{
for (int i = 0; i <= len(S1); ++i)
{
T[i] = S1[i];
}
return false;
}
return true;
}
- 求子串
bool SubString(SString& sub,SString s,int pos,int len)
{
bool SubString(SString& sub, SString s, int pos, int len)
{
if (pos<1 || pos>s[0] || len<0 || len>s[0] - pos)
return false;
else
{
int k = 1;
for (int i = pos; i <= len + pos; ++i)
{
sub[k] = s[i];
}
sub[0] = len;
}
return true;
}
}
(2)堆分配存储表示
- 即使用堆动态分配串的长度,高级语言中经常使用该方法
typedef struct
{
char* ch;
int length;
}SString;
- 串插入操作
bool StrInsert(SString& s, int pos, SString T)
{
if (pos < 1 || pos + s.length + 1) return false;
if (T.length) //T非空,则重新分配空间
{
s.ch = (char*)realloc(s.ch,(s.length+T.length)*sizeof(char));
if (!s.ch) return false;
for (int i = pos-1; i < s.length; ++i)
s.ch[i + T.length] = s.ch[i];
int k = 0;
for (int i = pos - 1; i < pos - 1 + T.length; ++i)
{
s.ch[i] = T.ch[k++];
}
s.length += T.length;
}
return true;
}
(3)串的块链存储表示
- 一个结点可以存放多个字符,通常链表中的最后一个结点不一定全被串值占满,此时通常补上“#”或其他的非串值字符
- 除了头指针外还可附设一个尾指针指示链表中的最后一个结点,并给出当前串的长度,称如此定义的串存储结构为块链结构。
- 块链结构的定义
#define CHUNKSIZE 80
typedef struct Chunk
{
char ch[CHUNKSIZE];
struct Chunk *next;
}Chunk;
typdef struct{
Chunk *head,*tail;
int curlen;
}
3.串的模式匹配算法
- 子串的定位操作通常称作串的模式匹配
(1)普通算法
int Index(SString S,SString T,int pos)
{
i = pos;
j = 1;
while(1<=S[0]&&j<=T[0])
{
if(S[i]==T[j])
{
++i;
++j;
}
else //如果不相等,回溯
{
i = i-j+2;
j = 1;
}
}
if(j>T[0])
return i-T[0];
else
return 0;
}
- 上述算法的优点是匹配过程简单容易理解,且在某些场合,如文本编辑等,效率也比较高,但是某些场合会出现效率特别低的情况,比如当模式串为"000000001",而主串为“0000000000000000000000000000000000000000000001”时,指针会回溯很多次。在最坏的情况下时间复杂度为O(n*m)
(2)KMP匹配算法
void GetNext(int next[],String t)
{
int j = 0,k=-1;
next[0]=-1;
while(j<t.length-1)
{
if(k==-1 || t[j]==t[k])
{
j++;k++;
if(t[j]==t[k])
next[j]=next[k];
else
next[j]=k;
}
else
k = next[k];
}
}
int KMP(String s,String t)
{
int next[MaxSize],i=0;j=0;
Getnext(t,next);
while(i<s.length&&j<t.length)
{
if(j==-1 || s[i]==t[j])
{
i++;
j++;
}
else
j = next[j];
}
if(j>=t.length)
return i-t.length;
else
return -1;
}
4.字符串hash
(1)将字符串转换成唯一整数
假设A—Z对应0—25,a—z对应26~51,0-9对应52-61,这样就把26个大写字母和数字对应到了六十二进制中,就可以实现字符串对应唯一整数
int hashFunc(char S[],int len)
{
int id = 0;
for(int i = 0;i<len;++i)
{
if(S[i]>='A'&&S[i]<=Z)
id = id*62+(S[i]-'A');
else if(S[i]>='a'&& S[i]<='z')
id = id*62+(S[i]-'a')+26;
else if(S[i]>='0'&&S[i]<='9')
id = id*62+(S[i]-'0')+52;
}
return id;
}
(2)字符串hash进阶
由于按照62进制来hash字符串,当字符串较长时,产生的整数会非常大,为了应对这种情况,只能舍弃一些“唯一性”,将产生的结果对一个整数mod取模,但是这又产生另外的问题,也就是可能有多个字符串的hash值相同,导致冲突,不过幸运的是,在实践中发现,在int数据范围内,如果把进制数设置为一个107级别的素数p(例如10000019),同时把mod设置为一个109级别的素数(例如1000000007),那么冲突的概率将会变得非常小,很难产生冲突。