“ 串由零个或者多个字符组成的有限序列,又名字符串,相邻字符之间具有前驱和后继的关系,是一种特定的线性表。”
串插入函数
在进行串插入时,插入位置pos将串分为两部分(假设为A,B长度为LA,LB),及待插入部分(假设为C,长度为LC),则串由插入前的AB变为ACB,可能有三种情况:
1 插入后串长:LA+LB+LC<=MAXLEN:则将B后移LC个元素位置,再将C插入
1 插入后串长:>MAXLEN:则将B后移时会有部分字符被舍弃
1 插入后串长:>MAXLEN且pos+LC>MAXLEN:则将B全部字符被舍弃,并不需要后移,并且C在插入时也有部分字符被舍弃
StrInsert(s,pos,t) /*在串s中序号为pos的字符之前插入串t*/ SString *s,t; int pos; { int i; if(pos <= 0 || pos > s->len) /*插入位置不合法*/ return 0; if(s->len + t.len <=MAXLEN ) /*插入后串长<=MAXLEN */ { for (i = s->len + t.len - 1;i>=t.len + pos;i--) s->ch[i] = s ->ch[i-t.len]; for(i =0;i < t.len;i++) s->ch[i + pos] = t.ch[i]; s->len = s->len + t.len; } else if(pos + t.len <= MAXLEN) // 插入后串长>MAXLEN,但串t的字符序列可以全部插入 { for(i = MAXLEN - 1;i > t.len + pos - 1;i--) s->ch[i] = s-> ch[i - t.len]; for(i = 0;i < t.len;i++) s->ch[i + pos] = t.ch[i]; s->len = MAXLEN ; } else { // 串t的部分字符序列要舍弃 for(i = 0;i < MAXLEN - pos; i++) s->ch[i + pos] = t.ch[i]; s->len = MAXLEN ; } }串删除函数
StrDelete(s,pos,len) /* 在串s中删除从序列pos起len个字符·*/ SString *s;int pos,len; { int i; if(pos < 0 || pos > (s->len - len) ) return 0; for (i = pos + len ;i < s->len;i++) s->ch[i - len] = s ->ch[i]; s->len = s->len-len; return (1); }串复制函数
StrCopy(s,t) /* 在串t中值复制到序列p串s*/ SString *s ,t; { int i; for (i = 0 ;i < t.len;i++) s->ch[i ] = t.ch[i]; s->len = t.len; }判空函数
StrEmpty(s) /* 若串s为空(即串长为0)则返回1,否则返回0 */ SString s; { if(s.len == 0 ) return(1); else return (0); }串比较函数
StrCopare(s,t) /* 相等返回0,s>t返回1,s<t返回-1*/ SString s ,t; { int i; for (i = 0 ;i < s.len && i < t.len;i++) if(s.ch[i] != t.ch[i]) return (s.ch[i] - t.ch[i]); return(s.len - t.len); }串长函数
StrLength(s) SString s; { return (s.len); }清空函数
StrClear(s) SString *s; { s->len = 0; return (1); }连接函数
StrCat(s,t) /*串t连接到串s后面*/ SString *s,t; { int i,flag; if(s->len + t.len <= MAXLEN ) { /* 链接后串 <= MAXLEN */ for (i = s->len ;i < s->len + t->len;i++) s->ch[i] = t ->ch[i-s-len]; s->len += t.len; flag = 1; } else if(s->len < MAXLEN) { // 连接后串长度大于MAXLEN,但串s的长度小于MAXLEN,即连接后串t的·部分·字符序列被舍弃 */ for(i = s->len;i < MAXLEN;i++) s->ch[i] = t.ch[i - s->len]; s->len = MAXLEN ; flag = 0; } else flag = 0; // 串s的长度等于 MAXLEN ,串t不被连接 return (flag); }求子串函数
StrString(sub,s,pos,len) /* 在串s序列pos起len个字符复制到sub中·*/ SString *sub,s;int pos,len; { int i; if(pos < 0 || pos > s.len || len<1|| len > s.len-pos) {sub->len = 0;return (0);} else { for (i = 0;i < len ;i++) sub->ch[i] = s.ch[i + pos]; sub->len = len; return (1); } }定位函数
StrIndex(s,pos,t) /* 求串t在串s中的位置 SString s ,t; int pos; { int i,j; if(t.len == 0) return (0); i=pos;j=0; while(i<s.len && j<t.len) if(s.ch[i]==t.ch[j]) {i++;j++;} else {i=i-j+1;j=0;} if(j>=t.len) return(i-j); else return(0); }堆串
堆:系统将一个地址连续,容量很大的存储空间作为字符串的可用空间。
每建立一个新串时,系统就从这个空间中分配一个大小和字符串长度相同的空间储存新串的串值。
通过指针来指示起始和终结位置
堆串的存储表示,用C语言的堆实现堆串,其定义为
typedef struct { char *ch; int len; } HString;其中len域指示串的长度,ch域指示串的起始地址
StrIndex(s,pos,t) HString s,t; int pos; { int i,j; if(s.len == 0 || t.len == 0) return (0); i = pos;j = 0; while(i<s.len && j<t.len) if(s.ch[i] == t.ch[j]) {i++;j++;} else{i = i-j+1;j=0;} if(j>=t.len) return(i-j); else return(0); }内部实现需要给出起始位置,串的长度
块链串
块链串也是一种线性表,因而也可以采用链式储存·。在具体实现的时候,一个链表存放一个串值,每个结点既可以存放一个字符,也可以存放多个字符。每个结点称为块,整个链表称为块链结构,为便于操作,再增加一个尾指针。
一个结点放一个元素处理类似单链表,多个元素有些不同
块链结构定义:
#define BLOCK_SIZE 4 typedef struct Block { char ch[BLOCK_SIZE]; struct Block *next; } Block; typedef struct { Block *head; Block *tail; int len; } BLString;4代表一个结点放四个元素
基本结点块定义:存放字符的部分(结点) 结点关联指针 两部分可分别定义大小
定义链表指针
可将文本看成一个大的字符串,文本编辑器就相当对字符串的处理。文本编辑程序用于原程序的输入和修改,公文书信,报刊和书籍的编辑排版等。常用的文本编辑器程序有Edit,Wps,Word等。文本编辑的实质是修改字符串数据的形式和格式,虽然各个文本编辑器程序的功能不同,但基本操作是一样的,都包括串的查找,插入和删除等。
为了编辑方便,可以用分页符和换行符将文本分为若干也,每页有若干行。我们把文本当做一个字符串,称为文本串,页是字符串的子串,行是页的子串。
我们采用堆存储结构来存储文本,同时设立页指针,行指针和字指针,分别来指向当前操作的页,行和字符,同时建立页表和行表存储每一页,每一行的起始位置和长度。
该程序输入内存后放到一个堆中,如下图所示。其中->为换行符
由以上表格肯出,在某行内插入字符时,就要修改行表中该行的长度,若运行长度超出了分配给他的存储空间,则需重新给他分配存储空间同时修改他的起始位置和长度,如果要插入或者删除一行,则需要进行行表的插入和删除,当行的插入和删除涉及页的变化就要对页表进行修改。
通过指针来指示起始和终结位置
字符串是一种特定的线性表,其特殊性就在于组成线性表的每个元素就是一个单字符。
字符串常用的存储形式有三种:顺序串、堆串和块链串。
顺序串以一维数组存储,欲奴三实现类同顺序表。
块链串以链表作为存储结构,运算实现类同链表。
串的模式匹配算法是本章的重点。
简单的模式匹配算法,处理思路简单,由于是带回溯的处理,时间复杂度较高。
改进的模式匹配算法(KMP),是无回溯的处理提高了处理的速度。这种的算法是D.E.Knuth与V.R.Pratt和J.H.Morris同时发现的,因此人们称它为克努特-莫里斯-普拉特算法(简称KMP算法)该算法可以在O(n+m)的时间数量级上完成串的模式匹配操作,其改进在于:每当一趟匹配过程中出现字符比较不等时,不需要回溯i指针,而是利用已经得到的‘部分匹配’的结果将模式向右‘滑动’尽可能远的一段距离后,继续进行比较。
假设主串为,模式串为
为了实现改进算法,需要解决下述问题:
当匹配过程中产生“失配”(即)时,主串中的第i个字符应和模式中的哪个字符再比较?
(1) 假设此时主串中第i个字符应与模式中的第k(k<j)个字符继续比较,则模式中前k-1个字符的子串必须满足下列关系式,且不存在k’>k满足下列关系式:
(2) 当主串中第i个字符与模式中的第j个字符“失配”时,已经得到的“部分”匹配结果是:
由(1)和(2)推得下列等式:
我们称前缀子串,为的后缀子串。
若模式串中存在满足上式的两个子串,则匹配过程找那个第i个字符串与模式找那个第j个字符串相比较不等时,仅需将模式向右滑动至模式中的第K个字符和主串中第i个字符对齐。
若令next[j]=k则next[j]表明当模式中第j个字符与主串中相应字符“失配”时,在模式中需要重新和主串中该字符进行比较的字符位置。由此可引出模式串的next函数的定义:
sp tp指针保留
典型题例
要求编写一个用带头结点的单链表实现串的模式匹配算法。
每个结点存放一个字符(节点大小为1)借助节点大小为1定义链串类型LKString。
【问题分析】
改算法类同顺序串的简单模式匹配,实现匹配过程序需考虑链表的特征(从头比较的技术,指针保留的技术)。
【算法思想】
从主串s的第1个字符和模式串t的第1个字符开始比较,如果相等,就继续比较:如果不等,则从主串s的下一个字符开始重新和模式串t比较。直到模式串t中的每一个字符依次和主串s中的对应字符相等,则匹配成功,返回主串的当前起始位置指针。如果主串处理完还没有找到和模式串相同的子串,则称匹配不成功,返回空指针NULL。
【算法实现】
下面用单链表实现了串的模式匹配
Link *StrIndex(LKString *s,LKString *t) { Link *sp,*tp,*start; If(t->len==0) return s->head->next;/*空串是任意串的子串*/ Start = s->head-next; /*记录主串的起始比较位置*/ sp=start; /*主串从start开始*/ tp=t->head->next; /*模式串从第一个节点开始*/ while(sp!=NULL&&tp!=NULL) { If(sp->ch==tp->ch) /*若对应字符相同则继续比较*/ { sp=sp->next; tp=tp->next; } else/*发现失配字符,返回到主串当前起始位置的下一个节点继续比价*/ { start=start->next; /*更新主串的起始位置*/ sp=start; tp=t->head->next; } } }