数据结构3.1 - 串 基础

1. 串

1.1 定义

串用数组表示,由 0 或 多个字符组成

1.2 串的长度 VS 数组长度(易混淆!)

strlen(str) = 串str包含字符的个数
串的长度 = strlen(str)
数组长度 = strlen(str) + 1,1 指数组结尾 - 看不见的 ‘\0’

空格也是单个字符
多个空格组成的串 叫 ‘空格串’ ,不等于‘空串’

逻辑上:
串 是限定元素为字符的线性表
操作上不同:
串:针对串内的子串
线性表:针对某一元素

1.2 存储结构

定长 与 变长:
变长用的更广泛

typedef struct //定长 
{
	char str[max + 1];
	int length; 
}Str1;

typedef struct // 变长 
{
	char *ch;
	int length;
}Str2;

1.3 基本操作

1赋值 + 2串长 + 3串比较 + 4串连接 + 5求子串
以变长存储结构为例

#include<bits/stdc++.h>
#define max 100 
using namespace std;

typedef struct // 变长 
{
	char *ch;
	int length;
}Str;

void strsign(Str& str, char *ch)
{
	if(str.ch)
		str.ch = NULL;
	int len = 0;
	int i;
	
	char *c = ch;
	while(*c)
	{
		++len;
		c++;
	}
	if(len == 0)
	{
		str.ch = NULL;
		str.length = 0;
		cout << "为空串!" << endl << endl;
	}
	else
	{
		str.ch = (char*)malloc(sizeof(char)*(len + 1));
		if(str.ch == NULL)
		{
			cout << "分配空间失败!" << endl <<endl;
		}
		else
		{
			c = ch;
			for(i = 0; i <= len; i++, c++)
				str.ch[i] = *c;
			str.length = len;
			cout << "赋值完成!" << endl;
		}
	}
}

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

void concat(Str &s, Str s1, Str s2)
{
	int i, j;
	if(s.ch)
		s.ch = NULL;
	s.ch = (char*)malloc(sizeof(char) * (s1.length + s2.length+ 1));
	if(s.ch == NULL)
		cout << "空间分配失败" << endl << endl;
	else
	{
		for(i = 0; i < s1.length; i++)
		{
			s.ch[i] = s1.ch[i];
		}
		for(j = 0; j <= s2.length; j++)
		{
			s.ch[i+j] = s2.ch[j];
		}
		s.length = s1.length + s2.length + 1;
		cout << "串连接完成!" << endl;
	} 
}

void substring(Str& str, Str str2, int pos, int len) 
{
	int i;
	if(pos < 0 || pos >= str2.length || len > (str2.length - pos))
		cout << "位置参数有误!" << endl << endl;
	else
	{
		if(str.ch)
			str.ch = NULL;
		if(len == 0)
		{
			str.length = 0;
			cout << "为空串!" << endl << endl;
		}
		else
		{
			str.ch = (char*)malloc(sizeof(char)*(len + 1));
			for(i = 0; i < len; i++)
			{
				str.ch[i] = str2.ch[i + pos];
			}
			str.ch[i] = '\0';
			str.length = len;
		}
		
	} 
}

int main()
{
	Str str, str2, str3;
	int pos, len;
	char *ch = "asd" ; // <=> char ch[max];
	strsign(str, ch);
	cout << endl;
	
	cout << "str=" << str.ch << endl << endl;
	//trace(str);
	cout << "串长:" << str.length << endl << endl;
	
	cout << "串比较:" << endl;
	char *ch2 = "sdf";
	strsign(str2, ch2);
	cout << "str = " << str.ch << endl;
	cout << "str2 = " << str2.ch << endl;
	strcompare(str, str2);
	
	cout << "串连接:" << endl;
	concat(str3, str, str2);
	cout << "str3 = str + str2 = " << str3.ch << endl << endl;
	
	cout << "求子串:" << endl;
	pos = 0;
	len = 1;
	substring(str3, str, pos, len);
	cout << "str = " << str.ch << endl;
	cout << "pos = " << pos << ", " << "len = " << len << endl; 
	cout << "str3 = " << str3.ch << endl << endl;
	
	return 0;
}

2. 串匹配算法

2.1 简单匹配

#include<bits/stdc++.h>
#define max 100 
using namespace std;

typedef struct // 变长 
{
	char *ch;
	int length;
}Str;

