参考链接: C++ 查找和替换子字符串

一、字符串类的创建

问题提出:C语言不支持真正意义上的字符串

C语言使用字符数组(“\0”结束)和一组函数实现字符串操作

C语言不支持自定义类型,因此无法获得字符串类型。

C++中的原生类型系统中是否包含字符串类型?——No!

C++通过库来支持字符串类型——标准库。

STL和Qt都有自己实现的字符串类型,会出现不兼容的情况。

所以我们可以自己实现字符串类来作为一个通用的库来使用。

通过面向对象的技术来对C语言中字符串相关函数进行封装,实现代码复用。

class String : public Object

{

protected:

char* m_str;

int m_length;

void init(const char* s);

public:

String();

String(const char* s); //重载构造函数

String(const String& s);  //拷贝构造函数

int length() const;   //字符串长度

const char* str() const;  //自定义字符串类与C语言字符串进行转换

String(char c);   //重载构造函数

bool operator ==(const String& s) const;

bool operator ==(const char* s) const;

bool operator !=(const String& s) const;

bool operator !=(const char* s) const;

bool operator >(const String& s) const;

bool operator >(const char* s) const;

bool operator <(const String& s) const;

bool operator <(const char* s) const;

bool operator >=(const String& s) const;

bool operator >=(const char* s) const;

bool operator <=(const String& s) const;

bool operator <=(const char* s) const;

String operator +(const String& s) const;

String operator +(const char* s) const;

String& operator +=(const String& s) ;

String& operator +=(const char* s) ;

String& operator =(const String& s);

String& operator =(const char* s); //赋值操作符的重载

String& operator =(const char c);  //赋值操作符的重载

~String();   //析构函数

};在具体的实现前,要注意放在同一个名称空间中,同时注意对C库的包含。

#include <cstring>

#include <cstdlib>

构造函数的实现:

void String::init(const char *s)

{

m_str = strdup(s);

if( m_str)

{

m_length = strlen(m_str) ;

}

else

{

THROW_EXCEPTION(NoEnoughMemoryException,"No memory to create Sting object...");

}

}

String::String()

{

init("");

}

String::String(const char* s)

{

init(s ? s : ""); //如果s为空指针,则将空指针转换为空字符串

}

String::String(const String& s)

{

init(s.m_str);

}

String::String(char c)

{

char s[] = {c,'\0'};

init(s);

}

int  String::length() const

{

return m_length;

}

const char* String::str() const

{

return m_str;

}

操作符重载是自定义字符串类类型最重要的部分,并且需要进行重载。(返回值为STRING类型的引用,目的是为了实现链式操作)

第一次针对自定义字符串类类型,

第二种针对C中的字符串。

操作符重载只列出下面几个函数的实现,其余请读者自行实现:

String String::operator +(const char* s) const

{

String ret;

int len = m_length + strlen(s ? s : "");

char* str = reinterpret_cast<char*>(malloc(len + 1));

if( str )

{

strcpy(str, m_str);

strcat(str,s ? s : "");

free(ret.m_str);

ret.m_str = str;

ret.m_length =len;

}

else

{

THROW_EXCEPTION(NoEnoughMemoryException,"No memory to add String values...");

}

return ret;

}

String& String::operator =(const char* s)

{

if( m_str != s )

{

char* str = strdup(s ? s : "");

if(str)

{

free(m_str);

m_str = str;

m_length = strlen(m_str);

}

else {

THROW_EXCEPTION(NoEnoughMemoryException,"No memory to assign new String...");

}

}

}

实现自定义字符串类类型中的常用成员函数:

char& operator [] (int i);  //操作符重载函数,访问指定下标的字符

char operator [](int i)const;

bool startWith(const char* s)const;  //判断字符串是否以s开头

bool startWith(const String& s)const;

bool endOf(const char* s)const;  //判断字符串是否以s结束

bool endOf(const String& s)const;

String& insert(int i, const char* s);  //在字符串的位置i处插入s

String& insert(int i, const String& s);

String& trim();   //去掉字符串两端的空白

