0.目录

1.KMP 子串查找算法

2.KMP 算法的应用

3.小结

1.KMP 子串查找算法

问题:

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

朴素解法:

朴素解法的一个优化线索:

示例:

伟大的发现:

  • 匹配失败时的右移位数与子串本身相关,与目标串无关
  • 移动位数 = 已匹配的字符数 - 对应的部分匹配值
  • 任意子串都存在一个唯一的部分匹配表

部分匹配表示例:

问题:

部分匹配表是怎么得到的?

  • 前缀

    1. 除了最后一个字符以外,一个字符串的全部头部组合
  • 后缀
    1. 除了第一个字符以外,一个字符串的全部尾部组合
  • 部分匹配值
    1. 前缀和后缀最长共有元素的长度

示例:ABCDABD

问题:

  • 怎么编程产生部分匹配表?

实现关键:

  • PMT[1] = 0 ( 下标为0的元素匹配值为0 )
  • 从 2 个字符开始递推 ( 从下标为 1 的字符开始递推 )
  • 假设 PMT[n] = PMT[n-1] + 1 ( 最长共有元素的长度 )
  • 当假设不成立,PMT[n] 在 PMT[n-1] 的基础上减小

编程产生部分匹配表:

(ll代表longest length,即最长共有元素的长度。推导过程遵循下列原则:

(1). 当前欲求的ll值,通过历史ll值推导。

(2). 当可选ll值为0时,直接比对首尾元素。

在求ababax的最后一项ll值时,

前缀为aba b,

后缀为aba x。

重叠部分的长度就是当前的ll值,即:3;PMT(3)的含义是查找3个字符时的ll值,而3个字符时的ll值对应着下标为2的情形;编程实现时注意长度与下标的对应关系。)

#include <iostream>
#include <cstring> using namespace std; int* make_pmt(const char* p)
{
int len = strlen(p);
int* ret = static_cast<int*>(malloc(sizeof(int) * len)); if( ret != NULL )
{
int ll = 0; ret[0] = 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 main()
{
int* pmt_1 = make_pmt("ababax"); cout << "ababax:" << endl;
for(int i=0; i<strlen("ababax"); i++)
{
cout << i << " : " << pmt_1[i] << endl;
}
cout << endl; int* pmt_2 = make_pmt("ABCDABD"); cout << "ABCDABD:" << endl;
for(int i=0; i<strlen("ABCDABD"); i++)
{
cout << i << " : " << pmt_2[i] << endl;
} return 0;
}

运行结果为:

ababax:
0 : 0
1 : 0
2 : 1
3 : 2
4 : 3
5 : 0 ABCDABD:
0 : 0
1 : 0
2 : 0
3 : 0
4 : 1
5 : 2
6 : 0

部分匹配表的使用 ( KMP 算法 ):

实现KMP算法:

#include <iostream>
#include <cstring> using namespace std; int* make_pmt(const char* p)
{
int len = strlen(p);
int* ret = static_cast<int*>(malloc(sizeof(int) * len)); if( (ret != NULL) && (len > 0) )
{
int ll = 0; ret[0] = 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)
{
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 = pmt[j-1];
} if( s[i] == p[j] )
{
j++;
} if( j == pl )
{
ret = i + 1 - pl;
break;
}
}
} free(pmt); return ret;
} int main()
{
cout << kmp("abcde", "cde") << endl;
cout << kmp("ababax", "ba") << endl;
cout << kmp("ababax", "ax") << endl;
cout << kmp("ababax", "") << endl;
cout << kmp("ababax", "ababaxy") << endl; return 0;
}

运行结果为:

2
1
4
-1
-1

2.KMP 算法的应用

思考:

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

字符串类中的新功能:

将kmp算法的代码集成到自定义字符串类中去:

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

具体实现:

int* String::make_pmt(const char* p)
{
int len = strlen(p);
int* ret = static_cast<int*>(malloc(sizeof(int) * len)); if( (ret != NULL) && (len > 0) )
{
int ll = 0; ret[0] = 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 String::kmp(const char* s, const char* p)
{
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 = pmt[j-1];
} if( s[i] == p[j] )
{
j++;
} if( j == pl )
{
ret = i + 1 - pl;
break;
}
}
} free(pmt); return ret;
}

子串查找 ( KMP 算法的直接运用 ):

  • int indexOf(const char* s) const
  • int indexOf(const String& s) const

子串查找:

public:
int indexOf(const char* s) const;
int indexOf(const String& s) const;

具体实现:

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);
}

在字符串中将指定的子串删除:

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

在字符串中将指定的子串删除:

public:
String& remove(int i, int len);
String& remove(const char* s);
String& remove(const String& s);

具体实现:

String& String::remove(int i, int len)
{
if( (0 <= i) && (i < m_length) )
{
int n = i;
int m = i + len; while( (n < m) && (m < m_length) )
{
m_str[n++] = m_str[m++];
} m_str[n] = '\0';
m_length = n;
} 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 实现字符串间的减法操作

    1. 字符串自身不被修改
    2. 返回产生的新串

字符串的减法操作定义:

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

具体实现:

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& replace(const char* t, const char* s)
  • String& replace(const String& t, const char* s)
  • String& replace(const char* t, const String& s)
  • String& replace(const String& t, const String& s)

字符串中的子串替换:

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

具体实现:

String& String::replace(const char* t, const char* s)
{
int index = indexOf(t); if( index >= 0 )
{
remove(t);
insert(index, s);
} return *this;
} String& String::replace(const String& t, const char* s)
{
return replace(t.m_str, s);
} String& String::replace(const char* t, const String& s)
{
return replace(t, s.m_str);
} String& String::replace(const String& t, const String& s)
{
return replace(t.m_str, s.m_str);
}

从字符串中创建子串:

  • String sub(int i, int len) const

    1. 以 i 为起点提取长度为 len 的子串
    2. 子串提取不会改变字符串本身的状态

从字符串中创建子串:

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

具体实现:

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;
}
else
{
THROW_EXCEPTION(IndexOutOfBoundsException, "Parameter i is invalid ...");
} return ret;
}

3.小结

  • 部分匹配表是提高子串查找效率的关键
  • 部分匹配值定义为前缀后缀最长共有元素的长度
  • 可以用递推的方法产生部分匹配表
  • KMP 利用部分匹配值与子串移动位数的关系提高查找效率
  • 字符串类是工程开发中必不可少的组件
  • 字符串中应该包含常用字符串操作函数
    1. 增 : insert , operator + , ...
    2. 删 : remove , operator - , ...
    3. 查 : indexOf , ...
    4. 改 : replace , ...

最终的自定义字符串类代码:

StString.h

#ifndef STSTRING_H
#define STSTRING_H #include "Object.h" namespace StLib
{ class String : public Object
{
protected:
char* m_str;
int m_length; void init(const char* s);
bool equal(const char* l, const char* r, int len) const; static int* make_pmt(const char* p);
static int kmp(const char* s, const char* p);
public:
String();
String(char c);
String(const char* s);
String(const String& s); int length() const;
const char* str() const;
bool startWith(const char* s) const;
bool startWith(const String& s) const;
bool endOf(const char* s) const;
bool endOf(const String& s) const;
String& insert(int i, const char* s);
String& insert(int i, const String& s);
String& trim();
int indexOf(const char* s) const;
int indexOf(const String& s) const;
String& remove(int i, int len);
String& remove(const char* s);
String& remove(const String& s);
String& replace(const char* t, const char* s);
String& replace(const String& t, const char* s);
String& replace(const char* t, const String& s);
String& replace(const String& t, const String& s);
String sub(int i, int len) const; char& operator [] (int i);
char operator [] (int i) 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;
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) 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 = (char c); ~String();
}; } #endif // STSTRING_H

StString.cpp