void strsign(Str& str, char *ch)
{
	if(str.ch)
		str.ch = NULL;
	int len = 0;
	int i;
	
	char *c = ch;
	while(*c)
	{
		++len;
		c++;
	}
	if(len == 0)
	{
		str.ch = NULL;
		str.length = 0;
		cout << "为空串!" << endl << endl;
	}
	else
	{
		str.ch = (char*)malloc(sizeof(char)*(len + 1));
		if(str.ch == NULL)
		{
			cout << "分配空间失败!" << endl <<endl;
		}
		else
		{
			c = ch;
			for(i = 0; i <= len; i++, c++)
				str.ch[i] = *c;
			str.length = len;
			cout << "赋值完成!" << endl;
		}
	}
}

void index(Str s1, Str s2)
{
	int i = 0, j = 0, k = i;
	for(;i < s1.length && j < s2.length;)
	{
		if(s1.ch[i] == s2.ch[j])
		{
			++i;
			++j;
		}
		else
		{
			j = 0;
			i = ++k;
		}
	}
	if(j >= s2.length)
		cout << "位置:第 " << k+1 << " 个" << endl << endl;
	else
		cout << "不匹配!" << endl << endl;
}

int main()
{
	Str str1, str2, str3;
	char *ch = "ABABCABCACBAB" ; // <=> char ch[max];
	strsign(str1, ch);
	cout << endl;
	cout << "str1 = " << str1.ch << endl << endl;
	
	char *ch2 = "ABCAB";
	strsign(str2, ch2);
	cout << endl;
	cout << "str2 = " << str2.ch << endl << endl;
	
	cout << "串简单匹配:" << endl; 
	index(str1, str2);
	
	
	return 0;
}

2.2 KMP

神仙 - 手算教程:https://www.bilibili.com/video/BV1tW41157tv
数组下标从 1 开始!
next[1] = 0 永恒 不变!
next[i] 就是匹配串位置 1 - (i - 1) 的 最长公共前后缀 长度
所以 next[2] = 1 也是肯定的!

2.2.1 傻瓜式求 next[] 数组

数组下标从 1 开始!
next[1] = 0,next[2] = 1 永恒 不变!

求 next[i] 原则 ( i >= 3 )
看位置 (i - 1) 的字符 和 位置 next[i - 1] 的字符是否相等
若相等,next[i] = next[i - 1] + 1

否则,
看位置 (i - 1) 和 位置为【 next[i - 1] 对应的next[] 】= next[next[i - 1]] 的字符是否相等,
若相等,next [i] = next[next[i - 1]] + 1

否则,
位置 (i - 1) 和 位置为【next[next[i - 1]] 对应的next[]】= next[next[next[i - 1]]] 的字符是否相等

直到,回溯到 位置为 1 的时候
位置 (i - 1) 和 位置为 1 的字母是否相等
若相等,next[i] = 1 + 1 = 2

否则 next[i] = 1

例子

求 ababaaababaa 的 next[] 数组

aba
下标位置123
next[]01

求 第 3 个位置字符 ‘a’ 的 next[3] = ?
看它前一个字符是 ‘b’,对应的 next[2] = 1
位置是 1 的字符是 ‘a’,a ≠ b,且 j = 1,next[3] = 1

abab
下标位置1234
next[]011

求 第 4 个位置字符 ‘b’ 的 next[4] = ?
看它前一个字符是 ‘a’,对应的 next[3] = 1
位置是 1 的字符是 ‘a’,a = a,next[4] = 1 + 1 = 2

ababa
下标位置12345
next[]0112

求 第 5 个位置字符 ‘a’ 的 next[5] = ?
看它前一个字符是 ‘b’,对应的 next[4] = 2
位置是 2 的字符是 ‘b’,b = b,next[5] = 2 + 1 = 3

ababaaa
下标位置1234567
next[]011234

next[6] = 4 同理,略
求 第 7 个位置字符 ‘a’ 的 next[7] = ?
看它前一个字符是 ‘a’,对应的 next[6] = 4
位置是 4 的字符是 ‘b’,a ≠ b,j ≠ 1
next[4] = 2
位置是 2 的字符是 ‘b’,a ≠ b,j ≠ 1
next[2] = 1
位置是 1 的字符是 ‘a’,a = a,next[7] = 1 + 1 = 2
以此类推…

2.2.2 KMP简析

简单匹配中,1个1个字符移动匹配,成功还好,若不匹配,又要从匹配串的头部开始,感觉太慢太麻烦!
当匹配失败,我不从头开始匹配,而是从匹配串的 某个位置开始,将这个位置用数组存起来,这就是 next[]!

例子
源自:https://www.bilibili.com/video/BV1Px411z7Yo?from=search&seid=11232141016422342714

跳位置后:

2.2.3 与简单匹配的区别

  1. 有 next[] 数组
  2. 主串的 i 不需要回溯,解释:

2.2.4 (kmp / kmp改进) 与 (下标0、1不同) next[]代码对比

kmp - 小标从 1 开始:

void getnext(Str str, int next[])
{
	int length = str.length;
	int i = 1, j = 0;
	next[1] = 0;
	while(i < length-1)
	{
		if(j == 0 || str.ch[i] == str.ch[j] )
		{
			i++;j++;
			next[i] = j;
		}
		else
			j = next[j];
	}
	cout << endl;
}

从 0 开始

void getnext(Str str, int next[])
{
	int length = str.length;
	int i = 0, j = -1;
	next[0] = -1;
	while(i < length)
	{
		if( j == -1 || str.ch[i] == str.ch[j] )
		{
			i++;j++;
			next[i] = j;
		}
		else
			j = next[j];
	}
	cout << endl;
}

kmp - 改进
从 1 起:

void getnextval(Str str, int nextval[])
{
	int length = str.length;
	int i = 1, j = 0;
	int next[length];
	nextval[1] = 0;
	while(i < length-1)
	{
		if(j == 0 || str.ch[i] == str.ch[j] )
		{
			i++;j++;
			if(str.ch[i] != str.ch[j])
				nextval[i] = j;
			else
				nextval[i] = nextval[j];
		}
		else
			j = nextval[j];
	}
	cout << endl;
}

从 0 起:

void getnextval(Str str, int nextval[])
{
	int length = str.length;
	int i = 0, j = -1;
	nextval[0] = -1;
	while(i < length)
	{
		if( j == -1 || str.ch[i] == str.ch[j] )
		{
			i++;j++;
			if(str.ch[i] != str.ch[j])
				nextval[i] = j;
			else
				nextval[i] = nextval[j];
		}
		else
			j = nextval[j];
	}
	cout << endl;
}

去除 next[] 改进一下:

void getnextval(Str str, int nextval[])
{
	int length = str.length;
	int i = 0, j = 0;
	nextval[0] = 0;
	while(i < length)
	{
		if(i == 0 || ( j == 0 && str.ch[i] != str.ch[j]) )
		{
			i++;
			if(str.ch[i] != str.ch[j])
				nextval[i] = j;
			else
				nextval[i] = nextval[j];
		}
		else if( str.ch[i] == str.ch[j] )
		{
			i++;j++;
			if(str.ch[i] != str.ch[j])
				nextval[i] = j;
			else
				nextval[i] = nextval[j];
		}
		else
			j = nextval[j];
	}
	cout << endl;
}

2.2.5 下标起始不同

从 0 起 和 从 1 起,一些细节不同:

  1. 字符串创建,分配的长度多1
  2. 判断字符串结尾不同
  3. j 起始不同
  4. 其他的就很类似了!

2.2.6 代码

下标从 1 开始!
#include<bits/stdc++.h>
#define max 100 
using namespace std;

typedef struct // 变长 
{
	char *ch;
	int length;
}Str;

void strsign(Str& str, char *ch)
{
	if(str.ch)
		str.ch = NULL;
	int len = 0;
	int i;
	
	char *c = ch;
	while(*c)
	{
		++len;
		c++;
	}
	if(len == 0)
	{
		str.ch = NULL;
		str.length = 0;
		cout << "为空串!" << endl << endl;
	}
	else
	{
		str.ch = (char*)calloc((len + 2), sizeof(char));
		if(str.ch == NULL)
		{
			cout << "分配空间失败!" << endl <<endl;
		}
		else
		{
			c = ch;
			for(i = 1; i < len+2; i++, c++)
				str.ch[i] = *c;
			str.ch[i]='\0';
			str.length = len+2;
			cout << "赋值完成!" << endl;
		}
	}
}

void getnext(Str str, int next[])
{
	int length = str.length;
	int i = 1, j = 0;
	next[1] = 0;
	while(i < length-1)
	{
		if(j == 0 || str.ch[i] == str.ch[j] )
		{
			i++;j++;
			next[i] = j;
		}
		else
			j = next[j];
	}
	cout << endl;
}

void kmp(Str s1, Str s2, int next[])
{
	int i = 1, j = 1;
	for(;i < s1.length-1 && j < s2.length-1;)
	{
		if(j == 0 ||s1.ch[i] == s2.ch[j])
		{
			++i;
			++j;
		}
		else
			j = next[j];
	}
	if(j > s2.length-2)
		cout << "位置下标(从1起):" << i - (s2.length-2) << endl << endl;
	else
		cout << "不匹配!" << endl << endl;
}

