数据结构学习第四章串和数组

文章介绍了串(字符串)的概念、子串和位置,以及在病毒检测中的应用。接着,定义了串的抽象数据类型,并探讨了顺序存储(顺序串和链串)和链式存储(块链结构)。重点讲述了模式匹配算法,包括BF算法和KMP算法,分析了它们的时间复杂度。最后,文章涉及数组的存储,特别是矩阵的压缩存储方法,如对称矩阵、稀疏矩阵的顺序和链式存储方案。
摘要由CSDN通过智能技术生成

第四章串、数组和广义表

1.串(字符串)

  • 串——零个或多个任意字符组成的有限序列(内容受限的线性表)。

  • 子串——串中任意个连续字符组成的子序列(包含空串)称为该穿的子串。

​ 例如,“abcde"的子串有:”"、“a”、“ab”、“abc”、“abcd”、"abcde"等。

​ 注:真子串指不包含自身的所有子串。

  • 字符位置——字符在序列中的需要为字符在串中的位置。
  • 子串位置——子串第一个字符在主串中的位置。
  • 空格串——由一个或者多个空格字符组成的串,不同于空串。
  • 空串——不包含任何字符。
  • 串的长度——字符个数
  • 串相等——当且仅当两个串长度相等并各个对应位置上的字符都相同。所有空串都相等

案例1:病毒感染检测

研究者将人的DNA和病毒DNA均表示成由字母组成的字符串序列。然后检测某种病毒DNA序列是否在患者的DNA序列出现过,如果出现则已经感染,否则没有感染。(注:人的DNA序列是线性的,病毒的DNA序列是环状的)。

例如病毒DNA序列为baa,由于病毒DNA序列是环状的,因此融入人的DNA中情况有baa、aab、aba三种。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-znfgwAa5-1689241591145)(F:\桌面文件\编程学习\img\image-20230711161502109.png)]

*患者1的DNA序列为aaabbba,此时患者1感染了。

*患者2的DNA序列为babbba,此时患者2没有感染。

2.串的定义

2.1.类型定义

ADT String{
    数据对象:D={ai | ai∈Char, i=1,2,3……}
    数据关系:R={<ai-1 , ai> | ai-1,ai ∈ D, i=1,2,3……}
    基本操作:
        StrAssign(&T,chars)  //串赋值
        StrCompare(S,T)  //串比较
        StrLength(S)  //求串长
        Concat(&T,S1,S2)  //串连结
        SubString(&Sub,S,pos,len) //求子串
        StrCopy(&T,S) //串拷贝
        StrEmpty(S) //串判空
        ClearString(&S) //清空串
        Index(S,T,pos)  //子串位置
        Replace(&S,T,V)  //串替换
        StrInsert(&S,pos,T) //子串插入
        StrDelete(&S,pos,len) //子串删除
        DestroyString(&S)  //串销毁
}ADT String

2.2串的顺序存储—顺序串

1.顺序存储实现(用的比较多)

#define MAXLEN 255
typedef struct{
    char ch[MAXLEN+1]; //存储串的一堆数组  这里表示数组下标为[0] ~ [255]。为了某些算法的渐变,一般0下标不用
    int length;  //串当前长度
}SString;

2.3串的链式存储—链串

串的链式存储示意图:一个结点存储一个字符

image-20230711163659690

一个结点存储一个字符会导致存储密度低。存储密度=串值所占存储/实际分配的存储。

为了解决存储密度低,可以将多个字符放在一个结点。

image-20230711164810525

1.链式存储实现—块链结构

#define CHUNKSIZE 80  //定义块的大小
typedef struct Chunk{
    char ch[CHUNKSIZE];
    struct Chunk *next;
}Chunk;

typedef struct  
    Chunk *head,*tail; //定义串的头指针和尾指针
    int curlen; //串的当前长度
}LString; //字符串的块链结构

3.串的模式匹配算法

算法目的:确定主串中所含子串第一次出现的位置。

算法应用:搜索引擎、拼写检查、语言翻译、数据压缩

算法种类:

—BF算法(Brute-Force):暴力破解法

—KMP算法:特点速度快

3.1BF算法(顺序存储)

简单匹配算法(Brute-Force)简称BF算法。采用穷举法的思路。即一个一个比较。

算法思路:从正文串的每一个字符开始,依次与T的字符进行匹配比较。

例如有正文串:aaaabcd

模式串(匹配的子串):abc

在这里插入图片描述

【案例1】:设目标串S=“aaaaab”,模式串T=“aaab”

​ S的长度为n(n=6),T的长度为m(m=4)。

