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看来好久。........
找了好多错,裂开!