#include <cstring>
#include <cstdlib>
#include "StString.h"
#include "Exception.h" using namespace std; namespace StLib
{ int* String::make_pmt(const char* p)
{
int len = strlen(p);
int* ret = static_cast<int*>(malloc(sizeof(int) * len)); if( (ret != NULL) && (len > 0) )
{
int ll = 0; ret[0] = 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 String::kmp(const char* s, const char* p)
{
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 = pmt[j-1];
} if( s[i] == p[j] )
{
j++;
} if( j == pl )
{
ret = i + 1 - pl;
break;
}
}
} free(pmt); return ret;
} 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 String object ...");
}
} String::String()
{
init("");
} String::String(char c)
{
char s[] = {c, '\0'}; init(s);
} String::String(const char *s)
{
init(s ? s : "");
} String::String(const String &s)
{
init(s.m_str);
} int String::length() const
{
return m_length;
} const char* String::str() const
{
return m_str;
} 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); 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)
{
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)); 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(NoEnoughMemoryException, "No memory to insert string value ...");
}
}
}
else
{
THROW_EXCEPTION(IndexOutOfBoundsException, "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--; 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 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);
} String& String::remove(int i, int len)
{
if( (0 <= i) && (i < m_length) )
{
int n = i;
int m = i + len; while( (n < m) && (m < m_length) )
{
m_str[n++] = m_str[m++];
} m_str[n] = '\0';
m_length = n;
} 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());
} String& String::replace(const char* t, const char* s)
{
int index = indexOf(t); if( index >= 0 )
{
remove(t);
insert(index, s);
} return *this;
} String& String::replace(const String& t, const char* s)
{
return replace(t.m_str, s);
} String& String::replace(const char* t, const String& s)
{
return replace(t, s.m_str);
} String& String::replace(const String& t, const String& s)
{
return replace(t.m_str, s.m_str);
} 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;
}
else
{
THROW_EXCEPTION(IndexOutOfBoundsException, "Parameter i is invalid ...");
} return ret;
} char& String::operator [] (int i)
{
if( (0 <= i) && (i < m_length) )
{
return m_str[i];
}
else
{
THROW_EXCEPTION(IndexOutOfBoundsException, "Parameter i is invalid ...");
}
} char String::operator [] (int i) const
{
return (const_cast<String&>(*this))[i];
} bool String::operator == (const String& s) const
{
return (strcmp(m_str, s.m_str) == 0);
} bool String::operator == (const char* s) const
{
return (strcmp(m_str, s ? s : "") == 0);
} bool String::operator != (const String& s) const
{
return !(*this == s);
} bool String::operator != (const char* s) const
{
return !(*this == s);
} bool String::operator > (const String& s) const
{
return (strcmp(m_str, s.m_str) > 0);
} bool String::operator > (const char* s) const
{
return (strcmp(m_str, s ? s : "") > 0);
} bool String::operator < (const String& s) const
{
return (strcmp(m_str, s.m_str) < 0);
} bool String::operator < (const char* s) const
{
return (strcmp(m_str, s ? s : "") < 0);
} bool String::operator >= (const String& s) const
{
return (strcmp(m_str, s.m_str) >= 0);
} bool String::operator >= (const char* s) const
{
return (strcmp(m_str, s ? s : "") >= 0);
} bool String::operator <= (const String& s) const
{
return (strcmp(m_str, s.m_str) <= 0);
} bool String::operator <= (const char* s) const
{
return (strcmp(m_str, s ? s : "") <= 0);
} String String::operator + (const String& s) const
{
return (*this + s.m_str);
} 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 String& s)
{
return (*this = *this + s.m_str);
} String& String::operator += (const char* s)
{
return (*this = *this + s);
} 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::operator = (const String& s)
{
return (*this = s.m_str);
} 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 value ...");
}
} return *this;
} String& String::operator = (char c)
{
char s[] = {c, '\0'}; return (*this = s);
} String::~String()
{
free(m_str);
} }