分别对上述成员函数进行实现:

char& String::operator [] (int i)  //非const对象,返回值为引用,可以出现在赋值操作符的左边

{

if( (0 <= i) && (i < m_length))

{

return m_str[i];

}

else

{

THROW_EXCEPTION(InvalidOperationException,"Parameter i is invalid...");

}

}

char String::operator [](int i)const

{

return (const_cast<String&>(*this))[i];

}

在实现字符串开头、结尾比较的函数前,可以实现一个字符串相等与否的函数:

bool String::equal(const char* l, const char* r, int len)const

{

bool ret = true;

for(int i=0; i<len && ret; i++)

{

ret = ret && (l[i] == r[i]);

}

return ret;

}

这样就可以通过两个字符串以及需要比较的长度信息进行下面的函数实现:

bool String::startWith(const char* s)const

{

bool ret = ( s != NULL);

if(ret)

{

int len = strlen(s);

ret = (len < m_length) && equal(m_str, s, len);

}

return ret;

}

bool String::startWith(const String& s)const

{

return startWith(s.m_str);

}

bool String::endOf(const char* s)const

{

bool ret = ( s != NULL);

if(ret)

{

int len = strlen(s);

char* str = m_str + (m_length - len) ;  //指针运算,最后n个字符起始的下标

ret = (len < m_length) && equal(str, s, len);

}

return ret;

}

bool String::endOf(const String& s)const

{

return endOf(s.m_str);

}插入函数也有两个版本,一个是字符串对象,一个是字符指针。

String& String::insert(int i, const char* s)//返回值为STRING类型的引用,目的是为了实现链式操作

{

if( ( 0 <= i) && ( i <= m_length))

{

if( (s != NULL) && ( s[0] != '\0') )

{

int len =strlen(s);

char* str = reinterpret_cast<char*>(malloc(m_length + len + 1));//强制类型转换;为什么加1?: \0

if( str != NULL)

{

strncpy(str, m_str, i);

strncpy(str+i, s , len);

strncpy(str+i+len, m_str+i, m_length - i);

str[m_length  +len] = '\0';

//**更新字符串数据**//

free(m_str);

m_str = str;

m_length = m_length + len;

}

else

{

THROW_EXCEPTION(InvalidOperationException,"Parameter i is invalid...");

}

}

}

else

{

THROW_EXCEPTION(InvalidOperationException,"Parameter i is invalid...");

}

return *this;

}

String& String::insert(int i, const String& s)

{

return insert(i,s.m_str);

}

去掉字符串两端的空白字符:

String& String::trim()

{

int b = 0;

int e = m_length - 1;

while (m_str[b] == ' ')  b++;

while (m_str[e] == ' ')  e--;

cout<< b <<endl;

cout<< e <<endl;

if( b == 0)  //前端无空格

{

m_str[e + 1] = '\0';

m_length = e + 1;

}

else

{

for(int i=0,j=b; j<=e;i++,j++)

{

m_str[i] = m_str[j];

}

m_str[e-b+1] = '\0';  //添加结束符

m_length = e-b+1;

}

return *this;

}

测试代码:

int main()

{

String s = "   willing   ";

if( s.trim().insert(0, "good").endOf("willing") && s.startWith("good"))

{

cout<< s.str() <<endl;

}

return 0;

}

思考:如何在目标字符串中查找是否存在指定的子串?

二、KMP子串查找算法

如何在目标字符串S中,查找是否存在子串P?

朴素算法:

朴素算法是可以想到的最简单的算法,通过不断的进行每个字符的匹配来对整个字符串进行检查,但是时间复杂度高,效率比较低。

朴素算法中存在的一个优化线索:

伟大的发现:

匹配失败时的右移位数与子串本身相关,而与目标串是无关的;

移动位数 = 已匹配的字符数 - 对应的部分匹配值;

任意子串都存在一个唯一的部分匹配表。

所以如何获取部分匹配表呢?

先看部分匹配值是怎么求得的。

前缀:除了最后一个字符外,一个字符串的全部头部组合;