BF算法匹配过程:用“ i ”记录正文串匹配字符,用“ j ”记录模式串匹配字符。两个都从1开始。

在这里插入图片描述

当遇到不匹配字符时,“ i ”回退到刚匹配的第一个字符的后一位(i-j+2)。" j "回退到1。

image-20230711170655866

当匹配成功时。返回" i - T.length"为子串出现的位置。

在这里插入图片描述

【算法设计思想】:Index(S,T,pos)

*将主串的第pos个字符和模式串的第一个字符比较

*若相等,继续逐个比较后续字符。(i - j + 2)

*若不等,从主串的下一个字符起,重新与模式串的第一个字符比较。(j = 1)

*直到主串的一个连续子串字符序列与模式串相等,返回值为S中的T匹配的子序列第一个字符的序号,即匹配成功。否则匹配失败返回值0。

【算法实现】

int Index_BF(SString S,SString T){
    int i=1,j=1;
    while(i<=S.length && j<=T.length){  //i不超过主串长度,j超过模式串的长度
        if(S.ch[i] == T.ch[j]){  //主串和模式串字符匹配,匹配下一个字符
            i++;
            j++;
        }else{  //主串和模式串字符不匹配,则回溯
            i = i-j+2; 
            j = 1;
        }
    }
    if(j>= T.length){ //如果是j超过模式串长度,即匹配成功
        return i-T.length; //返回主串匹配模式串的第一个字符
    }
    else{  //如果是i超过主串的长度,即匹配失败
        return 0; //返回值0
    }
}

倘若主串不从第一个位置开始,而是从某个值pos开始。只需要把" i "刚开始值赋为pos。其余相同

int Index_BF(SString S,SString T,int pos){
    int i=pos,j=1;
    while(i<=S.length && j<=T.length){  //i不超过主串长度,j超过模式串的长度
        if(S.ch[i] == T.ch[j]){  //主串和模式串字符匹配,匹配下一个字符
            i++;
            j++;
        }else{  //主串和模式串字符不匹配,则回溯
            i = i-j+2; 
            j = 1;
        }
    }
    if(j>= T.length){ //如果是j超过模式串长度,即匹配成功
        return i-T.length; //返回主串匹配模式串的第一个字符
    }
    else{  //如果是i超过主串的长度,即匹配失败
        return 0; //返回值0
    }
}

BF算法的时间复杂度:若n为主串的长度,m为子串的长度。

最坏情况:主串前面 n - m 个位置的部分都与子串匹配到最后一位,即总次数为(n - m +1) *m。

若m<<n(m远远小于n),则算法复杂度为O(n*m)

平均情况:O(n/2 * m)

3.2KMP算法(顺序存储)

  • KMP较BF算法有较大改进,使算法效率有了某种程度的提高。利用部分已经匹配的结果,主串指针" i “不必回溯,而从当前位置继续比较。模式串” j "回溯到相应位置。

【算法思想】

利用已经部分匹配的结果加快模式串的滑动速度,主串的指针" i "不比回溯,可提速到O(n+m)。

利用一个数组定义为next[j](注:这里" j "从1开始),来存储第“ j ”个字符与主串中相应字符“ 失配 ”时,在模式串中需要回溯的位置。

:一般的,第一个是0,第二个是1。

在这里插入图片描述

例如:

在这里插入图片描述

简单说:就是看这个字符前面的n个字符,与开头的n个字符一样。则k-1=n。所以next[j] = k。

【算法实现】

int Index_KMP(SString S,SString T,int pos){
    int i = pos,j=1;
    while(i<S.length && j<T.length){
        if(j == 0 || S.ch[i]==T.ch[j]){
            i++;
            j++;
        }//if
        else{
            //i不变,j退到next[j]记录的数组。
            j = next[j];
        }
    }
    if(j>T.length){ //如果是j已经超出长度,即匹配成功,返回这一组第一个字符位置
        return i-T.length;
    }
    else //如果是i超出长度,即匹配失败,返回值0。
        return 0;
    
}

//如何求next[j]
void get_next(SString T,int &next[]){
    i=1; //用来标记目前模式串中next数组的位置
    next[1]=0;  //第一个一定是0
    j=0; //用来标记需要回溯的值
    while(i<T.length){
        if(j==0 || T.ch[i] == T.ch[j]){  //模式串中,如果i标记的字符与j标记的字符相等,则同时自增,并且next[i]=j。
            ++i;
            ++j;
            next[i]=j; 
        }//if
        else{
            j=next[j]; //如果两个位置不相等,则j=next[1]。即j=0,也可以使得上面的if继续循环。
        }
    }
}

