笔记:数据结构——第四章 串

第四章 串

4.1 串

  • 串,即字符串(String)是由零个或多个字符组成的有限序列

    例如:S=“Hello World!”;有的地方用双引号(Java、C);有地方用单引号(Python)

  • 子串——任意个连续的字符组成;主串——包含子串的串

    空串——无字符;空格串——字符是空格,每个空格字符占1 B

    字符在主串中的位置:字符在串中的序号;子串在主串中的位置:子串的第一个字符在串中的序号

    (注意:序号是从1开始,而不是0开始)

  • 串 VS 线性表:

    串是一种特殊的线性表,数据元素之间呈现线性关系

    串的数据对象:字符集(如中文字符、英文字符、数字字符、标点字符等)

    串的基本操作:通常以子串为操作对象,如增删改查;Index(S,T)——定位操作;StrCompare (S,T)——比较操作

  • 串的比较操作:先出现更大的符号的串更大(ASCII码:小写字母97>大写字母65);长串前缀与短串相同时,长串更大

  • 拓展——乱码问题:在文件中,原本采用的是一套编码规则;打开文件时,软件以为采用的是另一套编码规则

4.1.2 串的存储结构

*串的顺序存储
  • 静态数组实现——定长顺序存储(函数执行结束后,系统自动回收)

    动态数组实现——堆分配存储(用完需要手动free)

#define MAXLEN 255
typedef struct{
    char ch[MAXLEN];
    int length;
}SString;

typedef struct{
    char *ch;
    int length;
}HString;
  • 保存字符串的长度数据

    1. 方案一:变量length放在数组的末尾

    2. 方案二:变量length放在数组的开头ch[0]

      优点:字符的位序和数组下标相同;缺点:ch[0]为char型,只能放下1B=8bit,表示范围0-255

    3. 方案三:没有length变量,以符号’\0’表示结尾(对应ASCII码的0)

      方法:遍历到’\0’计算length,适用于不常访问字符串长度的情况

    4. 方案四:ch[0]废弃不用,末尾声明变量length(结合方案一、二)

*串的链式存储
  • 存储密度低:在32位计算机中,一个char占 1 B,但一个指针占4B即32个字节bit
  • 让每个结点存放多个字节,来增加存储密度;存不满的字节可以用#填充
typedef strut StringNode{
    char ch;
    struct StringNode *next;
}StringNode,*String;

typedef strut StringNode{
    char ch[4];						//每个结点存放多个字符
    struct StringNode *next;
}StringNode,*String;
*顺序串——求子串
  • (方案四的存储方式)求子串:用Sub返回串S第pos个字符起长度为len的子串
#define MAXLEN 255
typedef struct{
    char ch[MAXLEN];
    int length;
}SString;

bool SubString(SString &Sub,SString S,int pos,in len){
    if(pos+len-1>S.length)
        return false;
    for(int i=pos;i<pos+len;i++)
        Sub.ch[i-pos+1]=S.ch[i];	//此时子串的位置为i-pos+1
    Sub.length=len;
    return true;
}
*顺序串——比较操作
  • 比较操作:若S>T,则返回值>0;若S=T,则返回值为0;若S<T,则返回值<0,
int StrCompare(SString S,SString T){
    for(i=1;i<=SString S&&i<=SString T;i++){	//此处结束长度为两串中较短的
        if(S.ch[i]!=T.ch[i])
            return S.ch[i]-T.ch[i];		//扫描过程,return为直接将值相减
    }
    return S.length-T.length;			//扫描结束,长度较长的更大
}
*顺序串——定位操作
  • 定位操作:设主串S中存在子串T;返回字串第一次出现的位置,否则返回0
int Index(SString S,SString T){
    int i=1,n=StringLength(S),m=StringLength(T);
    SString sub;
    while(i<=n-m+1){					//前面定义i初始为1,所以+1
        SubString(Sub,S,i,m);			//i初始必为1,在主串里取一个长度相同的子串
        if(StrCompare(sub,T)!=0)		//比较两个子串是否一样
            i++;						//不一样就后移继续比较
        else
            return i;					//一样就返回子串位置
    }
    return 0;							//全部比较,没有相等子串
}

4.2 串的模式匹配

4.2.1 串的朴素模式匹配算法

  • 子串:一定是主串中存在的才叫“子串”;模式串:想尝试在主串中找到的串,未必存在

  • 串的模式匹配:在主串中找到与模式串相同的子串,并返回其所在位置

*朴素模式匹配算法
  • 与定位操作相似,但只要有一个字符不同就可以停止检查当前子串

  • 时间复杂度为O(nm);n远大于m

int Index(SSring S,SString T){
    int k=1;							//k为检查子串在主串的位置
    int i=k;j=1;						//i为检查子串的检查字符的位置,j为子串检查字符的位置
    while(i<=S.length&&i<=T.length){
        if(S.ch[i]==T.ch[j]){
            i++;
            j++;
        }else{
            k++;
            i=k;
            j=1;
        }
    }
    if(j>T.length)						//循环结束是因为子串匹配成功
        return k;
    else								//循环结束是因为子串到末尾也没匹配到
        return 0;
}

