【数据结构C++邓俊辉】Chapter11 串

1 串匹配

1.1 蛮力匹配法——版本1
int match(char* P, char* T) {  // P是匹配串(模式位复位),T是被匹配串(文本串)
	size_t n = strlen(T), i = 0;
	size_t m = strlen(P), j = 0;
	while (j < m && i < n) {
		if (T[i] == P[j])
		{  
			i++;
			j++;
		}
		else {  // 文本串回退,模式位复位
			i = i - j + 1;
			j = 0;
		}
	}
	// i-j是匹配开始位置,若无匹配,i=n+1, j = 0, i-j > n
	return i - j;  
}
1.2 蛮力匹配法——版本2
  • 意思与版本1相同,只不过内for循环的判断语句 【if(T[i+j] != P[j])】 设计的有意思
    int match(char* P, char* T) {
    	size_t n = strlen(T), i = 0;
    	size_t m = strlen(P), j;
    	for (int i = 0; i < n-m+1; i++) {
    		for (j = 0; j < m; j++)
    			if (T[i+j] != P[j])  break;
    		if (j >= m)  break;
    	}
    	// 若i>n-m则说明无匹配
    	return i;
    }
    
1.3 next表
  • 若模式P经适当右移之后,能够与T的某一(包括T[i])子串完全匹配,则一项必要条件是:【其中t是模式串下一轮比对的位置】

    P[0, t) = T[i - t, i)
    P[0, t) = P[j - t , j) 【这个条件是重点】
    即:在P[0, j)中长度为t的真前缀,应与长度为t的真后缀完全匹配

    N(P, j) = {0 <= t <j | P[0, t) = P(j - t, j)}

  • 为使P与T的对齐位置绝不倒退(即index变大),同时不遗漏任何可能的匹配,应在集合N[ P, j) 中挑选最大的t。

    next[j] = max( N(P, j) )

1.4 KMP算法

1)匹配,文本串与模式串的指针向前移动1个字符,判断下一字符是否匹配;
2)模式串已无字符与当前字符匹配,文本串与模式串的指针向前移动1个字符,相当于模式串整体向前移动一个1个字符,判断下一字符是否匹配;
综上二种情况均为:i++; j++;

int match(char* P, char* T) {  // P是匹配串(模式位复位),T是被匹配串(文本串)
	int* next = buildNext(P);
	size_t n = strlen(T), i = 0;
	size_t m = strlen(P), j = 0;
	while (j < m && i < n) {
		// 若匹配,或P已移出最左侧(两个判断的次序不可交换)
		if ( 0 > j || T[i] == P[j])
		{  
			i++;
			j++;
		}
		else 
			// 保持失配位置不动,回退模式串的指针
			j = next[j];
	}
	delete [] next;
	
	return i - j;  
}
1.4.1 next[0] = -1
  • 若在某一轮比对中首对字符即失配,则应将P直接右移一个字符,然后启动下一轮比对。此时i = i + 1; j = j + 1;

    策略:令next[j] = -1

1.4.2 next[j+1]

【这种构造方法的核心是真前缀等于真后缀】

  • 由于next[j] = t,则必有next[j+1] <= next[j] + 1。
    证明如下:
    在这里插入图片描述

  • 当且仅当P[j] = P[t]时等号成立,即next[j+1] = next[j] + 1

  • 当P[j] != P[t]时,next[j+1] 的候选者 应该依次是next[next[j] ] + 1,next[next[next[j] ] ] + 1,…

    即只需反复用t = next[t],直至P[j] = P[t]时,令next[j+1] = next[t] + 1
    Q:为什么不是 next[j+1] = t + 1?
    A:若next[j+1] = t + 1,即P[0, t+1) = P[(j+1) - (t+1), j+1) , 即P[t] = P[j],与条件矛盾!

1.4.3 构造next表

【这种构造方法的核心是真前缀等于真后缀】

int* buildNext(char* P) {
	size_t m = strlen(P), j = 0;
	int* N = new int[m];  // next表
	// 说明整体向右移动一个位置,开启下一阶段的匹配
	int t = N[0] = -1;  
	while(j < m-1) {
		if (0 > t || P[j] == P[t]) {
			j++;
			t++;
			N[j] = t; // 此句可改进
		}
		else
			t = N[t];
	}
	return N;
}
1.4.4 改进next表

上述构造next表的方法核心是真前缀等于真后缀

  • 缺点是:与有可能进行多次不必要的比对

  • 原因:不仅需要匹配“成功”的经验,还需要匹配失败的“教训”。
    在这里插入图片描述

  • 改进:把“负面”信息引入next表,即除“真前缀等于真后缀”,还需要满足“当前字符不匹配”:

    N(P, j) = { 0 <= t <j | P[0, t) = P(j - t, j) 且} P[t] != P[j] }

int* buildNext(char* P) {
	size_t m = strlen(P), j = 0;
	int* N = new int[m];  // next表
	// 说明整体向右移动一个位置,开启下一阶段的匹配
	int t = N[0] = -1;  
	while(j < m-1) {
		if (0 > t || P[j] == P[t]) {
			j++;
			t++;
			N[j] = (P[j] != P[t] ? t : N[t]); // 此句可改进
		}
		else
			t = N[t];
	}
	return N;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值