一、串的相关定义
- 串的定义:串是由零个或多个组成的有序序列,又叫字符串。
- 串的长度:串中字符的数目称为串的长度。
- 空串:由零个字符组成的串叫做空串,空串不包括任何字符,其长度为零。
- 子串:串中任意个连续的字符组成的子序列称为该串的子串,空串是任何串的子串。
- 主串:包含子串的串相应的称为主串。
例:假设有两个串 a 和 b,如果 a 中可以找到几个连续字符组成的串与 b 完全相同,则称 a 是 b 的主串,b 是a 的子串。例如,若 a = “hello world”,b = “world”,由于 a 中也包含 “world”,因此串a 和串 b是主串和子串的关系;
二、串的实现
1. 定长顺序存储:实际上就是用普通数组(又称静态数组)存储
2. 堆分配存储:用动态数组存储字符串;
3. 块链存储:用链表存储字符串;
2.1 定长顺序存储
- 串的定长顺序存储结构,可以简单地理解为采用 “固定长度的顺序存储结构” 来存储字符串,因此限定了其底层实现只能使用静态数组。
- 使用定长顺序存储结构存储字符串时,需结合目标字符串的长度,预先申请足够大的内存空间。
我们在这里使用 定长顺序存储 存储 “hello world" 这个字符串,加上尾字符,一个11个
#include <stdio.h>
#include <string.h>
int main(void)
{
char arr[11] = "hello world";
printf ("size: %ld string: %s\n",strlen(arr), arr);
return 0;
}
结果:
size: 11 string: hello world
2.2 堆分配存储
使用动态数组,优势就是长度可变
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int main(void)
{
char *str1, *str2;
int length1, length2;
int i;
str1 = (char *) malloc (11 * sizeof(char)); //str1
strcpy (str1, "hello world");
length1 = strlen (str1);
printf ("str1 string: %s\n",str1);
str2 = (char *) malloc (5 * sizeof(char)); //str2
strcpy (str2, " yes!");
length2 = strlen (str2);
printf ("str2 string: %s\n",str2);
if (length1 < length1 + length2) { //扩展str1
str1 = realloc (str1, (length1 + length2 + 1) * sizeof (char));
}
for (i = length1; i < length1 + length2; i++) { //合并两个字符串到str1
str1[i] = str2[i - length1];
}
str1[i] = '\0';
printf ("new string: %s\n",str1);
free (str1);
free (str2);
return 0;
}
结果:
str1 string: hello world
str2 string: yes!
new string: hello world yes!
2.3 链表存储
2.3.1 定义数据类型
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
typedef char datatype;
typedef struct strlist_t {
datatype *data; //数据域
struct strlist_t *next; //指针域
}strlist;
2.3.2 初始化串
strlist *InitStrList (datatype *data) //初始化链串
{
strlist *str = NULL;
str = malloc (sizeof (strlist));
str->next = NULL;
str->data = malloc (sizeof (datatype) * SIZE);
memcpy (str->data, data, sizeof (datatype) * SIZE);
printf ("str1 : %s\n",str->data);
return str;
}
2.3.3 尾插子串
int InsertStr (strlist *str, datatype *data) //尾插子串
{
strlist *list = NULL;
strlist *q = NULL;
list = malloc (sizeof (strlist));
list->next = NULL;
list->data = malloc (sizeof (datatype) * SIZE);
memcpy (list->data, data, sizeof (datatype) * SIZE);
for(q = str; q->next != NULL; q = q->next);
q->next = list;
q->next->next = NULL;
printf ("add str : %s\n", list->data);
return 0;
}
2.3.4 打印串
int displaystr (strlist *str) //打印串
{
strlist *q = NULL;
for(q = str; q != NULL; q = q->next) {
printf ("%s",q->data);
}
printf ("\n");
return 0;
}
2.3.5 测试
int main (void) //块链存储
{
strlist *StrList = NULL;
StrList = InitStrList ("test ");
InsertStr (StrList, "ok!");
displaystr (StrList);
return 0;
}
结果:
str1 : test
add str : ok!
test ok!
三、字符串匹配
3.1 BF算法
全名 Brute-Force 是一个纯暴力算法,使用穷举依次比对,如下:
// str1 主串 str2 模式串
int str_mate (char *str1, char *str2)
{
int i = 0, j = 0;
while (i < strlen (str1) && j < strlen (str2)) {
if (str1[i] == str2[j]) {
printf ("匹配: %c \n", str1[i]);
i++;
j++;
} else {
printf ("不配: %c \n", str2[j]);
i = i - j + 1;
j = 0;
}
}
if (j == strlen (str2)) {
return i - strlen (str2) + 1; //返回匹配的位置
}
return 0;
}
int main (void) //普通模式匹配
{
int num;
num = str_mate ("abcdacba", "acb");
printf ("第%d个位置\n", num);
return 0;
}
测试如下
3.2 KMP算法
Knuth-Morris-Pratt 字符串查找算法,简称为 KMP算法,常用于在一个文本串 S 内查找一个模式串 P 的出现位置。
3.2.1 寻找前缀后缀最长公共元素长度
KMP算法的核心,是一个被称为部分匹配表(Partial Match Table)的数组,简称PMT (前缀后缀最长公共元素长度)
- 前缀:除最后一个字符外,一个字符串的全部头部组合。例如:“Hello” 的前缀包括 { “H” , “He” , “Hel” , “Hell”};
- 后缀:除了第一个字符外,一个字符串的全部尾部组合。例如:"OkHe"的后缀包括 {“e” , “He” , “kHe”};
所以上述前缀后缀最长公共元素长度为: “He”
假设模式串 “abaabcac”,则
3.2.2 Next数组
next 数组考虑的是除当前字符外的最长相同前缀后缀,相当于把PMT整体后移一位,前一位为 0 的,由 -1 替代。
求next数组的过程完全可以看成字符串匹配的过程,即以模式字符串为主字符串,以模式字符串的前缀为目标字符串,一旦字符串匹配成功,那么当前的next值就是匹配成功的字符串的长度。具体来说,就是从模式字符串的第一位(注意,不包括第0位)开始对自身进行匹配运算。 在任一位置,能匹配的最长长度就是当前位置的next值。
void GetNextval(char* p, int next[])
{
int k;
int i = 0, j = -1;
next[0] = -1;
while (i < (int)strlen(p)) {
if (j == -1 || p[i] == p[j]) {
++i;
++j;
next[i] = j;
//printf ("i: %d %d \n",i, next[i]);
} else {
//printf ("j: %d %d \n",j, next[j]);
j = next[j];
}
}
printf ("next 数组:\n");
for (k = 0; k < strlen(p); k++) {
printf ("[%d]:%d ",k, next[k]);
}
}
3.2.3 kmp实现
int KmpSearch(char* s, char* p)
{
int next[10];
int i = 0;
int j = 0;
int sLen = strlen(s);
int pLen = strlen(p);
GetNextval (p, next);
while (i < sLen && j < pLen) {
//如果j = -1,或者当前字符匹配成功(即S[i] == P[j]),都令i++,j++
if (j == -1 || s[i] == p[j]) {
i++;
j++;
} else {
//如果j != -1,且当前字符匹配失败(即S[i] != P[j]),则令 i 不变,j = next[j]
//next[j]即为j所对应的next值
j = next[j];
}
}
if (j == pLen)
return i - j;
else
return -1;
}
3.2.4 测试
int main(void)
{
int num;
num = KmpSearch ("acabaabaabcacb", "abaabcac"); //返回匹配数组下标
printf("\n匹配数组下标:%d\n",num);
return 0;
}