数据结构学习笔记——第4章 串

数据结构学习笔记——第4章 串

  • 4 串
    • 4.1 串的定义和实现
      • 4.1.1 串的定义
      • 4.1.2 串的存储结构
        • 定长顺序存储表示
        • 堆分配存储表示
        • 块链存储表示
      • 4.1.3 串的基本操作
    • 4.2 串的模式匹配
      • 4.2.1 简单的模式匹配算法
      • 4.2.2 改进的模式匹配算法——KMP算法
        • 字符串的前缀、后缀和部分匹配值
        • KMP算法的原理是什么?
      • 4.2.3 KMP算法的进一步优化

4 串

4.1 串的定义和实现

4.1.1 串的定义

  • (String)是由零个或多个字符组成的有限序列
  • 一般记为 S = ′ a 1 a 2 . . . a n ′ ( n ≥ 0 ) S='a_1a_2...a_n' (n \geq 0) S=a1a2...an(n0),其中S是串名,单引号括起来的字符序列是串的值 a i a_i ai可以是字母、数字或其他字符;串中字符的个数n称为串的长度。n=0时的串称为空串(用∅表示)
  • 串中任意个连续的字符组成的子序列称为该串的子串,包含子串的串相应地称为主串。某个字符在串中的序号称为该字符在串中的位置。子串在主串中的位置以子串的第一个字符在主串中的位置来表示
  • 当两个串的长度相等且每个对应位置的字符都相等时,称这两个串是相等
  • 由一个或多个空格(空格是特殊字符)组成的串称为空格串(注意空格串不是空串),其长度为串中空格字符的个数

4.1.2 串的存储结构

定长顺序存储表示

#define MAXLEN 255             //预定义最大串长为255
typedef struct {
	char ch[MAXLEN];           //每个分量存储一个字符
	int length;                //串的实际长度
}SString;
  • 超过预定义长度的串值会被舍去,称为截断

堆分配存储表示

typedef struct {
	char *ch;
	int length
}HString;
  • 使用malloc()和free()动态存储管理

块链存储表示

  • 利用链表来存放字符串
  • 由于串的特殊性,在具体实现时,每个结点既可以存放一个字符,也可以存放多个字符
  • 每个结点称为,整个链表称为块链结构
  • 最后一个节点占不满时通常用“#”补上
    数据结构学习笔记——第4章 串_第1张图片

4.1.3 串的基本操作

  • StrAssign(&T, chars):赋值操作。把串T赋值为chars
  • StrCompare(S, T):比较操作。若S>T,则返回值>0;若S=T,则返回值=0;若S
  • StrLength(S):求串长。返回串S的元素个数
  • SubSrting(&Sub, S, pos, len):求子串。用Sub返回串S的第pos个字符起长度为len的子串
  • Concat(&T, S1, S2):串联接。用T返回由S1和S2联接而成的新串
  • 以上五种操作构成串类型的最小操作子集
  • StrCopy(&T, S):复制操作。由串S复制得到串T
  • StrEmpty(S):判空操作。若S为空串,则返回TRUE,否则返回FALSE
  • Index(S, T):定位操作。若主串S中存在与串T值相同的子串,则返回它在主串S中第一次出现的位置;否则函数值为0
  • Replace(&S, T, V):替换子串操作。用V替换主串S中出现的所有与T相等的不重叠的子串
  • StrInsert(&S, pos, T):插入操作。在串S的第pos个字符之前插入串T
  • StrDelete(&S, pos, len):删除子串。从串S中删除第pos个字符起长度为len的子串
  • ClearString(&S):清空操作。将S清为空串
  • DestroyString(&S):销毁串。将串S销毁
  • 例如,可用判等、求串长和求子串等操作实现定位函数Index(S, T)。算法的思想为:在主串S中取从第一个字符起、长度和串T相等的子串,与串T比较,若相等则求得函数值为i,否则i值增1,直至串S中不存在和串T相等的子串为止
int Index(String S, String T) {
	int i = 1, n = StrLength(S), m = StrLength(T);
	String sub;
	while(i <= n-m+1) {
		SubString(sub, S, i, m);
		if(StrCompare(sub, T) != 0)
			i++;
		else
			return i;      //返回子串在主串中的位置
	}
	return 0;      //S中不存在与T相等的子串
}

4.2 串的模式匹配

4.2.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;      //指针后退重新开始匹配
			j = 1;
		}
	}
	if(j > T.length)      //匹配成功
		return i - T.length;
	else      //匹配失败
		return 0;
}
  • 暴力模式匹配算法的最坏时间复杂度为O(nm),其中n和m分别主串和模式串的长度

4.2.2 改进的模式匹配算法——KMP算法

字符串的前缀、后缀和部分匹配值

  • 前缀指除最后一个字符以外,字符串的所有头部子串;后缀指除第一个字符外,字符串的所有尾部子串;部分匹配值则为字符串的前缀和后缀的最长相等前后缀长度
  • 下面以主串为 a b a b c a b c a c b a b,子串为 a b c a c为例:
    • ‘a’ 的前缀和后缀都为空集,最长相等前后缀长度为0
    • ‘ab’ 的前缀为{a},后缀为{b},{a} ∩ {b} = ∅,最长相等前后缀长度为0
    • ‘abc’ 的前缀为{a, ab},后缀为{c, bc},{a, ab} ∩ {c, bc} = ∅, 最长相等前后缀长度为0
    • ‘abca’ 的前缀为{a, ab, abc},后缀为{a, cx, bca},{a, ab, abc} ∩ {a, cx, bca} = {a},最长相等前后缀长度为1
    • ‘abcac’ 的前缀为{a,ab, abc, abca},后缀为{c, ac, cac, bcac},{a,ab, abc, abca} ∩ {c, ac, cac, bcac} = ∅,最长相等前后缀长度为0
  • 故字符串 ‘abcac’ 的的部分匹配值为 0 0 0 1 0。将部分匹配值写成数组形式,就得到了部分匹配值(Partial Match,PM)的表
