数据结构——复习四

自古美人都是妖i 提交于 2020-03-02 14:50:48

//虽然我觉得,我刚开始复习的这些,在库里其实有可以直接调用的,有现成的,但我们还需要明白这是一种思想,所以我静下心来好好体会了一下,做这些事,其实都是很有必要的。
串(字符串):是零个或多个字符组成的有限序列。记作: S=“a1a2a3…”,其中S是串名,ai(1≦i≦n)是单个,可以是字母、数字或其它字符。
串值:双引号括起来的字符序列是串值。
串长:串中所包含的字符个数称为该串的长度。
空串(空的字符串):长度为零的串称为空串,它不包含任何字符。
空格串(空白串):构成串的所有字符都是空格的串称为空白串。
注意:空串和空白串的不同,例如“ ”和“”分别表示长度为1的空白串和长度为0的空串。
子串(substring):串中任意个连续字符组成的子序列称为该串的子串,包含子串的串相应地称为主串。
子串的序号:将子串在主串中首次出现时的该子串的首字符对应在主串中的序号,称为子串在主串中的序号(或位置)。
串相等:如果两个串的串值相等(相同),称这两个串相等。换言之,只有当两个串的长度相等,且各个对应位置的字符都相同时才相等。
串在计算机中有三种存储方式:
1.定长顺序存储:将串定义成字符数组,利用串名可以直接访问串值。用这种表示方式。串的存储空间在编译时确定,其大小不能改变。

#define MAX_STRLEN 256
//存储结构定义
typedef struct{
    char str[MAX_STRLEN];
    int length;
}StringType;
//将两串连接的操作
bool StrConcat(StringType s,StringType t)
{
    int i,j;
    if(s.length+t.length>MAX_STRLEN)
    return false;
    for(int i=0;i<t.length;i++)
    s.str[s.length+i]=t.str[i];
    s.length+=t.length;
    return true;
}
//求子串
bool SubString(StringType s,int pos,int len,StringType *sub)
{
    int k,j;
    if (pos<0||pos>=s.length||len<pos||pos>=s.length)
     return false;   /*  参数非法  */
    sub->length=len-pos+1;//求得子串长度
    for(j=0,k=pos;k<=len;k++,j++)
    {
     sub->str[j]=s.str[k];   
    }
    return true;
    
}

2.串的堆分配存储表示
一个空间足够大且地址连续的存储空间(称为:“堆”)供串使用,可以使用C语言的动态存储分配函数malloc()和free()来管理。动态的,变长的。