//方法二:不设置变量k
int Index(SSring S,SString T){						
    int i=1;j=1;						
    while(i<=S.length&&i<=T.length){
        if(S.ch[i]==T.ch[j]){
            i++;
            j++;
        }else{
            i=i-j+2;					//i-(j-1)+1; j-1子串扫描过的,+1后移一位
            j=1;
        }
    }
    if(j>T.length)						
        return i-T.length;				//返回第一个字符的位置
    else
        return 0;
}

4.2.2 KMP算法(上)

  • 模式串指针回溯位置——int next[7]

  • 例如:g o o g l e

    当j=k且发现字符不匹配是,令j=next[k];若当前两字符匹配,i++,j++;

    若j=1时发生不匹配,则应让j回到0,i++,j++(让i++,j依旧为1);若j=2时发生不匹配,则应让j回到1;

    若j=3时发生不匹配,则应让j回到1;若j=4时发生不匹配,则应让j回到1;

    若j=5时发生不匹配,则应让j回到2;若j=6时发生不匹配,则应让j回到1

*KMP算法代码
int Index_KMP(SString S,SString T,int next[]){
    int i=1,j=1;
    while(i<=S.length||j<=T.length){
        if(j==0||S.ch[i]==T.ch[j]){		//如果从0开始,为i-1、j-1
            i++;
            j++;						//初始时或匹配,继续往后比较
        }else{
            j=next[j];					//不匹配,模式串向右移动
        }
        if(j>T.length)
            return i-T.length;
        else
            return 0;
    }
}

4.2.3 KMP算法(下)

*求next数组
  • 串的前缀:包含第一个字符,且不包含最后一个字符的子串

    串的后缀:包含最后一个字符,且不包含第一个字符的子串

  • 当第j个字符匹配失败,由前1~j-1个字符组成的串记为S,则next[j-1]=S的最长相等前后缀长度+1

    特别地,next[1]=0;且next[2]=1,前后缀无相等next[x]=1(0+1)

  • 时间复杂度为O(m+n)

void get_next(SString T,int next[]){
    int i=1,j=0;
    next[1]=0;
    while(i<T.length){
        if(j==0||T.ch[i]==T.ch[j]){
            i++;
            j++;
            next[i]=j;
        }else
            j=next[j];
    }
}

int Index_KMP(SString S,SString T){
    int i=1,j=1;
    int next[T.length+1];				//next[0]为空才能对应
    get_next(T,next);					//时间复杂度为O(n)
    while(i<=S.length||j<=T.length){	//时间复杂度为O(m)
        if(j==0||S.ch[i]==T.ch[j]){
            i++;
            j++;						
        }else{
            j=next[j];					
        }
        if(j>T.length)
            return i-T.length;
        else
            return 0;
    }
}

4.2.4 KMP算法的优化

  • next[j]的优化:当子串和模式串不匹配时j=nextval[j]

    思路:如果next回溯的字符和此字符相同,可将回溯字符的next给此字符:

*求nextval数组
//先算出next数组,先令nextval[1]=0
forint j=2;j<=T.length;j++){
    if(T.ch[next[j]]==T.ch[j])
        nextval[j]=nextval[next[j]];
    else
        nextval[j]=next[j];
}

补充:广义表

  • 广义表(列表Lists)是n>=0个元素的有限序列,其中每一个元素或者是原子,或者是一个广义表;例如 LS=(a 1,a 2, … ,an)

  • 其中:LS为表名,n为表的长度,每一个a i为表的元素;习惯上,一般用大写字母表示广义表,小写字母表示原子

  • 表头:若LS非空(n>=1),则其第一个元素就是表头;记作head(LS)=a 1;注意:表头可以是原子,也可以是表子

    表尾:除表头之外的其他元素组成的表;记作tail(LS)=(a 2,a 3,…,an);注意:表尾不是一个元素,而是一个子表;例如:A=(x,y,z) 长度为3,表头为x,表尾为(y,z);每一项都是原子

    B=(()) 长度为1,表头、表尾均为();代表一个空子表

    C=(a,(b,c)) 长度为2,表头为a,表尾为((b,c));由原子a和子表(b,c)组成

    D=(a,D) 长度为2,表头为a,表尾为(D);由原子a和它本身组成

  • 广义表的性质:

    1. 具有相对次序,一个直接前驱和一个直接后继
    2. 广义表的长度定义为最外层所包含元素的个数
    3. 广义表的深度为该广义表展开后所含括号的重数;注意:“原子”的深度为0,“空表”的深度为1
    4. 广义表可以为其他广义表共享,通过名称来引用
    5. 广义表也可以是一个递归的表;注意:递归表的深度是无穷值,长度是有限值
    6. 广义表是多层次结构,元素可以是单元素,也可以是子表;可以用图形象的表示
  • 广义表和线性表的区别:广义表是线性表的推广,线性表是广义表的特例;广义表可以兼容线性表、数组、树和有向图等常用数据结构

  • 广义表的基本运算

    1. 求表头:可以是第一个元素,也可以是第一个表
    2. 求表尾:一定是一个表
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值