4. 串和KMP算法

1、串的抽象数据类型的定义

ADT String {
数据对象:
D = { a i ∣ a i ∈ C h a r a c t e r S e t ;   i = 1 , 2 , . . . , n ;   n ⩾ 0 } D=\{a_{i} | a_{i} \in CharacterSet; \ i=1,2,...,n;\ n \geqslant 0\} D={aiaiCharacterSet; i=1,2,...,n; n0}

数据关系:
R 1 = { < a i − 1 , a i > ∣ a i − 1 , a i ∈ D ;   i = 1 , 2 , . . . , n } \begin{aligned}R_{1}=\{&<a_{i-1},a_{i}> | a_{i-1},a_{i} \in D;\ i=1,2,...,n\}\end{aligned} R1={<ai1,ai>ai1,aiD; i=1,2,...,n}

基本操作:
StrAssign(&T, chars)
初始条件:chars是字符串常量。
操作结果:生成一个其值等于chars的串T。

StrCoрy(&т, S)
初始条件:串S存在。
操作结果:由串S复制得串T。

StrEmpty(S)
初始条件:串S存在。
操作结果:若S为空串,则返回TRUE,否则返回FALSE。

StrCompare(S, T)
初始条件:串S和T存在。
操作结果:若S > T,则返回值 >0;若S = T,则返回值 =0;若S < T,则返回值 <0。

StrLength(S)
初始条件:串S存在。
操作结果:返回S的元素个数,称为串的长度。

ClearString(&S)
初始条件:串S存在。
操作结果:将S清为空串。

Concat(&T, S1, S2)
初始条件:串S1和S2存在。
操作结果:用T返回由S1和S2联接而成的新串。

SubString(&Sub, S, pos, len)
初始条件:串S存在, 1 ⩽ p o s ⩽ S t r L e n g t h ( S ) 1 \leqslant pos \leqslant StrLength(S) 1posStrLength(S) 0 ⩽ l e n ⩽ S t r L e n g t h ( S ) − p o s + 1 0 \leqslant len \leqslant StrLength(S)-pos+1 0lenStrLength(S)pos+1
操作结果:用Sub返回串S的第pos个字符起长度为len的子串。

Index(S, T, pos)
初始条件:串S和T存在,T是非空串, 1 ⩽ p o s ⩽ S t r L e n g t h ( S ) 1 \leqslant pos \leqslant StrLength(S) 1posStrLength(S)
操作结果:若主串S中存在和串T值相同的子串,则返回它在主串S中第pos个字符之后第一次出现的位置;否则函数值为0。

Replace(&S, T, V)
初始条件:串S,T和V存在,T是非空串。
操作结果:用V替换主S中出现的所有与T相等的不重叠的子串。

StrInsert(&S, pos, T)
初始条件:串S和T存在, 1 ⩽ p o s ⩽ S t r L e n g t h ( S ) + 1 1 \leqslant pos \leqslant StrLength(S)+1 1posStrLength(S)+1
操作结果:在串S的第pos个字符之前插入串T。

StrDelete(&S, pos, len)
初始条件:串S存在, 1 ⩽ p o s ⩽ S t r L e n g t h ( S ) − l e n + 1 1 \leqslant pos \leqslant StrLength(S)-len+1 1posStrLength(S)len+1
操作结果:从串S中删除第pos个字符起长度为len的子串。

DestroyString(&S)
初始条件:串S存在。
操作结果:串S被销毁。
}ADT String

串赋值StrAssign、串比较StrCompare、求串长StrLength、串联接Concat,求子串SubString 5种操作构成串类型的最小操作子集。即其他串操作(除串清除ClearString,串销毁DestroyString外)均可在这个最小操作子集上实现。

2、串的表示和实现

2.1、定长顺序存储表示

类似于线性表的顺序存储结构,用一组地址连续的存储单元存储串值的字符序列。在串的定长顺序存储结构中,按照预定义的大小,为每个定义的串变量分配一个固定长度的存储区,则可用定长数组如下描述之。

// -----串的定长顺序存储表示----
#define MAXSTRLEN 255 // 用户可在255以内定义最大串长
typedef unsigned char SString[MANSTRLEN + 1]; // 0号单元存放串的长度
// MAX_SIZE 表示串的最大长度;
#define MAX_SIZE 10
typedef struct Str {
	// str数组长度定义为 MAX_SIZE + 1,因为多出一个'\0'作为结束标记;
	char str[MAX_SIZE + 1];
	int length; //串长度
} Str;

串的实际长度可在这预定义长度的范围内随意,超过预定义长度的串值则被舍去,称之为“截断”。

对串长有两种表示方法:
一是以下标为0的数组分量存放串的实际长度。
二是在串值后面加一个不计人串长的结束标记字符,此时的串长为隐含值,显然不便于进行某些串操作。

