4.3串的链式存储与串的应用举例
1、块链串
由于串也是一种线性表,因而也可以采用链式存储。因为串是一个特殊的线性表(表中每个元素就是一个字符)。在具体实现时,一个链表存放一个串值,每个结点既可以存放一个字符, 也可以存放多个字符。每个结点称为块,整个链表称为块链结构,为便于操作,再增加一个尾指针。
块链结构可定义如下:
#define BLOCK_SIZE 4
/*每结点存放字符个数 4*/
typedef struct Block
{
char ch[BLOCK_SIZE];
struct Block *next;
}Block;
typedef struct
{
Block *head;
Block *tail;
int len;
}BLString;
结点大小:链表中的结点分成两个域data和link,其中结点大小是指data域中存放字符的个数,链域大小是指 link 域中占用字符的个数。
如果一个结点大小为 4 ,链域大小为 2 ,根据存 储密度=串值占用的存储位/实际为串分配存储位,故该字符串的存储密度为 2 /3 。
显然,串的存储密度越小,运算处理就越方便,但存储占用的量较大。应根据具体情况来确定使用串的何种存储结构。
结点大小等于 1 :当 BLOCK_SIZE 等于 1 时,每个结点存放 1 个字符,结构同线性链表,存储结构可定义如下,插入、删除的处理方法和线性链表一样,参见本章典型题解部分串的链式 模式匹配,算法处理简单,但存储密度较低。
结点大于 1 :当 BLOCK_SIZE 大于 1 时,每个结点存放多个字符,当最后一个结点未存满 时,不足处可用特定字符(如#)补齐。虽然存储密度相对结点大小等于 1 的存储方法来说,存储 密度较高,但此时插入、删除的处理方法比较复杂,需要考虑结点的分拆和合并。
2、串的应用举例:简单的行编辑器
可将文本看成为一个大的字符串,文本编辑就相当对字符串的处理。
文本编辑程序用于源程序的输入和修改,公文书信、报刊和书籍的编辑排版等。常用的文本编辑程序有 Edit,WPS,Word 等。
文本编辑的实质是修改字符数据的形式和格式,虽然各个文本编辑程序的 功能不同,但基本操作是一样的,都包括串的查找、插入和删除等。
为了编辑方便,可以用分页符和换行符将文本分为若干页,每页有若干行。
可以把文本当作一个字符串,称为文本串,页是文本串的子串,行是页的子串。
这里采用堆存储结构来存储文本,同时设立页指针、行指针和字符指针,分别指向当前操作的页、行和字符,同时建立页表和行表存储每一页、每一行的起始位置和长度。
假设有如下 Pascal 源程序:
FUNC max(x,y:integer): integer;
VAR z; integer;
BEGIN
IF x>y THEN z:=x;
ELSE z:=y;
RETURN(z);
END;
该程序输入内存后放到一个堆中,如图 4.3 所示。
其中↙为换行符。表 4-1 和表 4-2 分别为下图所示文本串的页表和行表。
由表 4 -1 、表 4 -2 可以看出,当在某行内插入字符时,就要修改行表中该行的长度,若该 行的长度超出了分配给它的存储空间,则要重新给它分配存储空间,同时修改它的起始位置和长度。如果要插入或删除一行,就要进行行表的插入和删除,当行的插入和删除涉及页的变化是时要对页表进行修改。
3、总结与提高
1、 主要知识点
( 1 )字符串是一种特定的线性表,其特殊性就在于组成线性表的每个元素就是一个单字 符。
( 2 )字符串常用的存储方式有三种:定长顺序串、堆串和块链串。
( 3 )定长顺序串以一维数组作为静态存储结构,运算实现类同顺序表。堆串以动态分配的 方式产生一组地址连续的存储单元顺序存放串中的字符,运算实现类同顺序串。块链串以链表作为存储结构,运算实现类同链表。
( 4 )串的模式匹配算法是本章的重点,简单的模式匹配算法处理思路简单,由于进行了带回溯的处理,时间复杂度较高。改进的 KMP 模式匹配算法计算滑动位置,因而失配后无回溯, 匹配速度较高。
2、 典型题例
要求编写一个用带头结点的单链表实现串的模式匹配算法,每个结点存放一个字符(结点大小为 1 )。 借助于前面的结点大小为 1 定义链串类型
LKString。
【问题分析】
该算法类同顺序串的简单模式匹配,实现匹配过程需考虑链表的特征(从头 比较的技术,指针保留的技术)。
【算法思想】
从主串 s 的第一个字符和模式串 t 的第一个字符开始比较,如果相等,就继 续比较后续字符,如果不等,则从主串 s 的下一个字符开始重新和模式串 t 比较。一直到模式串 t 中的每一个字符依次和主串 s 中的对应字符相等,则匹配成功,返回主串的当前起始位置 指针。如果主串中没有和模式串相同的子串,则称匹配不成功,返回空指针 NULL。
【算法描述】
Link *StrIndex(LKString *s, LKString *t)
/* 求模式串 t 在主串 s 中第一次出现的位置指针 */
{
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; /* 模式串从第一个结点重新开始 */
}
}
if ( tp == NULL)
return start; /* 匹配成功,返回主串当前起始位置指针 */
else
return NULL; /* 匹配不成功,返回空指针 */ }