第二周,算法与数据结构

本文详细介绍了KMP算法,一种用于提高字符串匹配效率的方法,通过计算next数组避免回溯,相比于暴力法大大减少了比较次数。文中给出了KMP算法的步骤、next数组的求解过程以及在实际编程中的应用示例。
摘要由CSDN通过智能技术生成

1.KMP

详解:

kmp算法运用于字符串匹配时。

原始字符串匹配方法(暴力)

例如:有A串:abaababaab     (主串)下标用i

           有B串:ababa           (模式串)下标用j

暴力法想法就是从模式串中一一比较,如果不匹配的,i和j都需要回溯,

int fun(char* a, char* b)//a为主串,b为模式串
{
	int i = 0, j = 0;
	while (j < strlen(b)&&i<strlen(a))
	{
		if (a[i] == a[j])
		{
			i++; j++;
		}
		else
		{
			i = i - j + 1;
			j = 0;
		}
	}
	if (j == strlen(b))
		return i = i - j + 1;
	else return -1;

}

这里的暴力法就发现会有很多次主串下标的回溯,所以我们可以通过kmp算法,主串的下标不回溯,进行匹配。

kmp:

第一步:先找next数组(next数组就是算如果模式串中 j 当前位置不匹配,那么下标应该回溯到next【j】,再次进行匹配,如果还不匹配,那末应该再次找到新的next【j】,并回溯,直到回溯到模式串中的一个位置 j 能与元素匹配,或者直到最后也没找到一个相等的,此时j==-1;所以就进行跳出i位置的字符,因为没有一个前缀元素匹配。

分析:next【j】的含义在模式串 0~j-1位上,最长的前缀与后缀值。 其实分析可知,这个next数组只与模式串的前面位置的元素有关,与当前位置元素无关。

例如:ababa  next数组应该是-1  0  0   1  2

           ababc  next数组应该是-1  0   0   1  2

那么怎么求next数组

因为next数组是最长的前缀与后缀相等的长度,所以我们可以通过前一个next【j-1】进行计算next【j】,即如果第一次 j 处模式串的值与next【j-1】处模式串的值匹配相等时,就是前缀与后缀相等的长度直接加1,即next【j-1】+1. 如果不相等,那么进行j回溯前一个

代码:

void getNext(char* t, int * next)   
{                                   
	int i = 1, j = 0;
	next[0] = -1;
	next[1] = 0;
	while (i < strlen(t))           
	{
		if (j == -1 || t[i] == t[j]) 
		{
			i++;j++;
				next[i] = j;
		}   
		else {
			j = next[j];   
		}
	}
}

第二步:进行匹配。

#include <stdio.h>
#include <string.h>
void getNext(char* t, int * next)   //j代表了最长相同字符串的下标      在第一个if语句里面只有找到相同的才会找下一个next元素
{                                    //i代表的是模式串的下标 
	int i = 1, j = 0;
	next[0] = -1;
	next[1] = 0;
	while (i < strlen(t))            //这里的i要小于strlen模式串,即把t(模式串)找完了,即next完成了
	{
		if (j == -1 || t[i] == t[j])  //j==-1时(后面next【0】赋值的,代表的是后面一直没有找到相同的字符。
		{
			i++;j++;
			//if (t[i] != t[j])   
				next[i] = j;
			//else next[i] = next[j];               //这里next数组就是找到前面有0~j下标的匹配,所以next就是前一次前后缀最长的长度。
		}   
		else {
			j = next[j];    //这里代表的是t【i】不等于t【j】所以进行找他前面字符串中相等的字符
		}
	}
}
//
//
//
int KMP(char* s, char* T,int *next)   //s是母串  i代表的母串的下标,不会回溯,j是模式串的下标,如果不匹配就回溯next数组的值,再次进行匹配
{
	
	int i = 0;
	int j = 0;
	int M = strlen(s);
	int N = strlen(T);
	while (i < M && j < N )
	{
		if (-1==j||s[i] == T[j])
		{
			i++;
			j++;
		}
		else 
		{
			j = next[j];   			
		}
		
	}

	if (j>=N)
		  return i-j+1; 
	else return -1;
}
int main()
{
	char s[100];
	char t[100];
	while (1)
	{
		scanf("%s %s",s,t); 
		int next[100];
		getNext(t, next);
		int k = KMP(s, t,next);
		printf("pipei:%d\n", k);
		for (int i = 0; i < strlen(t); i++)
			printf("%d ", next[i]);
		

	}
	return 0;
}

这里有一个特别的问题(俩个大问题)

1.在KMP函数中while循环中不能用j<strlen(t)判断,因为j可能等于负一,直接跳出循环,(这里特别容易出错,并且你很难找出这个问题。

2.在kmp函数中用主串和模式串匹配,用i下标和j下标分别代表。在get_next函数中下标,i代表模式串从1开始,因为从满足了第一个条件后,i就会++,所以直接求出next【2】,前俩个因为是固定的,j从0开始。

代码中有一个next数组的进阶版,是为了减少循环次数,直接算出,不匹配下标回溯到的位置。

这里的代码之所以会有很多涉及-1是因为满足条件后会自加1,最根本的原因还是因为这里的next数组中j下标的值与模式串j-1有关,与j无关,所以都是算前一位。

这里一道题:

解题:

#include <stdio.h>
#include <string.h>
char s1[1000001], s2[1000001];
	int next[1000001];
int main()
{
	
	scanf("%s%s", s1, s2);
	int len1 = strlen(s1);
	int len2 = strlen(s2);
	next[0] = -1;
	int i = 0, j = -1;
	while (s2[i] != '\0')
	{
		if (-1 == j || s2[i] == s2[j])
		{
			i++; j++;
			next[i] = j;
		}
		else j = next[j];
	}

	i = 0; j = 0;
	while (i < len1)
	{
		if (-1 == j || s1[i] == s2[j])
		{
			i++; j++;
		}
		else j = next[j];
		if (j == len2)
		{
			printf("%d\n", i - j + 1);
			j = next[j];
		}
	}
	for (i = 0; i < len2; i++)
		printf("%d ", next[i + 1]);
	return 0;
}

这里的next数组代表的是后一位的前缀与后缀的最长长度,所以最后输出的时next【i+1】,因为算next数组时会算出next的最后一位,相当于最后一位的后一位不匹配返回的下标。

还有一道:

这里也可以用kmp算法

直接看解法:

#include <stdio.h>
#include <string.h>
int main()
{
	int n, t;
	char s1[1000];
	int next[1000];
	scanf("%d%d", &n, &t);
	scanf("%s", s1);
	//next
	next[0] = -1;
	int i = 0, j = -1;
	while (s1[i] != '\0')
	{
		if (-1 == j || s1[i] == s1[j])
			next[++i] = ++j;
		else
			j = next[j];

	}
	
	for (int i = 0; i <n; )
	{
		printf("%c", s1[i]);
		i++;
		if (i == n)
		{
			i = next[i]; 
			t--;
			if (t <= 0)break;
		}
	}

	

	return 0;
}

还是很有象征性的题。

2。链表

上一周用正式的链表,结果超时了

这时用了数组来代表链表的数据结构,可以快速的直接访问。

题目:

代码:

#include <stdio.h>

int a[1000002];
int main()
{
	int x, y, z, q;
	scanf("%d", &q);
	while (q--)
	{
		scanf("%d%d", &z, &x);
		if (z == 1)
		{
			scanf("%d", &y);
			a[y] = a[x];  
			a[x] = y;
		}
		else if (z == 2)
		{
			printf("%d\n", a[x]);
		}
		else if (z == 3)
		{
			a[x] = a[a[x]];    
		}

	}
	return 0;
}

这里的完全时数据结构。这周收获最大的还是kmp看来好久。........

找了好多错,裂开!

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值