后缀:除了第一个字符以外,一个字符串的全部尾部组合;

部分匹配值:前缀和后缀最长共有元素的长度。

怎样通过编程产生部分匹配表呢?

实现关键:(贪心算法)

注:贪心算法:将优化问题转换成这样的一个问题:即先做出选择,再解决剩下的一个子问题;

即在贪心算法中,先做出总是当前看似最佳的选择,然后再解决选择之后所出现的子问题。

先通过编程实现部分匹配表的实现:

注意下面一张图的逻辑,第五步,当进行匹配的时候,ll=3,前缀后缀都右移一位,发现匹配不上。这个时候!注意!注意方框中重叠的部分,根据上一级的ll值,得知能够匹配上的最大长度,然后根据最大长度,对右移的元素再进行匹配,PMT(3)= ll` = 1,然后再进行匹配,发现仍然匹配不上,然后ll`` = 0。

此处的3,来源于已经匹配的最大长度。

关键在于,匹配失败之后进行的操作,即将已经匹配的元素作为“种子”再进行扩展。

int* make_pmt(const char* p) //建立指定字符串的部分匹配表      O(n)

{

int len = strlen(p);

int* ret = static_cast<int*>(malloc(sizeof(int)* len)); //指向部分匹配表的指针

if( ret != NULL)

{

int ll = 0;  //前缀后缀交集的最大长度

ret[0] = 0;   //长度为1的字符串,ll值为0

for(int i=1; i<len; i++)

{

while( ( ll > 0) && (p[ll] != p[i]) )//不成立的时候

{

ll = ret[ll-1];  //正确理解这行代码:已经匹配好的子串中重新进行匹配

}

if( p[ll] == p[i])  //意味着扩展  //成立的时候

{

ll++;

}

ret[i] = ll;

}

}

return ret;

}

通过上述代码,可以得出目标字符串的部分匹配值。

知道了部分匹配值后,就可以进行匹配中的右移操作:

编程实现:

int kmp(const char* s, const char* p) //  O(m) + O(n)  ==> O(m+n)

{

int ret = -1;

int sl = strlen(s);

int pl = strlen(p);

int* pmt = make_pmt(p);

if( (pmt != NULL) && (0 < pl) && (pl < sl))  //判断是否可以进行查找

{

for(int i=0, j=0; i<sl; i++)

{

while ( (j > 0) && (s[i] != p[j]))  //不相等的话,改变j(子串)的值;结束条件(j=0)

{

j= pmt[j-1];  //得出右移的位数

}

if( s[i] == p[j])  //理想的情况   (贪心算法的体现)

{

j++;

}

if( j == pl ) //结束条件

{

ret = i+1-pl;  //匹配成功后,i的值在最后一个匹配成功的字符上(下标),用这个下标减去pl+1,得到开始匹配成功时的下标。

break;

}

}

}

free(pmt);  //释放内存

return ret;

}

函数返回匹配成功后字符串的下标。

测试代码:

int main()

{

cout<< kmp("ABCDE","CD") <<endl;

return 0;

}

运行结果:

部分匹配表是提高子串查找效率的关键;

部分匹配值定义为前缀和后缀共有元素的长度;

可以用递推的方法产生部分匹配表;

KMP利用部分匹配表和子串移动位数的关系来提高查找效率。

三、KMP算法的应用

如何在目标字符串中查找是否存在指定的子串?——增加新的成员函数进行封装。

static int* make_pmt(const char* p);

static int kmp(const char* s, const char* p);

增加以下成员函数的声明:

int indexOf(const char* s) const;  //查找子串s在字符串中的位置

int indexOf(const String& s) const;

String& remove(const char* s);  //将字符串中的子串s删除

String& remove(const String& s);

String& remove(int i, int len);  //自定义删除操作

String sub(int i, int len)const;  //从字符串中创建子串

String operator -(const String& s) const;  //定义字符串减法

String operator -(const char* s) const;

String& operator -=(const String& s) ;

String& operator -=(const char* s) ;

String& replace(const char* t, const char* s);  //将字符串中的子串s替换为t

String& replace(const String& t, const char* s);

String& replace(const char* t, String& s);

String& replace(String&, String& s);

具体实现(KMP算法的直接运用):

//查找子串s在字符串中的位置

int String::indexOf(const char* s) const

{

return kmp(m_str, s ? s : "");

}

int String::indexOf(const String& s) const

{

return kmp(m_str, s.m_str);

}

删除操作:

根据KMP在目标字符串中查找子串的位置;

通过子串位置和子串长度进行删除。

String& String::remove(int i, int len)  //删除第i个元素开始的len个元素

{

if( (0 <= i) && ( i < m_length))

{

int n = i;

int m = i+ len;

//cout<< n <<endl;

while ( (n<m) && (m<m_length))

{

m_str[n++] = m_str[m++];

}

m_str[n] = '\0';

m_length = n;

//cout<< n <<endl;

}

else

{

cout<< "No such String in current String"<<endl;

}

return *this;

}

String& String::remove(const char* s)

{

return remove(indexOf(s), s ? strlen(s):0);

}

String& String::remove(const String& s)

{

return remove(indexOf(s), s.length());

}

字符串的减法操作operator-:

使用remove()实现字符串间的减法操作:

字符串本身不被修改;

返回新的子串。

String  String::operator -(const String& s) const

{

return String(*this).remove(s);  //直接调用构造函数产生新的临时字符串对象,就可以不改变原先的字符串

}

String  String::operator -(const char* s) const

{

return String(*this).remove(s);

}

String&  String::operator -=(const String& s)  //原字符串被修改

{

return remove(s);

}

String&  String::operator -=(const char* s)

{

return remove(s);

}

字符串的子串替换:

String& String::replace(const char* t, const char* s)

{

int index = indexOf(t);   //Step 1

if( index >= 0)

{

remove(t);            //Step 2

insert(index, s);    //Step 3

}

else

{

cout<< "No such String in current String"<<endl;

//THROW_EXCEPTION(InvalidOperationException,"No such String in current String");

}

return *this;

}

String& String::replace(const String& t, const char* s)

{

return replace(t.m_str,s);

}

String& String::replace(const char* t, String& s)

{

return replace(t,s.m_str);

}

String& String::replace(String& t, String& s)

{

return replace(t.m_str,s.m_str);

}

从字符串创建子串:

以i为起点提取长度为len的子串;

子串提取不会改变字符串本身的状态。

String String::sub(int i, int len)const

{

String ret;  //构造新的字符串

if( (0 <= i) && (i < m_length))

{

if( len < 0)  len = 0;

if(len+i > m_length)

len = m_length - i;   //归一化 防止越界

char* str = reinterpret_cast<char*>(malloc(len + 1));

strncpy(str, m_str + i, len);

str[len] = '\0';

ret = str;

free(str);

}

else

{

THROW_EXCEPTION(IndexOutOfBoundsException,"Parameter i is invalid ...");

}

return ret;

}

字符串类的实现,一定要包含常见字符串的操作:增删查改。

代码的复用很重要!

代码的复用很重要!

代码的复用很重要!

[转载] 五、字符串类的实现及子串查找算法相关推荐

  1. python rfind函数用法_Python语法速查:字符串格式简单处理、子串查找与判断方法?...

    这是一篇python基础知识分享型文章,对学习python感兴趣的朋友们可以仔细看看 字符串常用方法 Python3中,字符串全都用Unicode形式,所以省去了很多以前各种转换与声明的麻烦.字符串属 ...

  2. (1.4.5)字符串类

    1以下那个不是标准的输入输出通道 2字符串循环移位左旋转问题 算法1杂技代码 算法2块交换 算法3求逆推荐 扩展google面试题用线性时间和常数附加空间将一篇文章的所有单词倒序 2求字符串的连续出现 ...

  3. 企业财务制度二--(五)损益类科目 5101 主营业务收入(转载)

    企业财务制度二--(五)损益类科目 5101 主营业务收入 5101 主营业务收入 一.本科目核算企业在销售商品.提供劳务及让渡资产使用权等日常活动中所产生的收入. 二.企业应按以下规定确认营业收入实 ...

  4. 企业财务制度二--(五)损益类科目 5801 以前年度损益调整(转载)

    企业财务制度二--(五)损益类科目 5801 以前年度损益调整 5801 以前年度损益调整 一.本科目核算企业本年度发生的调整以前年度损益的事项.企业在年度资产负债表日至财务会计报告批准报出日之间发生 ...

  5. 企业财务制度二--(五)损益类科目 5401 主营业务成本(转载)

    企业财务制度二--(五)损益类科目 5401 主营业务成本 5401 主营业务成本 一.本科目核算企业因销售商品.提供劳务或让渡资产使用权等日常活动而发生的实际成本. 二.月度终了,应当根据本月销售各 ...

  6. Java:字符串类String的功能介绍

    在java中,字符串是一个比较常用的类,因为代码中基本上处理的很多数据都是字符串类型的,因此,掌握字符串类的具体用法显得很重要了. 它的主要功能有如下几种:获取.判断.转换.替换.切割.字串的获取.大 ...

  7. Java 核心五个类(File、Outputstream、Inputstream、Reader、Writer)一个接口(Serializable)...

    java BIO(阻塞式IO)    java.io 核心五个类(File.Outputstream.Inputstream.Reader.Writer)一个接口(Serializable) 1.Fi ...

  8. 字符串类习题、面试题详解(第二篇)

    第一篇链接:字符串类习题.面试题详解(第一篇) 6题:回文串(竞赛基础题) 输入一个字符串,求出其最长回文子串.子串的含义是:在原串中连续出现的字符串片段.回文的含义是:正着看和倒着看相同,如abba ...

  9. [转载] python 字符串包含某个字符_python字符串

    参考链接: Python字符串capitalize() str字符串 本节内容概览 1.何为str?2.转义字符3.字符串格式化4.Python字符串内建函数和操作5.python字符串练习 一.字符 ...

最新文章

  1. 揭秘物联网之城无锡鸿山的科技密码
  2. Python-EEG工具库MNE中文教程(9)-参考电极应用
  3. 吴恩达:诸位CEO,我有一本「AI转型秘籍」传授给你
  4. [置顶] Android开发者官方网站文档 - 国内踏得网镜像
  5. java和网易我的世界有什么区别_网易我的世界手机版对比正版JAVA版我的世界有什么区别?...
  6. 我和Django那些事儿(8)----相册django插件photologue,jQuery插件Slides
  7. python函数降低编程复杂度_Python-面向对象编程
  8. halcon学习之颜色与纹理
  9. 《Flutter 从0到1构建大前端应用》读后感—第6章【使用网络技术与异步编程】
  10. java编程思想快速排序_快速排序里的学问:快速排序的过程
  11. 【数字信号调制】基于matlab无线电信号调制识别【含Matlab源码 912期】
  12. matlab数字图像处理灰度变换ppt课件,数字图像处理 灰度变换.ppt
  13. java同步异步的区别
  14. latex 小白 algorithmic already defined的原因
  15. 邮件中的 请看附件 请知悉,英语怎么说 要比较正式的用语
  16. 2021CCPC网络预选赛
  17. redis实现setnx,setex连用实现分布式锁
  18. throw new JSONException(JSONObject[ + JSONUtils.quote(key) + ] is not a number.);
  19. UTM设备代表产品介绍
  20. win10开始菜单打不开,菜单栏右键无反应解决办法

热门文章

  1. MySQL出错信息查询表
  2. pdf 一键生成书签目录
  3. 【codevs1295】N皇后问题
  4. excel php 数字科学计数_excel单元格设置技巧:这些自定义格式,你用过哪些?
  5. php文本框清除格式,php如何清除HTML格式
  6. Assignment 双向队列
  7. god is a girl
  8. 2.封装成帧和透明传输
  9. [leetcode]211. 添加与搜索单词 - 数据结构设计 ---前缀树解法
  10. 递归算法设计 —— 选择排序和冒泡排序