2.2、堆分配存储表示

通常C语言中提供的串类型就是以这种存储方式实现的。系统利用malloc()和free()进行串值空间的动态管理,为每一个新产生的串分配一个存储区,称串值共享的存储空间为“堆”,C语言中的串以一个空字符为结束符,串长是一个隐含值。

// -----串的堆分配存储表示----
typedef struct{
	char *ch; //若是非空串,则按串长分配存储区,否则ch为NULL
	int length; //串长度 
}HString;
typedef struct Str {
	char *ch;
	int length; // 串长度
} Str;
//初始化
Str S;
S.length = L;
S.ch = (char*) malloc((L + 1) * sizeof(char));
//S.ch[length范围内的位置] = 某字符变量
//某字符变量 = S.ch[length范围内的位置]

//释放内存
free(S.ch)

2.3、串的块链存储表示

和线性表的链式存储结构相类似,也可采用链表方式存储串值。由于串结构的特殊性——结构中的每个数据元素是一个字符,则用链表存储串值时,存在一个 “结点大小” 的问题,即每个结点可以存放一个字符,也可以存放多个字符。
当结点大小大于1时,由于串长不一定是结点大小的整倍数,则链表中的最后一个结点不一定全被串值占满,此时通常补上 “#" 或其他的非串值字符(通常 “#” 不属于串的字符集,是一个特殊的符号)。


// ======串的块链存储表示======
#define CHUNK_SIZE 80 //可由用户定义的块大小
typedef struct Chunk{
	char ch[CHUNK_SIZE];
	struct Chunk *next;
}Chunk;

typedef struct {
	Chunk *head, *tail; //串的头和尾指针
	int curlen; //串的当前长度
}LString;

串值的存储密度 = 串值所占的存储位 / 实际分配的存储位

3、字符串模式匹配算法

3.1、简单算法

采用定长顺序存储结构的不依赖于其他串操作的匹配算法:

int Index(SString S, SString T, int pos){
	//返回子串T在主串S中第pos个字符之后的位置;若不存在,函数返回值为0。
	//其中,T非空,1<=pos<=StrLength(S)
	int i = pos, j = 1;
	while(i<=S[0] && j<=T[0]){
		if(S[i] == T[j]){//继续比较后续字符
			++i;
			++j;
		}else{//指针后退重新开始匹配
			i = i-(j-2); // 或 i = i - j + 2;
			j = 1;
		}
	}
	if(j>T[0]){
		return i-T[0];
	}else{
		return 0;
	}
}

3.2、首尾匹配算法

先比较模式串的第一个字符,
再比较模式串的最后一个字符,
最后比较模式串中从第二个到第n-1个字符。 

int Index_FL(SString S, SString T, int pos){
	sLength = S[0];
	tLength = T[0];
	int i = pos;
	patStartChar = T[1]; 
	patEndChar = T[tLength];
	while(i <= sLength-tLength+1){
		if(S[i] != patStartChar){
			++i; //重新查找匹配起始点
		}else if(S[i+tLength-1] != patEndChar){
			++i; //模式串的"尾字符"不匹起
		}else{ //检查中间字符的匹配情况
		
		}
	}
	return 0;
}

3.3、KMP算法

《部分匹配表》(Partial Match Table)
"前缀"指除了最后一个字符以外,一个字符串的剩余全部字符;
"后缀"指除了第一个字符以外,一个字符串的剩余全部字符;

KMP算法的时间复杂度可以达到O(m+n)

经过简单匹配过程可知:
S[i]   !=   T[j] \LARGE{\textbf{S[i]\ !=\ T[j]}} S[i] != T[j]
已经得到的结果是 S[i-(j-1)...(i-1)]    ==    T[1...(j-1)] \LARGE{\textbf{S[i-(j-1)...(i-1)]\ ==\ T[1...(j-1)]}} S[i-(j-1)...(i-1)]  ==  T[1...(j-1)]
若已知 T[1...(k-1)]    ==    T[j-(k-1)...(j-1)] \LARGE{\textbf{T[1...(k-1)]\ ==\ T[j-(k-1)...(j-1)]}} T[1...(k-1)]  ==  T[j-(k-1)...(j-1)]
则有 S[i-(k-1)...(i-1)]    ==    T[1...(k-1)] \LARGE{\textbf{S[i-(k-1)...(i-1)]\ ==\ T[1...(k-1)]}} S[i-(k-1)...(i-1)]  ==  T[1...(k-1)]

