串
串的定義
串,即字符串(String)是由兩個或多個字符組成的有序序列。一般記為 \(S='a_1a_2\cdot \cdot \cdot a_n'\)
其中,S是串名,單引號括起來的字符序列是串的值;\(a_i\)可以是字符、數字或其他字符;
串中字符的個數n稱為串的長度。\(n=0\)時的串稱為空串(用\(\varnothing\)表示)
子串:串中任意個連續的字符組成的子序列
主串:包含子串的串
字符在主串的位置:字符在串中的序號
子串在主串的位置:子串的第一個字符在主串的位置
串是一種特殊的線性表,數據對象限定為字符集。基本操作以子串為操作對象
串的存儲結構
定長順序存儲表示
//定長數組
#define MAXLEN 255 //預定義最大串長為255
typedef struct{
char ch[MAXLEN]; //每個分量存儲一個字符
int length; //串的實際長度
}SString;
堆分配存儲表示
//動態分配
typedef struct{
char ch*; //按串長分配存儲區,ch指向串的基地址
int length; //串的長度
}HString;
塊鏈存儲表示
typedef struct StringNode{
char ch; //每個結點存一個字符
struct StringNode* next;
}StringNode,*String;
//比較推薦
typedef struct StringNode{
char ch[4]; //每個結點存多個字符
struct StringNode* next;
}String;
串的基本操作
StrAssign(&T,chars); //賦值操作,把串T賦值為chars
StrCopy(&T,S); //復制操作,由串S復制得到串T
StrEmpty(S); //判空操作,若S為空串,則返回TRUE,否則返回FALSE
StrLength(S); //求串長,返回串S的元素個數
ClearString(&S); //清空操作,將S清為空串
DestoryString(&S); //銷毀串。將串S銷毀(回收存儲空間)
Concat(&T,S1,S2); //串聯接。用T返回由S1和S2聯接而成的新串
SubString(&Sub,S,pos,len); //求子串。用Sub返回串S的第pos個字符起長度為len的子串
Index(S,T); //定位操作。若主串S存在與串T值相同的子串,則返回他在主串S第 //一次出現的位置
StrCompare(S,T); //比較操作。若S>T,則返回值>0;若S=T,則返回值=0;
//若S<T,則返回值<0
//從第一個字符開始往后依次對比,先出現更大字符的串就更大
//長串的前綴與短串相同時,長串更大
求子串
bool SubString(SSting &Sub,SSting S,int pos,int len)
{
//子串范圍越界
if(pos+len-1>S.length)
return false;
for(int i=pos;i<pos+len;i++)
Sub.ch[i-pos+1]=S.ch[i];
Sub.length=len;
return false;
}
比較操作
int StrCompare(SSting S,SSting 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];
}
//掃描過的所有字符都相同,則長度長的串更大
return S.length-T.length;
}
定位操作
int Index(SString S,SSting T)
{
int i=1,n=StrLength(S),m=StrLength(T);
SString sub;
while(i<=n-m+1)
{
SubString(sub,s,i,m);
if(StrCompare(sub,T)!=0)
++i;
else
return i; //返回子串在主串中的位置
}
return 0; //S中不存在與T相等的子串
}
模式匹配算法
串的模式匹配:在主串中找到與模式串相同的子串,並返回其所在位置。
朴素模式匹配算法
int Index(SStromh S,SString T)
{
int i=1,j=1;
while(i<=S.length&&j<=T.length)
{
if(S.ch[i]==T.ch[i])
{
++i;++j;
}
else
{
i=i-j+2;
j=1; //指針后退重新開始匹配
}
}
if(j>T.length)
return i-T.length;
else
return 0;
}
性能分析
較好情況:每個子串第一個字符就與模式串不匹配
若模式串長度為m,主串長度為n,則
匹配成功的最好時間復雜度:\(O(m)\)
匹配失敗的最好時間復雜度:\(O(n-m+1)=O(n-m)=O(n)\)
較壞情況:
若模式串長度為m,主串長度為n,則直到匹配成功/匹配失敗最多需要\((n-m+1)*m\)次比較
最壞時間復雜度:\(O(nm)\)
KMP算法
朴素模式匹配算法的缺點:當某些子串與模式串部分匹配時,主串的掃描指針\(i\)經常回溯,導致時間開銷增加。
改進思路:主串指針不回溯,只有模式串指針回溯。
如果\(j=k\)時才發現匹配識別,說明\(1到k-1\)都匹配成功
int Index_KMP(SString s,SString T,int next[])
{
int i=1;j=1;
while(i<=S.length&&j<=T.length)
{
if(j==0||S.ch[i]==T.ch[j])
{
++i;
++j; //繼續比較后繼字符
}
else
{
j=next[j];
}
}
if(j>T.length)
return i-T.length;
else
return 0;
}
next數組:當模式串的第\(j\)個字符匹配失敗時,令模式串跳到\(next[j]\)再繼續匹配
串的前綴:包含第一個字符,且不包含最后一個字符的子串
串的后綴:包含最后一個字符,且不包含第一個字符的子串
當第\(j\)個字符匹配失敗,由前\(1到j-1\)個字符組成的串記為\(S\),則:\(next[j]=s\)的最長相等前后綴長度+1
特別的,\(next[1]=0\)
//求next數組
void get_next(SString T,int next[])
{
int i=1,j=0;
next[1]=0;
while(i<T.length)
{
if(j==0||T.ch[i]==T.ch[j])
{
++i;++j;
//若pi=pj,則next[j+1]=next[j]+1;
next[i]=j;
}
else
{
//否則令j=next[j],循環繼續
j=next[j];
}
}
}
KMP算法性能分析:當子串和模式串不匹配時,主串指針i不回溯,模式串指針\(j=next[j]\)算法平均時間復雜度:\(O(m+n)\)
KMP算法優化
當子串和模式串不匹配時\(j=nextval[j]\)(減少對比)
//計算next數組修正值
void get_nextval(SString T,int nextval[])
{
int i=1,j=0;
nextval[1]=0;
while(i<T.length)
{
if(j==0||T.ch[i]==T.ch[j])
{
++i;++j;
if(T.ch[i]!=T.ch[j])
{
nextval[i]=j;
}
else
{
nextval[i]=nextval[j];
}
}
else
{
j=nextval[j];
}
}
}