数据结构——BF, KMP

4 篇文章 0 订阅

1.串的基本操作

  1. 串逻辑结构与线性表相似,但串操作一组数据元素,更多是查找子串位置,得到指定位置子串,替换子串操作。线性表更关注单个元素操作:增删改查一个元素
  2. 数据结构,串属于线性结构
  3. 定义:由零个或多个字符组成的有限序列
  4. 采用顺序存储与链式存储【不推荐,空间浪费,操作麻烦】

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模式匹配【定位操作】

  1. 现在要判断 T 是否含有 S ,如果存在,就返回第一次出现的位置,如果没有,则返回-1
  2. 主串:目标串; 子串:模式串

1. 规则:

主串S(指针 i 遍历),其中子串T(指针 j 遍历)在S 中匹配成功 T 中大部分 字符串,该部分字符串具有相同前缀子串【首串】(下标为 k - 1)和后缀子串【尾串】(长度 k),则在下一趟比较中,主串指针 i 不动,子串指着 j 向前回溯到 k 位置。

2. 举例:

首串和尾串可以重叠交叉 ,首串和尾串的长度应该小于该串本身,
在这里插入图片描述
①abc ②aaaa ③无,是空串

3. 采用以空间换时间策略,操作方法如下 :

**前两个标记下标一定是   -1  和   0**

4. 操作举例:

  1. 求解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); //返回不匹配标志

}

js实现:

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
}

  1. 手工模拟
    在这里插入图片描述
    在这里插入图片描述

5.改进KMP算法

  1. 求解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);
}

在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值