编号 1 2 3 4 5
S a b c a c
PM 0 0 0 1 0
  • 下面用PM表来进行字符串匹配:
    第一趟
  • 第一趟匹配过程:
    • 发现 c 与 a 不匹配,前面的2个字符 ‘ab’ 是匹配的,查表可知,最后一个匹配字符 b 对应的部分匹配值为0,因此按照下面的公式算出子串需要向后移动的位数:
    • 移 动 位 数 = 已 匹 配 的 字 符 数 − 对 应 的 部 分 匹 配 值 移动位数 = 已匹配的字符数 - 对应的部分匹配值 =
  • 因为 2 - 0 = 2,所以将子串向后移动2位,如下进行第二趟匹配:
    第二趟
  • 第二趟匹配过程:
    • 发现 c 与 b 不匹配,前面4个字符 ‘abca’ 是匹配的,最后一个匹配字符对应的部分匹配值为1
  • 4 - 1 = 3,将子串向后移动3位,如下进行第三趟匹配:
    第三趟
  • 第三趟匹配过程:
    • 子串全部比较完成,匹配成功
  • 整个匹配过程中,主串始终没有回退,故KMP算法可以在O(n+m)的时间数量级上完成串的模式匹配操作,大大提高了匹配效率

KMP算法的原理是什么?

  • 对算法的改进方法:
    • 已知: 右 移 位 数 = 已 匹 配 的 字 符 数 − 对 应 的 部 分 匹 配 值 右移位数 = 已匹配的字符数 - 对应的部分匹配值 =
    • 写成: M o v e = ( j − 1 ) − P M [ j − 1 ] Move = (j - 1) - PM[j - 1] Move=(j1)PM[j1]
    • 使用部分匹配时,每当匹配失败,就去找它前一个元素的部分匹配值,这样使用起来有些不方便,所以将PM表右移一位,就得到了next数组:
编号 1 2 3 4 5
S a b c a c
PM -1 0 0 0 1
  • 我们注意到:
    • 第一个元素右移以后空缺的用 -1 来填充,因为若是第一个元素匹配失败,则需要将子串向右移动一位,俄日不需要计算子串移动的位数
    • 最后一个元素在右移的过程中溢出,因为原来的子串中,最后一个元素的部分匹配值是其下一个元素使用的,但显然已没有下一个元素 ,故可以舍去
  • 这样,上式就改写为 M o v e = ( j − 1 ) − n e x t [ j ] Move = (j - 1) - next[j] Move=(j1)next[j]
  • 相当于将子串的比较指针 j 回退到 j = j − M o v e = n e x t [ j ] + 1 j = j - Move = next[j] + 1 j=jMove=next[j]+1
  • 有时为了使公式更加简洁、计算简单,将 next 数组整体+1。因此上述子串的next数组也可以写成
编号 1 2 3 4 5
S a b c a c
PM 0 1 1 1 2
  • 最终得到子串指针变化公式 j = n e x t [ j ] j = next[j] j=next[j]
  • 在实际匹配过程中,子串在内存里是不会移动的,而是指针在变化
  • next[j] 的含义是:在子串的第 j 个字符与主串发生失配时,则跳到子串的 next[j] 位置重新与主串当前位置进行比较
  • 设主串为 ′ s 1 s 2 . . . s n ′ 's_1s_2...s_n' s1s2...sn,模式串为 ′ p 1 p 2 . . . p m ′ 'p_1p_2...p_m' p1p2...pm
  • next 函数的公式:
    n e x t [ j ] = { 0 , j = 1 m a x { k ∣ 1 < k < j 且 ′ p 1 . . . p k − 1 ′ = ′ p j − k + 1 . . . p j − 1 ′ } , 当 此 集 合 不 空 时 1 , 其 他 情 况 next\left [ j \right ] = \begin{cases}0, j=1\\ max\left \{k|1next[j]=0,j=1max{k1<k<jp1...pk1=pjk+1...pj1},1,
  • 推理求解的科学步骤略
  • 求 next 值的程序如下:
void get_next(String 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;
			next[i] = j;      //若pi = pj,则next[j+1] = next[j]+1
		}
		else
			j = next[j];      //否则令j = next[j],循环继续
	}
}
  • KMP的匹配算法:
int Indecx_KMP(String S, String 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;
}
  • 在一般情况下,普通模式匹配的实际执行时间近似为O(m+n),因此至今仍被采用。KMP算法仅在主串与子串有很多“部分匹配”时才显得比普通算法快得多,其主要优点是主串不回溯

4.2.3 KMP算法的进一步优化

  • 若是出现 pj = pnext[j]则后续匹配必然失配,因此应当避免。如何处理?
  • 如果出现了,则需要再次递归,将 next[j]修正为 next[next[j]],直至两者不相等为止,更新后的数组命名为 nextval
  • 计算 next 算法修正值的算法如下,此时匹配算法不变:
void get_nextval(String 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];
	}
}

你可能感兴趣的:(数据结构)