文章目录
1.串的基本操作
- 串逻辑结构与线性表相似,但串操作一组数据元素,更多是查找子串位置,得到指定位置子串,替换子串操作。线性表更关注单个元素操作:增删改查一个元素
- 数据结构,串属于线性结构
- 定义:由零个或多个字符组成的有限序列
- 采用顺序存储与链式存储【不推荐,空间浪费,操作麻烦】
2. 串的BF模式【朴素的模式匹配】【暴力匹配】【定位操作】
对主串的每一个字符作为子串的开头,与要匹配的字符串进行匹配
// 普通版BF
function BF_Ordinary(sourceStr, searchStr) {
let sourceLength = sourceStr.length;
let searchLength = searchStr.length;
let padding = sourceLength - searchLength; //循环次数
// BBC ABB ABCF => 搜索9次
for (let i = 0; i <= padding; i++) {
if (searchStr.charAt(i) == searchStr.charAt(0)) {
// 匹配成功的数据
let complete = searchLength;
for (let j = 0; j < searchLength; j++) {
if (sourceStr.charAt(i + j) == searchStr.charAt(j)) {
--complete;
if (!complete) {
return i;
}
}
}
}
}
return -1;
}
you
// 优化后的BF算法:设置节流阀
function BF_Optimize_l(sourceStr, searchStr) {
let mainLength = sourceStr.length;
let searchLength = searchStr.length;
let padding = mainLength - searchLength;
for (let offset = 0; offset <= padding; offset++) {
// 设置节流阀
let match = true;
for (let i = 0; i < searchLength; i++) {
// 只要不相等就取反
if (searchStr.charAt(i) != sourceStr.charAt(offset + i)) {
match = false;
break;
}
}
if (match) return offset;
}
return -1
}
function show(bf_name, fn) {
let myDate = +new Date()
let r = fn();
let div = document.createElement('div')
div.innerHTML = bf_name + '搜索位置' + r + ",耗时" + (+new Date() - myDate) + "ms" + "<br/>";
show('BF_原始算法', function() {
return BF_Ordinary(sourceStr, searchStr)
})
show('BF_优化后的算法', function() {
return BF_Optimize_l(sourceStr, searchStr)
})
}
3.串的KMP模式匹配【定位操作】
- 现在要判断 T 是否含有 S ,如果存在,就返回第一次出现的位置,如果没有,则返回-1
- 主串:目标串; 子串:模式串
1. 规则:
主串S(指针 i 遍历),其中子串T(指针 j 遍历)在S 中匹配成功 T 中大部分 字符串,该部分字符串具有相同前缀子串【首串】(下标为 k - 1)和后缀子串【尾串】(长度 k),则在下一趟比较中,主串指针 i 不动,子串指着 j 向前回溯到 k 位置。
2. 举例:
首串和尾串可以重叠交叉 ,首串和尾串的长度应该小于该串本身,
①abc ②aaaa ③无,是空串
3. 采用以空间换时间策略,操作方法如下 :
**前两个标记下标一定是 -1 和 0**
4. 操作举例:
- 求解next : 前两个一定是 -1 和 0
由模式串球出next值:
void GetNext(SqString t,int next[])
{ int j,k;
j=0;k=-1;next[0]=-1;
while (j<t.length-1)
{ if (k==-1 || t.data[j]==t.data[k])
{ j++;k++;
next[j]=k;
}
else k=next[k];
}
}
KMP算法:
int KMPIndex(SqString s,SqString t)
{ int next[MaxSize],i=0,j=0;
GetNext(t,next);
while (i<s.length && j<t.length)
{ if (j==-1 || s.data[i]==t.data[j])
{ i++;
j++; //i,j各增1
}
else j=next[j]; //i不变,j后退
}
if (j>=t.length)
return(i-t.length); //返回匹配模式串的首字符下标
else
return(-1); //返回不匹配标志
}
function KMP_search(longStr, shortStr) {
const l_length = longStr.length, s_length = shortStr.length;
// longStr表示文本串 shortStr表示模式串
if (l_length < s_length) return -1;
else if (s_length === 0) return 0;
// 计算next数组的函数
nextArr = caculateNext(shortStr);
// 两个指针分别指向文本串和模式串
let l_point = 0, s_point = 0;
while (l_point < l_length && s_point < s_length) {
// 匹配时同时移动
if (longStr[l_point] === shortStr[s_point]) {
++l_point;
++s_point;
}
else {
// 当起始位置也不匹配时,需要在文本串移动
if (s_point === 0) ++l_point;
// 其他情况下不匹配时的移动指针
else s_point = nextArr[s_point-1];
}
}
// 判断是否是匹配成功才退出的循环
if (s_point >= s_length) return l_point - s_length;
else return -1;
}
// 计算next数组 在主函数中移动时取前一位的形式(相当于右移)
function caculateNext(strArr) {
// 先全部赋0
nextArr = new Array(strArr.length).fill(0);
let j;
// 从第一位也就是第二个字符开始比较
for (let i=1; i<strArr.length; i++){
j = nextArr[i-1]
while( j >= 0 && strArr[j] !== strArr[i]){
// 其实这里利用了数组负索引返回undefined因此当j-1小于0时会直接退出循环
// if(j-1<0) {
// j = -1
// break
// }
j = nextArr[j-1];
}
// 如果能继续上一位的相等情况,就在上一位基础上加1,否则就使用默认值0
if (j >= 0) nextArr[i] = j + 1;
}
// // 第一位赋值-1,方便后续的移位操作
// nextArr[0] = -1;
return nextArr
}
- 手工模拟
5.改进KMP算法
- 求解nextval : 先求解 next 数组,第一个nextval 一定是 -1 ,后续字符串检查 next 中下标,陆续与响应下标 j 对应匹配,不匹配返回 0 ,匹配成功返回相同 next 下标到 nextval 中,再次失败则直接返回该元素 next 值
由模式串球出nextval值:
void GetNextval(SqString t,int nextval[])
{ int j=0,k=-1;
nextval[0]=-1;
while (j<t.length)
{ if (k==-1 || t.data[j]==t.data[k])
{ j++;k++;
if (t.data[j]!=t.data[k])
nextval[j]=k;
else
nextval[j]=nextval[k];
}
else
k=nextval[k];
}
}
修改后的KMP算法:
int KMPIndex1(SqString s,SqString t)
{ int nextval[MaxSize],i=0,j=0;
GetNextval(t,nextval);
while (i<s.length && j<t.length)
{ if (j==-1 || s.data[i]==t.data[j])
{ i++;
j++;
}
else
j=nextval[j];
}
if (j>=t.length)
return(i-t.length);
else
return(-1);
}