实用算法实现-第 8 篇 后缀树和后缀数组 [3 两个字符串的最长公共子串]

实用算法实现-第 8 篇 后缀树和后缀数组 [2 最长公共子串] 8.3    最长公共子串 求N个串的最长公共子串,可以转化为求一些后缀的最长公共前缀的最大值,这些后缀应分属于N个串。设N个串分别为S1,S2,S3,…,SN。 具体方法如下: 1. 建立字符串S,使得S = S1[P1]S2[P2]S3…SN-1[PN-1]SN。其中P1,P2,…,PN-1应为不同的N - 1个不在字符集中的字符,作为分隔符。插入分隔符的目的是为了防止S的后缀的公共前缀 阅读详情

8.4    两个字符串的最长公共子串

两个串的最长公共字串是相对于多个串要简单一点,不需要二分A。只需要判断相邻两个Height是不是分属两个字符串即可。

8.4.1   实例

PKU JudgeOnline, 2774, Long Long Message.

8.4.2   问题描述

给两个小写ASCII字母组成的字符串,求出它们最大公共子串的长度。

相比PKUJudgeOnline, 3450, Corporate Identity,这个题目比较简单,而且测试数据并不强,没有测出求Height数组的一个错误。

8.4.3   输入

yeshowmuchiloveyoumydearmotherreallyicannotbelieveit

yeaphowmuchiloveyoumydearmother

8.4.4   输出

27

8.4.5   分析

采用的算法是DC3算法,源程序可以在[i]下载。该算法实现得相当巧妙,所以只添加注释和计算height的函数,就可以使用了。

8.4.6   程序

#include <stdio.h>
#include <iostream>
#include <stdlib.h>
using namespace std;
inline bool leq(int a1, int a2,   int b1, int b2) { // lexic. orderfor pairs
  return(a1< b1 || a1 == b1 && a2 <= b2);
}                                                  // and triples
inline bool leq(int a1, int a2, int a3,   int b1, int b2, int b3) {
  return(a1< b1 || a1 == b1 && leq(a2,a3, b2,b3));
}
// stably sort a[0..n-1] to b[0..n-1] with keys in 0..K fromr
static void radixPass(int* a, int* b, int* r, int n, int K)
{// count occurrences
  int* c = new int[K + 1];                          // counter array
  for (int i = 0;  i<= K;  i++) c[i] = 0;         // resetcounters
  for (int i = 0;  i< n;  i++) c[r[a[i]]]++;    // countoccurences
  for (int i = 0, sum = 0; i <= K;  i++) { // exclusive prefix sums
     int t =c[i];  c[i] = sum;  sum += t;
  }
  for (int i = 0;  i< n;  i++) b[c[r[a[i]]]++] =a[i];      //sort
  delete [] c;
}
 