int main()
{
	Str str1, str2, str3;
	int next[max]={0}, i;
	char *ch = "ABABCABCACBAB" ; // <=> char ch[max]; 主串 
	strsign(str1, ch);
	cout << endl;
	cout << "str1 = " << str1.ch+1 << endl << endl;
	
	char *ch2 = "ABCAB"; // 匹配串 
	strsign(str2, ch2);
	cout << endl;
	cout << "str2 = " << str2.ch+1 << endl << endl;
	
	cout << "KMP匹配:" << endl; 
	getnext(str2, next);
	kmp(str1, str2, next);
	
	system("pause");
	return 0;
}
下标从 0 开始!
#include<bits/stdc++.h>
#define max 100 
using namespace std;

typedef struct // 变长 
{
	char *ch;
	int length;
}Str;

void strsign(Str& str, char *ch)
{
	if(str.ch)
		str.ch = NULL;
	int len = 0;
	int i;
	
	char *c = ch;
	while(*c)
	{
		++len;
		c++;
	}
	if(len == 0)
	{
		str.ch = NULL;
		str.length = 0;
		cout << "为空串!" << endl << endl;
	}
	else
	{
		str.ch = (char*)malloc(sizeof(char)*(len));
		if(str.ch == NULL)
		{
			cout << "分配空间失败!" << endl <<endl;
		}
		else
		{
			c = ch;
			for(i = 0; i < len; i++, c++)
				str.ch[i] = *c;
			str.length = len;
			cout << "赋值完成!" << endl;
		}
	}
}

void getnext(Str str, int next[])
{
	int length = str.length;
	int i = 0, j = -1;
	next[0] = -1;
	while(i < length)
	{
		if( j == -1 || str.ch[i] == str.ch[j] )
		{
			i++;j++;
			next[i] = j;
		}
		else
			j = next[j];
	}
	cout << endl;
}

void kmp(Str s1, Str s2, int next[])
{
	int i = 0, j = 0;
	for(;i < s1.length && j < s2.length;)
	{
		if(j == 0 ||s1.ch[i] == s2.ch[j])
		{
			++i;
			++j;
		}
		else
			j = next[j];
	}
	if(j >= s2.length)
		cout << "位置下标(从0起): " << i - s2.length  << endl << endl;
	else
		cout << "不匹配!" << endl << endl;
}

int main()
{
	Str str1, str2, str3;
	int next[max]={0}, i;
	char *ch = "ABABABCABBAB" ; // <=> char ch[max]; 主串 ABABCABCACBAB
	strsign(str1, ch);
	cout << endl;
	cout << "str1 = " << str1.ch << endl << endl;
	
	char *ch2 = "ABCAB"; // 匹配串 
	strsign(str2, ch2);
	cout << endl;
	cout << "str2 = " << str2.ch << endl << endl;
	
	cout << "KMP匹配:" << endl; 
	getnext(str2, next);
	kmp(str1, str2, next);
	
	system("pause");
	return 0;
}

2.3 KMP改进

下标从 1 开始
在 KMP 算法中,当主串 和 匹配串 某位置不相等的时候,匹配串就往前跳到 next[i] 的字符位置,但有可能又不相等,又要往前跳到 next[ next[i] ] 的字符位置,我想一下子就跳对!
这里可以改进到 一下子就找到 相等的字符 的位置!
用 nextval[] 保存这些改进的位置

思路

边求 next[],边求 nextval[]
对于 (i-1) 每次回溯的位置,不是 next[next[i - 1]] 了,而是 nextval[next[i - 1]]了
现在求 nextval[2] = ?
位置 2 和 位置 next[2] 字符相比较
若相等,nextval[2] = nextval[1]
若不等,nextval[2] = next[2]

现在求 nextval[i] = ?(i > 1)
位置 i 和 位置 next[i] 字符相比较
若相等,nextval[i] = nextval[next[i]]
若不等,nextval[i] = next[i]

以 nextval[] 为我,next[] 是你
不相等是你,相等是我

2.3.1 代码

下标从 1 开始!
#include<bits/stdc++.h>
#define max 100 
using namespace std;

typedef struct // 变长 
{
	char *ch;
	int length;
}Str;

