数据结构之:字符串

一、简介

字符串或者说串(String)是由数字、字母。下划线组成的一串字符。一般可以记为s="a0a1a2a3...an" (n>=0并且n是有限非负整数)。

从数据结构上来看,用c++来说,字符串是一种特殊的线性表,也就是里面的每个元素都是字符的一种线性表。可以是用数组实现,或者链表实现。具体的优缺点可以参照数组和链表的优缺点。

二、c++中的字符串string

而在c++中的string(头文件为string),其中保存的变量的char *,也就是一个不定长的字符数组,因为它重载了[]运算符,可以像数组一样去用下标访问元素;也可以说是一个链表,因为本质就是指针操作。但是其实内部实现是根据大小去调整string的大小的。我先贴出关于常用的一些string函数的声明:(没有考虑空间申请失败的情况)

#include <iostream>

using namespace std;

class String {
	private :
		char *s_data;
		int size;
	public :
		//构造函数和析构函数 
		String();
		String(const String &);
		String(const size_t, const char);
		~String();
		//一些属性函数 
		size_t length();  //返回长度 
		bool empty();     //是否为空 
		const char *c_str();    //返回指向开头的char指针
		//运算符重载
		friend String operator+ (const String &, const String &);
		friend String operator+ (const String &);
		friend String operator== (const String &, const String &);
		friend String operator!= (const String &, const String &);
		friend String operator< (const String &, const String &);
		friend String operator<= (const String &, const String &);
		friend String operator>= (const String &, const String &);
		friend String operator> (const String &, const String &);
		char &operator[] (const size_t);
		String &operator= (const String &);
		//一些串操作
		String substr(size_t, size_t); //返回两个size_t间的子串 
		String& append(const String&); //添加 
		String& insert(size_t, const String&); // 插入 
		String& assign(const String&, size_t, size_t); //替换 
		String& erase(size_t, size_t); //删除 
};

其实实现也不是很难的,我觉得比较核心的是关于长度的变换这一部分,所以我只贴出构造函数和+号重载的实现:

String::String() {
	Length = 0;
	s_data = NULL;
}

String::String(const size_t length, const char c) {
	this -> Length = length;
	s_data = new char[length + 1]; //加一的目的是为了在最后添加一个结束标志\0 
	s_data[length] = '\0';
	strset(s_data, c); 
}

String::String(const String& str) {
	Length = str.Length;
	s_data = new char[Length];
	strcpy(s_data, str.s_data);
}

String operator+(const String& s1, const String& s2) {
	String s;
	s.Length = s1.Length + s2.Length;
	s.s_data = new char[s.Length + 1];
	strcpy(s.s_data, s1.s_data);
	strcat(s.s_data, s2.s_data);
	return s;
}

其中上面涉及的str开头的函数,是C标准库里面的<string.h>或者<cstring>里面的函数,是对char数组进行的一系列操作,所以string实际上是为了把char数组变成一种更加方便使用的一种对象,通过重载操作符,能做到像int,float这种数据类型的一些操作,同时又保留着char数组下标访问的特性,能直接用s[i]的形式去访问某一元素,而且也是在常数时间内就完成的。所以用完string,会有一种不再想用char*的感觉。

不过话说回来,如果对<string.h>里面的一些函数不了解的话,我建议先回去把这部分的学一学,如果需要的话,我可以把<string.h>的自己实现的一个头文件给你参考一下= =(你不嫌弃的话)。


三、字符串的模式匹配

模式匹配(Pattern matching)

-一个目标对象T(字符串)

-模式P(字符串)

在目标T中寻找一个给定的模式P的过程

例如:文本编辑时的特定词、句的查找;DNA信息的提取等等

简单来讲,就是给定你一大段字符串,然后查看里面是否存在某个子串,例如"abc"。

解决匹配问题的算法:朴素算法(Brute Force)和KMP算法(Knuth-Morrit-pratt)等等


*朴素算法

例如给定一个字符串 T="abcdabcdabcdef"

然后寻找在T中是否存在一个模式P=“abcdef"

所以朴素算法是指在T中一位一位得开始寻求匹配,例如第一步是:

abcdabcdabcdef

abcdef

这时候发现不匹配,然后就继续从T的第二位开始寻求匹配:

abcdabcdabcdef

abcdef

发现不匹配,就这样一直下去,一直到:

abcdabcdabcdef

abcdef

发现这时候匹配了模式P,所以存在,然后返回T中匹配的开头的下标。

这个就是朴素算法,像个大傻一样一个一个的挪,挪到合适的位置。分析一下,上面的例子肯定得嵌套两层的循环才能做到这样的遍历,所以时间复杂度肯定是在O(T * P)

假如这个字符串T很长很长,例如1W+个字符,而且模式P也有还几千个字符,那不就很麻烦?所以这就是朴素的局限性。下面会讲到KMP算法,这个就是一个比较简单的匹配的算法。


*KMP算法

-KMP是一种不回溯的匹配算法,也就是当T子串t不匹配模式P的是否,但是却存在模式P的子串p和t的子串t'匹配的话,假如能消除这个冗余的操作的话,就能大大加快速度。

-KMP算法就是确定这样的一个情况,确定应该右移多少位。

-而且KMP右移k位的k值仅仅依赖于模式P的本身,和T无关。


如下直接推荐一个写KMP算法的博客,写得很好!

传送门:KMP算法


以下是KMP算法代码,主要分为构造next数组,已经主体的循环框架:(没测试过的)

#include <iostream>
#include <string>

using namespace std;

int KMPStrMatching(string T, string P, int *N, int start) {
	int j = 0;
	int i = start;
	int tLen = T.length();
	int pLen = P.length();
	if (tLen - start < pLen) {
		return -1;
	}
	while (j < pLen && i < tLen) {
		if (j == -1 || T[i] == P[j]) {
			i++;
			j++;
		} else {
			j = N[j];
		}
	}
	if (j >= pLen) {
		return (i - pLen);
	} else {
		return -1;
	}
}

int findNext(string P) {
	int j, k;
	int m = P.length();
	int *next = new int[m];
	next[0] = -1;
	j = 0;
	k = -1;
	while (j < m - 1) {
		while (k >= 0 && P[k] != P[j]) {
			k = next[k];
		}
		j++;
		k++;
		next[j] = k;
	}
	return next;
}



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值