// find the suffix array SA of s[0..n-1] in {1..K}^n
// require s[n]=s[n+1]=s[n+2]=0, n>=2
void suffixArray(int* s, int* SA, int n, int K) {
  intn0=(n+2)/3, n1=(n+1)/3, n2=n/3, n02=n0+n2;
  //n0是字符串中模为的下标的个数,n1,n2依此类推
  int* s12  = new int[n02 + 3]; s12[n02]= s12[n02+1]= s12[n02+2]=0;
  int* SA12 = new int[n02 + 3];SA12[n02]=SA12[n02+1]=SA12[n02+2]=0;
  int* s0   = new int[n0];
  int* SA0  = new int[n0];
 
  // generatepositions of mod 1 and mod  2 suffixes
  // the"+(n0-n1)" adds a dummy mod 1 suffix if n%3 == 1
  for (int i=0, j=0; i < n+(n0-n1);  i++) if (i%3 != 0) s12[j++] = i;
  //将所有模不为的下标存入s12中
 
  // lsb radix sortthe mod 1 and mod 2 triples
  radixPass(s12 , SA12, s+2, n02, K);
  radixPass(SA12, s12 , s+1, n02, K); 
  radixPass(s12 , SA12, s  , n02, K);
  //radixPass实际是一个计数排序
  //对后缀的前三个字符进行三次计数排序完成了对SA12数组的基数排序
  //这个排序是初步的,没有将SA12数组真正地排好序,因为:
  //若SA12数组中几个后缀的前三个字符相等,则起始位置靠后的排在后面
 
  // findlexicographic names of triples
  int name = 0,c0 = -1, c1 = -1, c2 = -1;
  for (int i = 0;  i< n02;  i++) {
    if(s[SA12[i]] != c0 || s[SA12[i]+1] != c1 || s[SA12[i]+2] != c2) {
      name++; c0 = s[SA12[i]];  c1 =s[SA12[i]+1];  c2 = s[SA12[i]+2];
    }
     //name是计算后缀数组SA12中前三个字符不完全相同的后缀个数
     //这么判断的原因是:SA12有序,故只有相邻后缀的前三个字符才可能相同
    if (SA12[i]% 3 == 1) { s12[SA12[i]/3]      = name; }// left half
    else                  { s12[SA12[i]/3 + n0] = name;} // right half
     //SA12[i]模不是就是,s12保存的是后缀数组SA12中前三个字符的排位
  }
 
  // recurse if namesare not yet unique
  if (name <n02) {
     //如果name等于n02,意味着SA12前三个字母均不相等,即SA12已有序
     //否则,根据s12的后缀数组与SA12等价,对s12的后缀数组进行排序即可
    suffixArray(s12, SA12, n02, name);
    // store uniquenames in s12 using the suffix array
    for (int i = 0;  i< n02;  i++) s12[SA12[i]] = i + 1;
  } else // generate the suffix array of s12 directly
    for (int i = 0;  i< n02;  i++) SA12[s12[i] - 1] = i;
  //s12保存的是后缀数组SA12中前三个字符的排位
  //在所有后缀前三个字符都不一样的情况下,s12就是后缀的排位
 
  //至此SA12排序完毕
  //SA12[i]是第i小的后缀的序号(序号从到n02),s12[i]是序号为i的后缀的排位
  //使用后缀序号而不是实际位置的原因是递归调用suffixArray时不能保留该信息
 
  // stably sort themod 0 suffixes from SA12 by their first character
  for (int i=0, j=0; i < n02;  i++) if (SA12[i] < n0) s0[j++] = 3*SA12[i];
  //将SA12中所有的模为的后缀的实际位置减去按序存储在s0中
  //注意后缀序号到实际位置的转化需将前者乘
  //这意味着首先已经利用模为的后缀对SA0进行了初步排序
  //只需要采用一次计数排序即可对SA0完成基数排序的最后一步
  radixPass(s0, SA0, s, n0, K);
 
  //最后一步,对有序表SA12和SA0进行归并
  // merge sorted SA0suffixes and sorted SA12 suffixes
  for (int p=0, t=n0-n1,  k=0;  k < n; k++) {
#define GetI() (SA12[t] < n0 ? SA12[t] * 3 + 1 : (SA12[t] - n0) *3 + 2)
    int i =GetI(); // pos of current offset 12 suffix
    int j =SA0[p]; // pos of current offset 0  suffix
    if (SA12[t]< n0 ?
        leq(s[i],       s12[SA12[t] + n0], s[j],       s12[j/3]) :
        leq(s[i],s[i+1],s12[SA12[t]-n0+1],s[j],s[j+1],s12[j/3+n0]))
    { // suffix fromSA12 is smaller
      SA[k] = i;  t++;
      if (t ==n02) { // done --- only SA0 suffixes left
        for(k++;  p < n0;  p++, k++) SA[k] = SA0[p];
      }
    } else {
      SA[k] = j;  p++;
      if (p ==n0)  { // done--- only SA12 suffixes left
        for(k++;  t < n02;  t++, k++) SA[k] = GetI();
      }
    } 
  }
  delete []s12; delete [] SA12; delete[] SA0; delete [] s0;
}
void suffixArrayHeight(int *s, int *SA, int n, int K, int *rank, int *height)
{
    int i, j,h;
     for(i = 0;i < n; i++){
         rank[SA[i]] = i;
         //rank和SA互逆,即SA[rank[i]] == i&&rank[SA[i]] == i
     }
     h = 0;
     for(i = 0;i < n; i++){
         if(rank[i]== 0){
            height[rank[i]] = 0;
         }else{
              j = SA[rank[i] - 1];
              //如果用前缀的第一个字符的下标来标识前缀
              //那么,j是前缀i == SA[rank[i]]的左邻前缀
              while(s[i+ h] == s[j + h]){
                   h++;
                   //如果没有关于h[i]和h[i+1]的大小关系的定理,h需要从开始
              }
              height[rank[i]] = h;
              //求出了h[i]的值
              if(h> 0)
              {
                   h--;
                   //h[i+1]的值大于或等于h[i]-1
              }
        }
     }
}
#define maxNum (100002 * 2 + 3)
/*
int s[maxNum];
int SA[maxNum];
int rank[maxNum];
int height[maxNum];*/
bool isPermutation(int *SA, int n) {
  bool *seen = new bool[n];
  for (int i = 0;  i< n;  i++) seen[i] = 0;
  for (int i = 0;  i< n;  i++) seen[SA[i]] = 1;
  for (int i = 0;  i< n;  i++) if(!seen[i]) return 0;
  return 1;
}
bool sleq(int *s1, int *s2) {
  if (s1[0]< s2[0]) return 1;
  if (s1[0]> s2[0]) return 0;
  returnsleq(s1+1, s2+1);
}
// is SA a sorted suffix array for s?
bool isSorted(int *SA, int *s, int n) {
  for (int i = 0;  i< n-1;  i++) {
    if(!sleq(s+SA[i], s+SA[i+1])) return 0;
  }
  //每一个后缀都比其后的那个后缀小,那么后缀数组是升序的
  return1; 
}
#define Assert(c) if(!(c))\
  {cout << "\nAssertionviolation " << __FILE__ << ":"<< __LINE__ << endl;}
