数据结构C语言描述7(图文结合)--串的实现与BP算法、KMP算法讲解与模版提供

前言

  • 这个专栏将会用纯C实现常用的数据结构和简单的算法;
  • 有C基础即可跟着学习,代码均可运行;
  • 准备考研的也可跟着写,个人感觉,如果时间充裕,手写一遍比看书、刷题管用很多,这也是本人采用纯C语言实现的原因之一;
  • 欢迎收藏 + 关注,本人将会持续更新。

文章目录

串的定义

串:是一种特殊的线性表,它是由字符组成的,是由零个或多个字符组成的有限序列。一般记为:

S = "a~1~a~2~a~3~......a~n~" (n≥0)

如:A= "BEIJING", B= "JING"

串相关概念

  • 空串(null string):长度为零的串,它不包含任何字符。
  • 空格串(black string):任由一个或多个空格组成的串,长度大于等于1
  • 子串(sub string):串中任意个连续字符的组成的子序列称为该串的"子串"
  • 主串(master string):包含子串的串相应的称为"主串",因此子串是主串的一部分
  • 串长度:当前字符串的元素个数,不包含字符串结束标记\0
  • 前缀:除了最后一个字符以外,一个字符串的全部头部组合
  • 后缀:除了第一个字符以外,一个字符串的全部尾部组合

串常见操作

串的操作不局限于插入、删除元素,而是各种查找,替换的操作,串的数据对象限制为字符 ,并且一般以串作为整体进行操作,主要有以下操作:

  • 创建串
  • 插入子串
  • 查找子串
    • BF匹配
    • KMP匹配
  • 删除串
    • 区间删除
    • 删除子串
  • 求串长度
  • 串连接
  • 串比较
  • 串拷贝

串代码的实现

创建串

cpp 复制代码
typedef struct String {
	char* data;
	int size;
}String;

String* create_string(const char* str) 
{
	if (str == NULL) {
		return NULL;
	}

	int len = strlen(str);

	String* string = (String*)calloc(1, sizeof(String));
	assert(string);

	string->data = (char*)calloc(1, sizeof(char) * len);
	assert(string->data);


	for (int i = 0; i < len; i++) {
		string->data[i] = str[i];
	}

	string->size += len;

	return string;
}

万金油函数

cpp 复制代码
// 万金油函数
bool empty(String* str)
{
	assert(str);
	return str->size == 0;
}
int size(String* str)
{
	assert(str);

	return str->size;
}

插入

插入方式有很多,我这里实现在pos位置后插入,实现思路也很多,我这里的思路是:

  • 重新申请一块大内存,可以存放插入的字符串;
  • 先讲在插入位置后的字符串,全部移到最后面;
  • 后一个一个进行插入。
cpp 复制代码
// 向后插入, pos 从0开始
void insert(String* str, int pos, const char* data)
{
	assert(str);
	assert(str->data);

	// 获得插入数据大小
	int len = strlen(data);
	
	// 扩容
	char* new_data = (char*)realloc(str->data, sizeof(char) * str->size + len);
	assert(new_data);
	str->data = new_data;   // 保证realloc申请内存失败而导致原来数据为空

	// 规则:如果pos大于字符串长度,或者无效,则插入末尾
	if (pos >= str->size || pos < 0) {
		for (int i = 0; i < len; i++) {
			str->data[str->size++] = data[i];
		}
	}
	else {
		// 先腾位置
		int st = pos + len + 1;
		int i = pos + 1;
		for(; st < str->size + len; st++) {
			str->data[st] = str->data[i++];
		}

		for (int i = 0; i < len; i++) {
			str->data[pos + 1 + i] = data[i];
		}
	}

	str->size += len;
}

删除

我这里也是实现一个简单粗暴的方法,从新省钱内存,将满足条件的字符串一个一个插入

cpp 复制代码
// 简单方法:重新申请内存, 左闭右开
void erase(String* str, int begin, int end)
{
	assert(str);

	int dSzie = end - begin;
	int count = str->size - dSzie;
	char* temp = (char*)calloc(count, sizeof(char));
	assert(temp);

	int index = 0;
	for (int i = 0; i < str->size; i++) {
		if (i >= begin && i < end) {
			continue;
		}
		else {
			temp[index++] = str->data[i];
		}
	}

	free(str->data);
	str->data = temp;
	str->size = count;
}

BF算法与模版