定义模式串的next函数
n e x t ( j ) = { 0 当 j = 1 ; M a x { k   ∣   1 < k < j   且 ′ p 1 p 2 ⋅ ⋅ ⋅ p k − 1 ′ = ′ p j − ( k − 1 ) p j − ( k − 2 ) ⋅ ⋅ ⋅ p j − 1 ′   } 1 其他情况 \large{ next(j)= \begin{cases} 0& \text{当\ j\ =\ 1\ ;}\\ Max\{k\ |\ 1<k<j\ 且'p_{1}p_{2}\cdot \cdot \cdot p_{k-1}'='p_{j-(k-1)}p_{j-(k-2)}\cdot \cdot \cdot p_{j-1}'\ \}&\\ 1& \text{其他情况} \end{cases} } next(j)=0Max{k  1<k<j p1p2pk1=pj(k1)pj(k2)pj1 }1 j = 1 ;其他情况

3.3.1、KMP

int Index_KMP(SString S, SString T, int pos) {
	// 1<=pos<=StrLength(S)
	int i=pos, j=1;
	while(i<=S[0] && j<=T[0]){
		if(j==0 || S[i]==T[j]){//继续比较后续字符
			++i;
			++j;
		}else{ //模式串向右移动
			j=next[j];
		} 
	}
	if(j > T[0]){ //匹配成功
		return i-T[0];
	}else{
		return 0;
	}	
} //Index_KMP

3.3.2、next数组

void get_next(SString &T int &next[]){
	//求模式串T的next函数值并存入数组next。
	int i = 1;
	next[1] = 0;
	int j = 0;
	while(i<T[0]){
		if(j==0 || T[i]==T[j]){
			++i;
			++j;
			next[i]=j;
		}else{
			j=next[j];
		}
	}
}//get_next 

对于模式串 a b a b c a b a b a b c \large{ababcabababc} ababcabababc,求next数组过程
当i=1时,next[1]=0;
此后
第一次进入while循环,此时 j 初始化为0;进入if语句,{ ++i 即 i=2; ++j 即 j=1; next[2]=1;}
第二次进入while循环,此时 if 判断不符合,进入else语句,{ j = next[1] = 0; }
第三次进入while循环,此时 j=0,进入 if 语句,{ ++i 即 i=3; ++j 即 j=1; next[3]=1;}
第四次进入while循环,此时 T[3]==T[1],进入 if 语句,{ ++i 即 i=4; ++j 即 j=2; next[4]=2;}
. . . . . . 从而做到在不回溯i, 不断向前推移模式串的方法下求取next数组。

3.3.3、nextval数组

当面临下面这种特殊情况时
S=’aaabaaabaaabaaabaaab’ \LARGE{\textbf{S='aaabaaabaaabaaabaaab'}} S=’aaabaaabaaabaaabaaab’
T=’aaaab’ \LARGE{\textbf{T='aaaab'}} T=’aaaab’
next[j] = 01234
nextval[j] = 00004

void get_nextval(SString &T, int &nextval[]) {
	int i = 1;
	nextval[1] = 0;
	int j = 0;
	while(i<T[0]){
		if(j==0 || T[i]==T[j]){
			++i;
			++j;
			if(T[i] != T[j]){
				nextval[i] = j;
			}else{
				nextval[i] = nextval[j];
			}
		}else{
			j = nextval[j];
		}
	}
} //get_nextval

=======================================

待整理 串的模式匹配算法

串的基本操作

赋值操作:将一个常量字符串赋值给str;赋值成功返回1,否则返回0

int strassign(Str& str, char* ch){
	if(str.ch){
		free(str.ch)//释放原串空间
	}
	int len = 0;
	char *c = ch;
	while(*c){//求ch串的长度
		++len;
		++c;
	}
	if(len ==0){//如果ch为空串,则直接返回空串
		str.ch = NULL;
		str.length=0;
		return 1;
	}else{
		str.ch = (char*)malloc(sizeof(char) * (len+1));
		//取len+1是为了多分配一个空间存放'\0'字符
		if(str.ch == NULL){
			return 0;
		}else{
			c = ch;
			for(int i=0;i<=len;++i,++c){
				str.ch[i]=*c;
			}
			//注意:循环条件中之所以"<=",是为了将ch最后的'\n'复制到新串中作为结束标记
			str.length = len;
			return 1;
		}
	}
}

函数使用格式:
strassign(str,"Hello World")

取串长度的操作

int strlength(Str str){
	return str.length;
}

在没有给出串长度信息的情况下,求长度的操作可以借鉴函数strassign()中的求输入串长度的部分代码来实现

串的比较操作

int strcompare(Str s1, Str s2){
	for(int i=0;i<s1.length && i<s2.length;++i){
		if(s1.ch[i] != s2.ch[i]){
			return s1.ch[i] - s2.ch[i]
		}
	}
	return s1.length = s2.length;
}

串连接操作

求子串操作

串清空操作

int clear(){
	if(str.ch){
		free(str.ch);
		str.ch = NULL;
	}
	str.length-0;
	return 1
}


——《数据结构图 (C语言版) 严蔚敏》 学习笔记

  • 7
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值