int main()
{
     int i, j;
     int n;
     int b = 'z' - 'a' + 2;
    
     int* s;
     int* SA;
     int* rank;
     int*height;
     charstr1[100002];
     charstr2[100002];
     intlength1;
     intlength2;
     int max;
     scanf("%s%s",str1, str2);
     length1 = strlen(str1);
     length2 = strlen(str2);
     n = length1 + length2 + 1; 
     s = new int[n+3];
     SA = new int[n+3];
     rank = new int[n];
     height = newint[n];
     s[n] = s[n+1] = s[n+2] = SA[n] = SA[n+1] =SA[n+2] = 0;
     for(i = 0;i < length1; i++){
         s[i] = str1[i] - 'a' + 1;
     }
     s[i++] = 27;
     for(j = 0;j < length2; j++){
         s[i++] = str2[j] - 'a' + 1;
     }
     suffixArray(s, SA, n, b);
     //构建后缀数组
     /*
     Assert(s[n] == 0);
     Assert(s[n+1] ==0);
     //s是字符串数组,求后缀数组时不应被改变
     Assert(SA[n] == 0);
     Assert(SA[n+1] ==0);
     //长度为n的字符串有n个后缀
     Assert(isPermutation(SA,n));
     //后缀数组是以第i(0<= i < n)个字符作为起点的后缀的数组
     Assert(isSorted(SA,s, n));
     //后缀数组必须有序
     */
     suffixArrayHeight(s, SA, n, b, rank,height);
     max = 0;
     for(i = 1;i < n; i++){
         if((SA[i]< length1 && SA[i - 1] > length1)||
         (SA[i] > length1 && SA[i -1] < length1))
        {
            if(max< height[i])
                max = height[i];
        }
     }
     cout << max << endl;
     delete []s; delete [] SA; delete[] rank; delete [] height;
}

本文章欢迎转载,请保留原始博客链接http://blog.youkuaiyun.com/fsdev/article


[i] http://www.mpi-sb.mpg.de/~sanders/programs/suffix/