BF算法,即暴力(Brute Force)算法,是普通的模式匹配算法,BF算法的思想就是将目标串S的第一个字符与模式串T的第一个字符进行匹配,若相等,则继续比较S的第二个字符和 T的第二个字符;若不相等,则比较S的第二个字符和T的第一个字符,依次比较下去,直到得出最后的匹配结果,时间复杂度O(n^2)

实现:

cpp 复制代码
int bf_string(String* str1, String* str2)
{
	assert(str1);
	assert(str2);

	int i = 0, j = 0;

	while (i < str1->size && j < str2->size) {
		int t = i;
		if (str1->data[i] == str2->data[j]) {
			i++;
			j++;
		}
		else {
			i = t + 1;
			j = 0;
		}

		if (j == str2->size) {
			return i - j;
		}
	}
	return -1;
}

KMP算法与模版

KMP算法是一种改进的字符串匹配算法,由D.E.Knuth,J.H.Morris和V.R.Pratt提出的,因此人们称它为克努特---莫里斯---普拉特操作(简称KMP算法)。

KMP算法的核心是利用匹配失败后的信息,尽量减少模式串与主串的匹配次数以达到快速匹配的目的。

参考资料:[代码随想录KMP](代码随想录 (programmercarl.com))

部分匹配表

KMP算法确实很绕啊,我感觉一定要结合图像进行学习,才能掌握,这里推荐:

我这里讲一下前缀和后缀匹配,方便理解:

  • 字符串ABCDABD,其实KMPnext数组就是代表第i个字符有多少前缀和最开始字符串相匹配(当然也有前移1位和后移1位,但是本质一样,就是前后缀匹配)。

求解过程:

子串 前缀 后缀 部分匹配值
A 空集 空集 0
AB A B 0
ABC A,AB C,BC 0
ABCD A,AB,ABC D,CD,BCD 0
ABCDA A,AB,ABC,ABCD A,DA,CDA,BCDA 1
ABCDAB A,AB,ABC,ABCD,ABCDA B,AB,DAB,CDAB,BCDAB 2
ABCDABD A,AB,ABC,ABCD,ABCDA,ABCDAB D,BD,ABD,DABD,CDABD,BCDABD 0

代码模版:

cpp 复制代码
void getNext(int next[], String* str)
{
	assert(str);

	int j = -1;
	next[0] = j;

	for (int i = 1; i < str->size; i++) {
		while (j >= 0 && str->data[i] != str->data[j + 1]) {
			j = next[j];
		}
		if (str->data[i] == str->data[j + 1]) {
			j++;
		}
		next[i] = j;
	}
}

int kmp_string(String* str1, String* str2)
{
	assert(str1);
	assert(str2);

	int* next = (int*)calloc(str2->size, sizeof(int));
	assert(next);

	getNext(next, str2);

	int j = -1, i = 0;
	for (; i < str1->size; i++) {
		if (str1->data[i] == str2->data[j + 1]) {
			j++;
		}
		else if (j >= 0) {
			j = next[j];
		}

		if (j == str2->size - 1) {
			free(next);
			return i - j;
		}
	}


	free(next);
	return -1;
}

总代码

c 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <assert.h>
#include <string.h>

typedef struct String {
	char* data;
	int size;
}String;

String* create_string(const char* str) 
{
	if (str == NULL) {
		return NULL;
	}

	int len = strlen(str);

	String* string = (String*)calloc(1, sizeof(String));
	assert(string);

	string->data = (char*)calloc(1, sizeof(char) * len);
	assert(string->data);


	for (int i = 0; i < len; i++) {
		string->data[i] = str[i];
	}

	string->size += len;

	return string;
}

// 万金油函数
bool empty(String* str)
{
	assert(str);
	return str->size == 0;
}
int size(String* str)
{
	assert(str);

	return str->size;
}

// 向后插入, pos 从0开始
void insert(String* str, int pos, const char* data)
{
	assert(str);
	assert(str->data);

	// 获得插入数据大小
	int len = strlen(data);
	
	// 扩容
	char* new_data = (char*)realloc(str->data, sizeof(char) * str->size + len);
	assert(new_data);
	str->data = new_data;   // 保证realloc申请内存失败而导致原来数据为空

	// 规则:如果pos大于字符串长度,或者无效,则插入末尾
	if (pos >= str->size || pos < 0) {
		for (int i = 0; i < len; i++) {
			str->data[str->size++] = data[i];
		}
	}
	else {
		// 先腾位置
		int st = pos + len + 1;
		int i = pos + 1;
		for(; st < str->size + len; st++) {
			str->data[st] = str->data[i++];
		}

		for (int i = 0; i < len; i++) {
			str->data[pos + 1 + i] = data[i];
		}
	}

	str->size += len;
}