数据结构开发(14):KMP 子串查找算法的更多相关文章

  1. 第四十一课 KMP子串查找算法

    问题: 右移的位数和目标串没有多大的关系,和子串有关系. 已匹配的字符数现在已经有了,部分匹配值还没有. 前六位匹配成功就去查找PMT中的第六位. 现在的任务就是求得部分匹配表. 问题:怎么得到部分匹 ...

  2. 字符串类——KMP子串查找算法

    1, 如何在目标字符串 s 中,查找是否存在子串 p(本文代码已集成到字符串类——字符串类的创建(上)中,这里讲述KMP实现原理) ? 1,朴素算法: 2,朴素解法的问题: 1,问题:有时候右移一位是 ...

  3. 第41课 kmp子串查找算法

    1. 朴素算法的改进 (1)朴素算法的优化线索 ①因为 Pa != Pb 且Pb==Sb:所以Pa != Sb:因此在Sd处失配时,子串P右移1位比较没有意义,因为前面的比较己经知道了Pa != Sb ...

  4. 【数据结构】 字符串&KMP子串匹配算法

    字符串 作为人机交互的途径,程序或多或少地肯定要需要处理文字信息.如何在计算机中抽象人类语言的信息就成为一个问题.字符串便是这个问题的答案.虽然从形式上来说,字符串可以算是线性表的一种,其数据储存区存 ...

  5. KMP字符串查找算法

    #include <iostream> #include <windows.h> using namespace std; void get_next(char *str,in ...

  6. 串、串的模式匹配算法(子串查找)BF算法、KMP算法

    串的定长顺序存储#define MAXSTRLEN 255,//超出这个长度则超出部分被舍去,称为截断 串的模式匹配: 串的定义:0个或多个字符组成的有限序列S = 'a1a2a3…….an ' n ...

  7. 数据结构与算法之PHP查找算法(哈希查找)

    一.哈希查找的定义 提起哈希,我第一印象就是PHP里的关联数组,它是由一组key/value的键值对组成的集合,应用了散列技术. 哈希表的定义如下: 哈希表(Hash table,也叫散列表),是根据 ...

  8. 数据结构和算法(Golang实现)(26)查找算法-哈希表

    哈希表:散列查找 一.线性查找 我们要通过一个键key来查找相应的值value.有一种最简单的方式,就是将键值对存放在链表里,然后遍历链表来查找是否存在key,存在则更新键对应的值,不存在则将键值对链 ...

  9. 数据结构和算法(Golang实现)(27)查找算法-二叉查找树

    二叉查找树 二叉查找树,又叫二叉排序树,二叉搜索树,是一种有特定规则的二叉树,定义如下: 它是一颗二叉树,或者是空树. 左子树所有节点的值都小于它的根节点,右子树所有节点的值都大于它的根节点. 左右子 ...

随机推荐

  1. 在腾讯ubuntu云服务器上面部署asp.net core 2.1网站

    微软以后的政策肯定是在开源和跨平台这一块,所以最近在学习asp.net core 2.1,查看市面上面大部分的把asp.net core部署在Linux后,决定亲自实验一番,不操作不知道,居然最新版本 ...

  2. 开源项目CIIP(企业信息管理系统框架).2018.1.0910版更新介绍-上周工作总结

    又狂撸了一周的代码.简化了0904版本的多数操作. 上一次更新时,总共需要10步,这次简化成3步.嗯嗯,自我感觉不错. 重要的:在创建项目时,可以选择常用模块啦! 第一步:启动CIIP.Designe ...

  3. nginx解析漏洞,配置不当,目录遍历漏洞环境搭建、漏洞复现

    nginx解析漏洞,配置不当,目录遍历漏洞复现 1.Ubuntu14.04安装nginx-php5-fpm 安装了nginx,需要安装以下依赖 sudo apt-get install libpcre ...

  4. react学习(一)组件

    react这个东西,说实话,我刚刚接触一个月不到.感觉这玩意很颠覆我以前的前端开发 比方说,可能,整个项目,并没有一个html文件 比方说,以前我们写前端代码,分的清清楚楚,html里面就是放dom, ...

  5. php_package v2.7发布了 宋正河作品

    php_package 是一个面向过程的底层开发框架 http://download.csdn.net/download/songzhengdong82/4974123 欢迎大家下载

  6. Python之NMAP详解

    一.NMAP简介 NMap,也就是Network Mapper,最早是Linux下的网络扫描和嗅探工具包. nmap是一个网络连接端扫描软件,用来扫描网上电脑开放的网络连接端.确定哪些服务运行在哪些连 ...

  7. 如何在unix系统中用别的用户运行一个程序?

    1.问题的缘由 实际开发系统的时候,经常需要用别的用户运行一个程序.比如,有些系统为保证系统安全,不允许使用root来运行.这里,我们总结了unix系统下如何解决这个问题的一些方法.同时,我们还讨论如 ...

  8. 微信小程序-帝国cms会员系统调用

    在用户->管理会员字段,增加如下字段:openidsession_keylsktokennicknameheadimg设置用户名长度然后,在系统,系统变最设置,用户设置,将注册用户名设置长度改成 ...

  9. 从零开始的Python学习Episode 21——socket基础

    socket基础 网络通信要素: A:IP地址   (1) 用来标识网络上一台独立的主机 (2) IP地址 = 网络地址 + 主机地址(网络号:用于识别主机所在的网络/网段.主机号:用于识别该网络中的 ...

  10. 剑指 Offer——连续子数组的最大和

    1. 题目 2. 解答 初始化 sum=0,然后遍历数组进行累加.如果 sum 变为负数,也就说再继续累加的话贡献为负,我们需要更新 sum=0,重新开始累加. 初始化 max_sum 为数组的第一个 ...