void strsign(Str& str, char *ch)
{
	if(str.ch)
		str.ch = NULL;
	int len = 0;
	int i;
	
	char *c = ch;
	while(*c)
	{
		++len;
		c++;
	}
	if(len == 0)
	{
		str.ch = NULL;
		str.length = 0;
		cout << "为空串!" << endl << endl;
	}
	else
	{
		str.ch = (char*)calloc((len + 2), sizeof(char));
		if(str.ch == NULL)
		{
			cout << "分配空间失败!" << endl <<endl;
		}
		else
		{
			c = ch;
			for(i = 1; i < len+2; i++, c++)
				str.ch[i] = *c;
			str.ch[i] = '\0';
			str.length = len+2;
			cout << "赋值完成!" << endl;
		}
	}
}

void getnextval(Str str, int nextval[])
{
	int length = str.length;
	int i = 1, j = 0;
	int next[length];
	nextval[1] = 0;
	while(i < length-1)
	{
		if(j == 0 || str.ch[i] == str.ch[j] )
		{
			i++;j++;
			if(str.ch[i] != str.ch[j])
				nextval[i] = j;
			else
				nextval[i] = nextval[j];
		}
		else
			j = nextval[j];
	}
	cout << endl;
}

void kmp(Str s1, Str s2, int nextval[])
{
	int i = 1, j = 1;
	for(;i < s1.length-1 && j < s2.length-1;)
	{
		if(j == 0 || s1.ch[i] == s2.ch[j])
		{
			++i;
			++j;
		}
		else
			j = nextval[j];
	}
	if(j >= s2.length-1)
		cout << "位置下标(从1起): " << i - (s2.length-2)  << endl << endl;
	else
		cout << "不匹配!" << endl << endl;
}

int main()
{
	Str str1, str2, str3;
	int nextval[max]={0}, i;
	char *ch = "ABABCABCACBAB" ; // <=> char ch[max]; 主串 
	strsign(str1, ch);
	cout << endl;
	cout << "str1 = " << str1.ch << endl << endl;
	
	char *ch2 = "ABCAB"; // 匹配串 
	strsign(str2, ch2);
	cout << endl;
	cout << "str2 = " << str2.ch << endl << endl;
	
	cout << "KMP匹配 - 改进:" << endl; 
	getnextval(str2, nextval);
	kmp(str1, str2, nextval);
	
	system("pause");
	return 0;
}
下标从 0 开始!
#include<bits/stdc++.h>
#define max 100 
using namespace std;

typedef struct // 变长 
{
	char *ch;
	int length;
}Str;

void strsign(Str& str, char *ch)
{
	if(str.ch)
		str.ch = NULL;
	int len = 0;
	int i;
	
	char *c = ch;
	while(*c)
	{
		++len;
		c++;
	}
	if(len == 0)
	{
		str.ch = NULL;
		str.length = 0;
		cout << "为空串!" << endl << endl;
	}
	else
	{
		str.ch = (char*)malloc(sizeof(char)*(len + 1));
		if(str.ch == NULL)
		{
			cout << "分配空间失败!" << endl <<endl;
		}
		else
		{
			c = ch;
			for(i = 0; i <= len; i++, c++)
				str.ch[i] = *c;
			str.length = len;
			cout << "赋值完成!" << endl;
		}
	}
}

void getnextval(Str str, int nextval[])
{
	int length = str.length;
	int i = 0, j = -1;
	nextval[0] = -1;
	while(i < length)
	{
		if( j == -1 || str.ch[i] == str.ch[j] )
		{
			i++;j++;
			if(str.ch[i] != str.ch[j])
				nextval[i] = j;
			else
				nextval[i] = nextval[j];
		}
		else
			j = nextval[j];
	}
	cout << endl;
}

void kmp(Str s1, Str s2, int nextval[])
{
	int i = 0, j = 0;
	for(;i < s1.length && j < s2.length;)
	{
		if(j ==0 || s1.ch[i] == s2.ch[j])
		{
			++i;
			++j;
		}
		else
			j = nextval[j];
	}
	if(j >= s2.length)
		cout << "位置下标(从0起): " << i - s2.length  << endl << endl;
	else
		cout << "不匹配!" << endl << endl;
}

int main()
{
	Str str1, str2, str3;
	int nextval[max]={0}, i;
	char *ch = "ABABCABCACBAB" ; // <=> char ch[max]; 主串 
	strsign(str1, ch);
	cout << endl;
	cout << "str1 = " << str1.ch << endl << endl;
	
	char *ch2 = "ABCAB"; // 匹配串 
	strsign(str2, ch2);
	cout << endl;
	cout << "str2 = " << str2.ch << endl << endl;
	
	cout << "KMP匹配 - 改进:" << endl; 
	getnextval(str2, nextval);
	kmp(str1, str2, nextval);
	
	system("pause");
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

qq_1403034144

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值