typedef struct
{
    char *ch;
    int length;
} HString;
bool StrConcat(HString *T,HString *s1,Hstring *s2)
//用T返回由s1和s2连结成的串
{
    int k,j,t_len;
    if(T.ch)  
    free(T);//释放旧空间
    t_len=s1->length+s2->length ;
    if ((p=(char *)malloc(sizeof((char)*t_len))==NULL)
    {
    printf("系统空间不够,申请空间失败\n") ;
    return false;
    }
    for (j=0;j<s1->length;j++)
    T->ch[j]=s1->ch[j];//将串s复制到串T中
    for (k=s1->length;k<T->length;k++)
    T->ch[k]=s1->ch[k];//将串s2复制到串T中 
    free(s1->ch);//这种方式跟上一种最大的不同
    free(s2->ch);
    return true;
}

3.链式存储表示
串的链式存储结构和线性表的串的链式存储结构类似,采用单链表来存储串,结点的构成是:data域(存放字符),next域(存放下一结点的指针)
存在的问题及解决方法:若每个结点仅存放一个字符,则结点的指针域就非常多,为了节省存储空间,考虑串结构的特殊性,使每个结点存放若干字符,这种结构称为块链结构。
在这里插入图片描述

#define BLOCK_SIZE  4
typedef  struct  Blstrtype
{
    char  data[BLOCK_SIZE];
    struct  Blstrtype  *next;
}BNODE;
typedef  struct
{
    BNODE  head;//头指针
    int  Strlen ;//当前长度
}Blstring;
/*当一个块(结点)内存放多个字符时,往往会使操作过程变得较为复杂,如在串中插入或删除字符操作时通常需要在块间移动字符。*/

最重要串的模式匹配
BF算法

int BFindex(SString S,SString P,int pos)  
{
       if(pos<1||pos>S.length) 
        exit(ERROR);
        int i=pos,j=1;//字符串从1开始
       while(i<=S.length&&j<=P[0])  
        {
         if(S[i]==P[j])  
         {     
             i++;
             j++;
         } 
         else
         {              
         i=i-j+2;
         j=1;
        }     
        }  
        if(j>P.length) 
        return i-P.length;
        return ERROR;
      
}

该算法的时间复杂度为O(n*m) ,其中n 、m分别是主串和模式串的长度。通常情况下,实际运行过程中,该算法的执行时间近似于O(n+m) 。
KMP算法
这个改进算法在于:当每一趟匹配过程出现字符不相等时,主串指示器不用回溯,而是利用已经得到的“部分匹配”的结果,将模式串的指示器向右“滑动”尽可能远的一段距离后(j取较大的值)继续进行比较。
在这里插入图片描述
将si与pj的比较变为si与pk(可能不用从1开始)的比较(k<j)
当si≠pj(1≦i≦n-m,1≦j<m,m<n)时,主串s的指针i不必回溯,而模式串p的指针j回溯到第k(k<j)个字符继续比较,则模式串p的前k-1个字符必须满足4-1式,而且不可能存在k’>k满足4-1式。
p1p2…pk-1= si-(k-1) si-(k-2) … si-2 si-1 (4-1)
而已经得到的 “部分匹配”的结果为:
pj-(k-1) pj-k+2… pj-1=si-(k-1) si-(k-2) … si-2 si-1 (4-2)
由式(4-1)和式(4-2)得:
p1p2…pk-1=pj-(k-1) pj-k+2… pj-1 (4-3)
实际上,式(4-3)描述了模式串中存在相互重叠的子串的情况。
定义next[j]函数为
在这里插入图片描述
//我感觉这是我学kmp到现在最理解kmp算法的一次,哈哈哈,越看越明白
举个例子就是这样的,用上面那个公式其实很好理解
在这里插入图片描述
所以,我们的首要任务就是求出next数组,由next[j]的公式可知,求模式串的next[j]值与主串s无关,只与模式串p本身的构成有关:
当j=1时,next[1]=0
如果存在1<k<j,即next[j]=k,此时求next[j+1]的值有两种情况:
(1)若有pk=pj,则表明在模式串中有:p1p2…pk-1pk=pj-(k-1)pj-k+2… pj-1pj,即next[j+1]=next[j]+1=k+1
(2)若有pk≠pj,我们就开始找稍微短一点的重叠串,若next[k]= k’,且pj = pk’则说明在主串中第j+1字符之前存在一个长度为k’(即next[k])的最长子串,next[j+1]=k’+1
//next[j]的值是前1-j-1的最长重叠子串+1//也就是该比较的位置
//画个图以后特别明显,可以试着画一下,这其实就成了一个循环。
如果最后不存在任何k’(1< k’<j)满足等式:p1 p2…pk’-1 pk’=pj-(k’-1) pj-k’+2… pj-1 pj,则next[j+1]=1

void getnext(char p[],int nex[])
{
    int i=1,j=0;
    nex[1]=0;
    while(i<strlen(p))
    {
        if(j==0||p[i]==p[j])//j=next[i]
        {
            i++;
            j++;
            nex[i]=j;
        }
        else
        {
            j=nex[j];
        }
    }
}

在求得了next数组之后,KMP的思想是:
设目标串为s,模式串为p,并设i指针和j指针分别指示目标串和模式串中正待比较的字符,设i和j的初值均为1。
若有si=pj,则i和j分别加1。否则,i不变,j退回到j=next[j]的位置,再比较si和pj,若相等,则i和j分别加1。否则,i不变,j再次退回到j=next[j]的位置,依此类推。直到下列两种可能:
(1)j退回到某个下一个next[j]值时字符比较相等,则指针各自加1继续进行匹配。
(2)退回到j=0,将i和j分别加1,即从主串的下一个字符si+1模式串的p1重新开始匹配。

int KMPindex(string S,string P,int pos)  
{   
    if(pos<1||pos>S.length()) 
    exit(ERROR);  
    int i=pos,j=1;
    while(i<=S.length()&&j<=P.length())  
    {  
      if(j==0||S[i]==P[j]) 
       {  
         i++;
         j++;    
       } 
      else 
       {     
       j=next[j];  
       }  
    }  
    if(j>P.length()) 
    return i-P.length();  
    return ERROR;  
}  

KMP算法仅当模式与主串之间存在许多“部分匹配”的情况下才显得比BF算法快得多。

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!