void print_string(String* str)
{
	assert(str);

	for (int i = 0; i < str->size; i++) {
		printf("%c", str->data[i]);
	}
	printf("\n");
}

// 简单方法:重新申请内存, 左闭右开
void erase(String* str, int begin, int end)
{
	assert(str);

	int dSzie = end - begin;
	int count = str->size - dSzie;
	char* temp = (char*)calloc(count, sizeof(char));
	assert(temp);

	int index = 0;
	for (int i = 0; i < str->size; i++) {
		if (i >= begin && i < end) {
			continue;
		}
		else {
			temp[index++] = str->data[i];
		}
	}

	free(str->data);
	str->data = temp;
	str->size = count;
}

int bf_string(String* str1, String* str2)
{
	assert(str1);
	assert(str2);

	int i = 0, j = 0;

	while (i < str1->size && j < str2->size) {
		int t = i;
		if (str1->data[i] == str2->data[j]) {
			i++;
			j++;
		}
		else {
			i = t + 1;
			j = 0;
		}

		if (j == str2->size) {
			return i - j;
		}
	}
	return -1;
}
void erase_bf_string(String* str1, String* str2) 
{
	assert(str1);
	assert(str2);

	int begin = bf_string(str1, str2);
	assert(begin != -1);

	int end = begin + str2->size;

	erase(str1, begin, end);
}

void getNext(int next[], String* str)
{
	assert(str);

	int j = -1;
	next[0] = j;

	for (int i = 1; i < str->size; i++) {
		while (j >= 0 && str->data[i] != str->data[j + 1]) {
			j = next[j];
		}
		if (str->data[i] == str->data[j + 1]) {
			j++;
		}
		next[i] = j;
	}
}

int kmp_string(String* str1, String* str2)
{
	assert(str1);
	assert(str2);

	int* next = (int*)calloc(str2->size, sizeof(int));
	assert(next);

	getNext(next, str2);

	int j = -1, i = 0;
	for (; i < str1->size; i++) {
		if (str1->data[i] == str2->data[j + 1]) {
			j++;
		}
		else if (j >= 0) {
			j = next[j];
		}

		if (j == str2->size - 1) {
			free(next);
			return i - j;
		}
	}


	free(next);
	return -1;
}

int main()
{
	String* string = create_string("IamYXZ!!");

	print_string(string);

	insert(string, 5, "loveLt");

	print_string(string);

	erase(string, 3, 8);

	print_string(string);

	String* str2 = create_string("am");
	printf("%d\n", bf_string(string, str2));
	erase_bf_string(string, str2);
	print_string(string);
	

	String* str3 = create_string("abcffabcdabcdehf");
	String* str4 = create_string("abcdabc");

	printf("%d\n", kmp_string(str3, str4));

	
	return 0;
}

KMP,next数组,左移动一位

  • 对比的时候以 j + 1与 i 对比
  • 除了-1外,下标 j 的值表示的是前 j 的个相同前缀最大数量
相关推荐
_nirvana_w_2 分钟前
深入探索 C++ 编程技巧:从案例中学习高效实践
c++·学习·rpc
FranYeCisco20 分钟前
C++基础:操作符
数据结构·c++·算法
c++初学者ABC38 分钟前
蓝桥杯算法训练 黑色星期五
c++·算法·蓝桥杯
逆袭之路6661 小时前
c++死锁调试 ,gdb pstack
c++
a0023450011 小时前
冒泡排序与快速排序以及用快速排序的思想改进我们的冒泡排序
c语言
十年一梦实验室1 小时前
【C++】sophus : test_macros.hpp 用于单元测试的宏和辅助函数 (四)
开发语言·c++·单元测试
二闹1 小时前
青训营试题算法解析十九
后端·算法
集大周杰伦1 小时前
C语言指针与数组深入剖析及优化示例 指针解读 数组与指针的关系
c语言·c
不想当程序猿_2 小时前
【蓝桥杯每日一题】扫描游戏——线段树
c++·算法·蓝桥杯·线段树·模拟
老猿讲编程2 小时前
在 macOS 下安装和使用 Clang Static Analyzer
c++·macos