实用算法实现-第8篇 后缀树和后缀数组 [1简介] 8.1    后缀树 一棵后缀树包含一个指定文本的所有后缀,对于在一个长度为N的文本中查找一个长度为M的子字符串,一个后缀树仅仅需要M次比较,而这个比较次数是查找该字符串所需要的最小比较次数。 后缀树有以下特征:一条边可以表示文本的任何非空子串,每个非终端节点,除了根节点,必须至少有两个孩子边,兄弟边表示的子串必须开始于不同的字符。 ababa所对应的后缀树如下所示: 后缀树可以解 阅读详情

相关推荐

后缀数组原理及应用详解

后缀数组原理及应用详解      在pongba的讨论组上看到一道Amazon的面试题:找出给定字符串里的最长回文。例子:输入XMADAMYX。则输出MADAM。这道题的流行解法是用后缀树(Suffix Tree)。这坨数据结构最酷的地方是用它能高效解决一大票复杂的字符串编程问题:  1.在文本T里查询T是否包含子串P(复杂度同流行的KMP相当)。 2.文本T里找出最长重复子串。比如ab

5396

后缀数组的应用:最长公共子串

用O(N+M)的复杂度解决最长公共子串问题

明朗晨光的专栏 1702

光伏发电系统模拟及其发电预测开源python工具pvlib

本文介绍光伏发电系统模拟及其发电预测开源python工具pvlib,以及举例介绍获取辐照度方法、光伏发电预测思路。

肖永威的专栏 1万+

后缀数组——处理字符串的有力工具

<br />后缀数组——处理字符串的有力工具<br />作者:罗穗骞<br />2009年1月<br />【摘要】<br />  后缀数组是处理字符串的有力工具。后缀数组是后缀树的一个非常精巧的替代品,它比后缀树容易编程实现,能够实现后缀树的很多功能而时间复杂度也并不逊色,而且它比后缀树所占用的内存空间小很多。可以说,在信息学竞赛中后缀数组比后缀树要更为实用。本文分两部分。第一部分介绍两种构造后缀数组的方法,重点介绍如何用简洁高效的代码实现,并对两种算法进行了比较。第二部分介绍后缀数组在各种类型题目中的具体

岩山放牛娃 2969

Wireshark抓包实战:5个CTF比赛中高频出现的流量分析技巧(附真实案例)

本文分享了Wireshark在CTF比赛和实战渗透中的五个核心流量分析技巧。内容涵盖从宏观统计概览锁定可疑目标、深度解析ICMP与HTTP等协议、追踪重组完整会话流,到利用字符串搜索和命令行工具进行自动化分析。掌握这些高阶技巧能显著提升分析海量网络流量的效率与精准度。

rrr55的博客 261

Windows 10下用VS2022编译AFSIM 2.9.0的完整避坑指南

本文提供了在Windows 10系统下,使用Visual Studio 2022编译AFSIM 2.9.0仿真框架的详细指南。文章系统梳理了从环境准备、CMake配置、编译链接到安装打包的全流程,并重点针对编译过程中常见的文件编码、命名空间冲突等高频错误提供了具体解决方案,旨在帮助开发者高效避坑,顺利完成AFSIM在Windows平台上的构建。

weixin_28839549的博客 549

求两个字符串的最长公共子串(LCS)

最长公共子串(LCS),有三种情况:1.公共子串的元素必须相邻. 2.公共子串的元素可以不相邻联单3. 求多个字符串而不是两个字符串的最长公共子串 1.公共子串的元素必须相邻:     LCS问题就是求两个字符串最长公共子串的问题。解法就是用一个矩阵来记录两个字符串中所有位置的两个字符之间的匹配情况,若是匹配则为1,否则为0。然后求出对角线最长的1序列,其对应的位置就是最长匹配子串的位

zhendongzd 3310

QT实现计算器(C/C++)

digitClicked()槽函数用于处理数字按钮的点击事件,operatorClicked()槽函数用于处理运算符按钮的点击事件,equalsClicked()槽函数用于处理等号按钮的点击事件。在operatorClicked()槽函数中,我们获取被点击按钮的文本作为当前运算符,并将文本框中的内容转换为双精度浮点数,存储为操作数1。在上述代码中,我们添加了一个私有成员变量m_operand1,用于存储第一个操作数的值,以及一个私有成员变量m_currentOperator,用于存储当前运算符的值。

CyberNova的博客 385

