数据结构之:字符串

来源:互联网 发布:windows svn 备份恢复 编辑:程序博客网 时间:2024/05/28 05:16

一、简介

字符串或者说串(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;}



原创粉丝点击