当模式串" aaaab “与主串” aaabaaaab "匹配时,当i=4,j=4时候,S.ch[4]≠T.ch[4]还需要继续进行i=4,j=3……等比较。所以这个next数组是有缺陷的。

所以对next进行修正。用nextval[]数组存储。nextval是根据next值来求。

对比nextval数组与next数组来说,nextval数组在j回溯到0,也就是j

【nextval数组实现】

void get_nextval(SString T,int nextval[]){
    i=1;
    nextval[1]=0;
    j=0;
    while(i<T.length){
        if(j==0 || T.ch[i] == T.ch[j]){
            ++i;
            ++j;
            if(T.ch[i] != T.ch[j]){
                nextval[i]=j;
            }else{//nextval数组与next数组不同的地方在这里。
                nextval[i]=nextval[j];
            }
        }else{
            j=nextval[j];
        }
    }
}

4.数组

  • 数组:按照一定格式排列起来的具有相同类型的数据元素的集合。

  • 一维数组:像线性表中数据元素为非结构的简单元素,则为一位数组。逻辑结构为线性结构。也就是定长的线性表。

  • 一维数组的声明格式: 数组类型 变量名称[长度];

​ 例:int num[5] = {0, 1, 2, 3, 4}; (这里的下标为 0~4)

  • 二维数组:若一维数组中,数据元素又是一堆一维数组结构,则称为二维数组。

    如示例图:

在这里插入图片描述

  • 二维数组的声明格式:数据类型 变量名称 [行数] [列数];

    ​ 例:int num[5] [8];

在C语言中,二维数组也可以定义为一维数组类型。

typedef elemtype array2[m][n];

//等价于
typedef elemtype array1[n];
typedef array1 array2[m];
  • 以此类推,三维数组……n维数组,即n-1为数组中的元素又是一个一维数组结构,成为n维数组。

注:线性表结构是数组结构的一个特例,而数组结构又是线性表结构的拓展。

数组特点:结构定义后,维数和维界不再改变。除了初始化和销毁外,只有取(get)或修改(set)元素值的操作。

1.数组的存储

假设一个二维数组 a[m] [n]。即m行n列

1.以行为主序:

先按照一维数组存储a[0] [0]到a[0] [n]。然后a[1] [0] 在a[0] [n]后。以此类推。

即按照每一行为一个一维数组,存储第一行后,第二行存储在第一行后面。

在这里插入图片描述

2.以列为主序

先按照一维数组存储a[0] [0]到a[n] [0]。然后a[0] [1] 在a[n] [0]后。以此类推。

即按照每一列为一个一维数组,存储第一列后,第二列存储在第一列后面。

2.矩阵的存储

矩阵:由一个m×n个元素排成的m行n的表。一般将矩阵存储为一个二维数组。

在这里插入图片描述

当一个矩阵中,值相同的元素很多呈现某种规律分布;零元素很多,则很浪费存储空间。则通过“ 压缩存储 ”来存储一些比较特殊的矩阵。

  • 一些比较特殊的矩阵:对称矩阵,对角矩阵,三角矩阵,稀疏矩阵等。(注:矩阵中非零元素较少一般少于5%时,成为稀疏矩阵)
2.1对称矩阵

在这里插入图片描述

根据对称矩阵上或下三角中元素数均为n(n+1)/2。可以以行序为主序将元素存放在一个一维数组s[n(n+1)/2]中。

例:以行序为主序存储下三角

在这里插入图片描述

通过一维数组来存储下三角矩阵的数据。

在这里插入图片描述

如果需要求a[i] [j] 这个数据在一维数组中的位置,i行前面的 i-1行即是1+2+……+(i-1)个元素,并且数组下标从0开始。

所以a[i] [j]在一维数组中的位置为 [ i (i -1) / 2] + j -1。

2.2三角矩阵

在这里插入图片描述

2.3对角矩阵

在这里插入图片描述

在这里插入图片描述

2.4稀疏矩阵(顺序存储)

在这里插入图片描述

压缩存储原则:把每个非零元素通过三元组来表示,将其行、列、值存储到三元组中。

注:一般在三元组的小标为0存储稀疏矩阵的总行数、总列数、非零元素总个数。

在这里插入图片描述

2.5稀疏矩阵(链式存储)——十字链表

该结点除了行、列、值(row,col,value)以外还有两个域:

-right:用于指向同一行的下一个非零元素。

-down:用来指向同一列的下一个非零元素。

在这里插入图片描述

通过行头指针和列头指针来记录某一行或者某一列第一个非零元素。

在这里插入图片描述

学习视频:数据结构——王卓
参考文献:数据机构C语言版第2班——严蔚敏

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值