利用后缀树求多个字符串的最长公共连续子串(Longest Common Substring)

问题:有N个字符串,计算它们公共的最长连续子串。例如,字符串集合{abcd,bcca,aabc},它们公共的最长连续子串是bc。又如{abcfd,bcabca,aabcf}的公共的最长连续子串是abc。针对本问题,可以用特殊的字符(该字符必须是N个串中没有出现的字符)将N个字符串连起来形成一个新串,然后构建这个新串的后缀树。比如对字符串集合 {abcd,bcca,aabc},可以连成新串abcd$

ljsspace的专栏 4913

[QMT和ptrade量化交易小白入门]-109、小市值选股的基础上用六脉神剑评分,回测年化可到132%

本专栏主要是介绍QMT和ptrade的基础用法,常见函数,写策略的方法,也会分享一些量化交易的思路,大概会写200篇左右。QMT的相关资料较少,在使用过程中不断的摸索,遇到了一些问题,记录下来和大家一起沟通,共同进步。

木头大左的博客 1863

《网络安全自学教程》- 防火墙原理分析与Windows防火墙使用配置

防火墙分类及原理,Windows开启防火墙,Windows防火墙添加策略,

wangyuxiang946的博客 1万+

数据结构与算法 - 后缀树的入门:字符串匹配的高效结构

摘要 本文深入浅出地介绍了字符串处理中的高效数据结构——后缀树。文章首先通过朴素算法与KMP等算法的对比,指出后缀树在多次查询时的优势(预处理O(n),查询O(m))。随后详细讲解后缀树的定义、构建步骤(从标准Trie到压缩Trie)和核心性质,包括边标签不相交、叶子数与字符串长度相等等特性。文中还列举了后缀树能解决的典型问题,如子串查询、最长重复子串等,并简要介绍了Ukkonen线性时间构建算法。最后提供了一个基于显式插入的简化版Java实现,包含节点设计和基础构建方法。通过清晰的图示和代码示例,帮助读者

千淘万漉虽辛苦,吹尽狂沙始到金 2万+

从零开始学习CANoe(六)—— CAPL 测试节点

创建CAPL Test Module的两种方式

蚂蚁小兵 1万+

C++ 后缀树(Suffix Tree):原理、实现与应用全解析

后缀树(Suffix Tree)是处理字符串子串问题的**终极数据结构**,它将字符串的所有后缀压缩存储为一棵多叉树,能在 \(O(1)\)(预处理后)时间内回答子串存在性、最长重复子串、最长公共子串等问题。尽管构建过程复杂(Ukkonen 算法可做到 \(O(n)\) 时间),但其极致的查询效率使其成为字符串处理的“神器”。本文将从核心原理、节点设计、Ukkonen 算法实现到实战应用,全面解析后缀树的设计思想与 C++ 落地技巧。

互联网改遍了世界。 1203

【虚幻引擎UE】UE5 可编辑动态样条线轨迹及AI跟随路径移动(含工程源码)

【虚幻引擎UE】可编辑动态样条线轨迹及AI跟随路径移动

灵境引路人 6227

SIMATIC S7-1200, S7-1500 PID 控制[手册]

SIMATIC S7-1200, S7-1500 PID 控制[手册]

后缀树建立 java_实用算法实现-第 8 篇后缀树和后缀数组 [2 最长公共子串]

8.3 最长公共子串求N个串的最长公共子串,可以转化为求一些后缀的最长公共前缀的最大值,这些后缀应分属于N个串。设N个串分别为S1,S2,S3,…,SN。具体方法如下:1. 建立字符串S,使得S = S1[P1]S2[P2]S3…SN-1[PN-1]SN。其中P1,P2,…,PN-1应为不同的N - 1个不在字符集中的字符,作为分隔符。插入分隔符的目的是为了防止S的后缀的公共前缀超出原有串S...

weixin_32473663的博客 338

IDEA的使用指南

IDEA基于java的使用手册,可以最快速的上手,在使用中不断的丰富自己的使用技巧.

后缀树和后缀数组 [3 两个字符串的最长公共子串]

后缀树和后缀数组 [3 两个字符串的最长公共子串] 8.4 两个字符串的最长公共子串 两个串的最长公共字串是相对于多个串要简单一点,不需要二分A。只需要判断相邻两个Height是不是分属两个字符串即可。 8.4.1 实例 PKU JudgeOnline, 2774, Long Long Message. 8.4.2 问题描述 给两个小写ASCII字母组成的字符串

zhongjie的专栏 1446

lwip-2.0.3

lwip是瑞典计算机科学院(SICS)的Adam Dunkels 开发的一个小型开源的TCP/IP协议栈。实现的重点是在保持TCP协议主要功能的基础上减少对RAM 的占用。

华为的C语言编程规范.pdf

华为技术有限公司内部技术规范。本规范制定了编写C语言程序的基本原则、规则和建议。从代码的清晰、简洁、可测试、安全、程序效率、可移植各个方面对C语言编程作出了具体指导。

后缀树

后缀树的构建及查询等应用

zhangchuan的博客 633

字符串公共串

一、找出最长公共子序列的长度 题:求字符串str1,str2的最长公共子序列的长度(可以不连续)。 解:定义二元函数函数f(m,n):分别以str1[m],str2[n]结尾的连续公共子序列的长度 而对于f(m,n) 有以下两种情况 1.str1[m] != str2[n],则有f(m,n) = max(f(m-1,n), f(m, n-1)) 2.str

hepeng597的专栏 1500

【字符串新武器】后缀自动机

发链:http://neroysq.blogcn.com/articles/%E5%90%8E%E7%BC%80%E8%87%AA%E5%8A%A8%E6%9C%BA%E5%88%9D%E6%8E%A2.html http://blog.sina.com.cn/s/blog_7812e98601012cim.html 详细构造见上述链接,此处介绍性质与理解 后缀自动机具有两大性质,考虑转

huyuncong的专栏 1万+

深入理解后缀树:原理、构建与应用

后缀树(Suffix Tree)是一种用于字符串处理的压缩字典树(Trie)数据结构。它将一个字符串的所有后缀都存储在树中,从而支持在线性时间内完成多种字符串操作,如子串搜索、最长重复子串查找、最长公共子串等。根节点到每个叶子节点的路径对应 S 的一个唯一后缀。每个内部节点至少有两个子节点(保证压缩性)。边上的标签是 S 的子串。总节点数为 O(n)。后缀树是一种功能强大但实现复杂的字符串数据结构。虽然构建算法(Ukkonen)理解门槛较高,但其O(n) 构建时间和O(m) 查询时间。

QY8765的博客 171

后缀数组:字符串处理的高级数据结构

后缀数组(Suffix Array)是处理字符串问题的强大工具。后缀树太复杂,后缀数组更简洁,而且能解决大部分字符串问题。很多人只知道后缀数组是"后缀排序",却不知道为什么排序后能快速查询、LCP数组的作用。我并没有能力让你成为字符串算法专家,我只是想让你彻底理解后缀数组的定义、倍增构建的优化原理、以及LCP数组在模式匹配中的应用。每个概念都会详细推导,讲透本质。从"查找最长重复子串"问题出发,剖析后缀数组的核心思想与构建方法。

比较是偷走幸福的小偷 4290

最长公共子串_史上全网最清晰后缀自动机学习(五)后缀自动机和最长公共子串问题...

缘起最长公共子串(LCS)问题可谓是经典的问题了. 我们使用过DP、后缀树、后缀数组来解决过. 现在我们考虑后缀自动机和LCS问题的联系, 并且来看这一联系的典型例题—— hihocoder #1465 : 后缀自动机五·重复旋律8分析时间限制:10000ms单点时限:1000ms内存限制:256MB描述小Hi平时的一大兴趣爱好就是演奏钢琴。我们知道一段音乐旋律可以被表示为一段数构成...

weixin_39884832的博客 280
上一篇: 实用算法实现-第 8 篇 后缀树和后缀数组 [2 最长公共子串]
下一篇: 实用算法实现-第 8 篇 后缀树和后缀数组 [4 最长回文子串]
存储李希
博客等级 码龄15年 418粉丝 69原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值