数据结构——串

基本概念

串(字符串):由零个或多个字符组成的有限序列,一般记为 s= ´a_{1}a_{2}...a_{n}´ ,n≥0

   串名:s

   值:用单引号括起来的字符序列

   串值必须用一对单引号括起来,单引号本身不属于串,其作用是避免与变量名或数的常量混淆

   a_{i} :可以是字母、数字或其他字符

长度:串中字符的数目 n

空串 Φ:零个字符的串,长度为 0

空格串 ´ ´:右一个或多个空格组成的串,其长度为串中空格字符的个数

子串:串中任意个连续的字符组成的子序列

主串:包含子串的串

位置:字符在序列中的序号为该字符在串中的位置,子串在主串中的位置以子串的第一个字符在主串中的位置表示

相等:两个串的长度相等,并且各个对应位置的字符都相等

串类型的最小操作子集:串赋值 StrAssign,串比较 StrCompare,求串长 StrLength,串联结 Concat,求子串 SubString

抽象数据类型定义

ADT String{

    数据对象:D={ a_{i} | a_{i} ∈ CharacterSet , i=1,2,...,n , n ≥ 0 }

    数据关系:R_{1}={ <a_{i-1},a_{i}> | a_{i-1},a_{i} ∈ D , i=2,...,n }

    基本操作:

    StrAssign(&T,chars)

       初始条件:chars 是字符串常量

       操作结果:生成一个其值等于 chars 的串 T

    StrCopy(&T,S)

       初始条件:串 S 存在

       操作结果:由串 S 复制得串 T

    StrEmpty(S)

       初始条件:串 S 存在

       操作结果:若 S 为空串,返回 true ,否则返回 false

    StrCompare(S,T)

       初始条件:串 S 和 T 存在

       操作结果:若 S>T,则返回值>0;若 S=T,则返回值=0;若 S

    StrLength(S)

       初始条件:串 S 存在

       操作结果:返回 S 的元素个数,称为串的长度

    ClearString(&S)

       初始条件:串 S 存在

       操作结果:将 S 清为空串

    Concat(&T,S1,S2)

       初始条件:串 S1 和 S2 存在

       操作结果:用 T 返回由 S1 和 S2 联结而成的新串

    SubString(&Sub,S,pos,len)

       初始条件:串 S 存在,1≤pos≤StrLength(S)

       操作结果:用 Sub 返回串 S 的第 pos 个字符起长度为 len 的子串

    Index(S,T,pos)

       初始条件:串 S 和 T 存在,T 是非空串,1≤pos≤StrLength(S)

       操作结果:若主串 S 中存在和串 T 值相同的子串,则返回它在主串 S 中第 pos 个字符之后第一次出现的位置;否则函数值为 0

    Replace(&S,T,V)

       初始条件:串 S,T 和 V 存在,T 是非空串

       操作结果:用 V 替换主串 S 中出现的所有与 T 相等的不重叠的子串

    StrInsert(&S,pos,T)

       初始条件:串 S 和 T 存在,1≤pos≤StrLength(S)+1 

       操作结果:在串 S 的第 pos 个字符之前插入串 T

    StrDelete(&S,pos,len)

       初始条件:串 S 存在,1≤pos≤StrLength(S)-len+1

  操作结果:从串 S 中删除第 pos 个字符起长度为 len 的子串

    DestroyString(&S)

       初始条件:串 S 存在

       操作结果:串 S 被销毁

}ADT String

顺序表示

定长顺序存储表示

       类似于线性表的存储结构,用一组地址连续的存储单元存储串值的字符序列。

顺序串类型定义

       在串的定长顺序存储结构中,按照预定义的大小,为每个定义的串变量分配一个固定长度的存储区,可用定长数组存储。

        串的实际长度可在该预定义长度的范围内随意,超过预定义长度的串值则被舍去,称之为“截断”。

表示串长:

    1. 以下标为 0 的数组分量存放串的实际长度

    2. 在串值后面加一个不计入串长的结束标记字符,此时串长为隐含值

typedef struct {
	char data[MaxSize];//串中字符
	int length;//串长
}SqString;

初始化

依次将字符串常量的每一个字符赋给串,并记录串长

void StrAssign(SqString& s, char cstr[]) {
	int i;
	for (i = 0;cstr[i] != '\0';i++)//依次给串s的每个元素赋值
		s.data[i] = cstr[i];
	s.length = i;//记录串长
}

串复制

依次将串 t 中的每一个字符复制到串 s 中,将串 t 的长度赋给串 s

void StrCopy(SqString& s, SqString t) {
	for (int i = 0;i < t.length;i++)//依次将串t的每个字符复制到串s
		s.data[i] = t.data[i];
	s.length = t.length;//将串t的长度赋给串s
}

判串相等

       设置一个布尔类型的变量,记录比较的状态。先判断两个串的长度是否相等,若不相等,返回 false,若相等,对对应字符进行比较,直到找到不相等的对应字符或比较完整个串,结束循环,返回状态。

bool StrEqual(SqString s, SqString t) {
	bool same = true;//记录状态
	if (s.length != t.length)//比较串长
		same = false;
	else {
		for (int i = 0;i < s.length;i++) {//若串长相等,追个比较
			if (s.data[i] != t.data[i]) {//存在对应字符不相等
				same = false;//改变状态
				break;//退出循环
			}
		}
	}
	return same;
}

求串长

直接返回串的 length 值

int StrLength(SqString s) {
	return s.length;//直接返回串s的长度,即其length值
}

串连接

       串 T 由串 S1 联结串 S2 得到,即串 T 的值的前一段和串 S1 的值相等,串 T 的值的后一段和串 S2 的值相等,只要进行相应的“串值复制”操作即可。但应根据串长实施“截断”操作。

串 T 产生的情况:

   1. S1.length + S2.length <= MaxSize,得到的串 T 是正确的情况

   2. S1.length < MaxSize,S1.length + S2.length > MaxSize,将串 S2 的一部分截断,得到的串 T 只包含串 S2 的一个子串

   3. S1.length = MaxSize,得到的串 T 并非联结结果,与串 S1 相等

bool Concat(SqString& T, SqString S1, SqString S2) {
	//用T返回S1和S2连接而成的新串,若未截断,返回true,若截断,返回false
	if (S1.length + S2.length <= MaxSize) {//S1.length + S2.length <= MaxSize
		for (int i = 0;i < S1.length;i++)
			T.data[i] = S1.data[i];
		for (int i = S1.length, j = 0;j < S2.length;i++, j++)
			T.data[i] = S2.data[j];
		T.length = S1.length + S2.length;
		return true;
	}
	else if (S1.length < MaxSize) {//S1.length < MaxSize
		for (int i = 0;i < S1.length;i++)
			T.data[i] = S1.data[i];
		for (int i = S1.length, j = 0;i < MaxSize;i++, j++)
			T.data[i] = S2.data[j];
		T.length = MaxSize;
		return false;
	}
	else {//S1.length = MaxSize
		for (int i = 0;i < S1.length;i++)
			T.data[i] = S1.data[i];
		return false;
	}
}

求子串

        返回串 s 中从第 i 个字符开始长度为 j 的字符序列,先判断传入的参数是否正确,子串位置 i ,00,子串最后一个字符的位置 i+j-1,i+j-1<=s.length,参数不正确,返回空串。反之,返回子串。

SqString SubStr(SqString s, int i, int j) {//返回串s中从第i个字符开始长度为j的字符序列
	SqString str;//记录返回的子串
	int k;
	str.length = 0;
	if (i <= 0 || i > s.length || j<0 || i + j - 1>s.length)
		return str;//参数不正确,返回空串
	for (k = i - 1;k < i + j - 1;k++)//s.data[i-1..i+j-2]->str
		str.data[k - i + 1] = s.data[k];
	str.length = j;
	return str;
}

插入子串

       在串 s1 的第 i 个字符之前插入串 s2 ,先判断插入位置是否合法,插入的合法位置有 s1.length 个,分别是1~s1.length+1,所以 1<=i<=s1.length+1 ,若插入位置不合法,返回空串,合法则返回新的字符串。

SqString InsStr(SqString s1, int i, SqString s2) {//在串s1的第i个字符之前插入s2,并返回
	int j;
	SqString str;
	str.length = 0;
	if (i <= 0 || i > s1.length + 1)//1<=i<=s1.length,有s1.length个插入位置
		return str;//参数不正确时返回空串
	for (j = 0;j < i - 1;j++)//s1.data[0..i-2]->str
		str.data[j] = s1.data[j];
	for (j = 0;j < s2.length;j++)//s2.data[0..s2.length-1]->str
		str.data[i + j - 1] = s2.data[j];
	for (j = i - 1;j < s1.length;j++)//s1.data[i-1..s1.length-1]->str
		str.data[s2.length + j] = s1.data[j];
	str.length = s1.length + s2.length;
	return str;
}

删除子串

       删除串 s 的第 i 个字符开始的 j 个字符,先判断删除位置是否合法,删除的位置有 s.length 个,分别是 1~s.length,删除子串的最后一个字符的位置 i+j-1,i+j-1<=s.length,参数不正确,返回空串,参数正确,返回新串。

SqString DelStr(SqString s, int i, int j) {
	int k;
	SqString str;
	str.length = 0;
	if (i <= 0 || i > s.length || i + j > s.length + 1)//删除的子串的位置有s.length个,
		//分别是1~s.length,删除子串的最后一个字符的位置i+j-1<=s.length
		return str;//参数不正确时返回空串
	for (k = 0;k < i - 1;i++)//s.data[0..i-2]->str
		str.data[k] = s.data[k];
	for (k = i + j - 1;k < s.length - 1;k++)//s.data[i+j-1..s.length-1]->str
		str.data[k - j] = s.data[k];
	str.length = s.length - j;
	return str;
}

替换子串

       使用串 t 替换串 s 中的第 i 个字符开始的 j 个字符,先判断被替换子串的位置是否合法,被替换子串的位置有 s.length 个,分别是 1~s.length,被替换子串的最后一个字符的位置 i+j-1,i+j-1<=s.length,参数不正确,返回空串,参数正确,返回新串。

SqString RepStr(SqString s, int i, int j, SqString t) {
	int k;
	SqString str;
	str.length = 0;
	if (i <= 0 || i > s.length || i + j - 1 > s.length)//被替换子串位置i,1<=i<=s.length,
		//被替换子串的最后一个字符的位置i+j-1<=s.length
		return str;//参数不正确返回空串
	for (k = 0;k < i - 1;k++)//s.data[0..i-2]->str
		str.data[k] = s.data[k];
	for (k = 0;k < t.length;k++)//t.data[0..t.length-1]->str
		str.data[i + k - 1] = t.data[k];
	for (k = i + j - 1;k < s.length;k++)//s.data[i+j-1..s.length-1]->str
		str.data[t.length + k - j] = s.data[k];
	str.length = s.length - j + t.length;
	return str;
}

输出

void DispStr(SqString s) {
	if (s.length > 0) {
		for (int i = 0;i < s.length;i++)
			printf("%c", s.data[i]);
		printf("\n");
	}
}

堆分配存储表示

特点:用一组地址连续的存储单元存放串值字符序列,但其存储空间是在程序执行过程中动态分配得到

操作:用堆分配存储的串的操作与定长顺序存储的串的操作基本相同,只是增加了申请存储空间和释放存储空间的操作

类型定义

typedef struct {
	char* ch;//若是非空串,则按串长分配存储区,否则ch为NULL
	int length;//串长度
}HString;

初始化

1. 释放串 T 原有的空间

2. 求 chars 的长度

3. 若 chars 为空串,则说明串 T 为空串,其 ch 指向为空,串的长度为 0

4. 若 chars 不为空,则根据 chars 的长度为串 T 分配空间,若分配失败,则返回 false

5. 若为串 T 分配空间成功,则用 chars 初始化 T,串 T 的长度就是 chars 长度 

bool StrAssign(HString& T, char* chars) {
	if (T.ch)
		free(T.ch);//释放T原有空间
	int i;
	char* c;
	for (i = 0, c = chars;c;i++, c++);//求chars的长度i
	if (!i) {//i==0说明chars是空串
		T.ch = NULL;
		T.length = 0;
	}
	else {
		if (!(T.ch = (char*)malloc(i * sizeof(char))))
			return false;//空间分配失败
		for (int j = 0;j < i;j++)
			T.ch[j] = chars[j];
		T.length = i;
	}
	return true;
}

求串长

int StrLength(HString S) {
	return S.length;
}

判串相等

       逐个比较串 S 和串 T 的对应字符,若对应字符出现不相等的情况,直接比较这两个字符,即两个串的比较结果,返回比较结果;若对应字符均相等,直到其中一个串到达终点时,退出循环,此时比较两个串的长度,即两个串的比较结果【两个串的关系:1.子串关系 2.相等关系】,返回比较结果。

int StrCompare(HString S, HString T) {
	//若S>T,返回值>0;若S=T,返回值=0;若S

清空串

将 S 清为空串,释放用于存储串的存储单元,串的长度为0。

bool ClearString(HString& S) {//将S清为空串
	if (S.ch) {
		free(S.ch);
		S.ch = NULL;
	}
	S.length = 0;
	return true;
}

串连接

bool Concat(HString& T, HString S1, HString S2) {
	//用T返回由S1和S2连接而成的新串
	if (T.ch)
		free(T.ch);//释放旧空间
	if (!(T.ch = (char*)malloc((S1.length + S2.length) * sizeof(char))))//申请新空间
		return false;//申请失败
	T.length = S1.length + S2.length;
	for (int i = 0;i < S1.length;i++)
		T.ch[i] = S1.ch[i];
	for (int i = S1.length, j = 0;j < S2.length;i++, j++)
		T.ch[i] = S2.ch[j];
	return true;
}

求子串

1. 判断参数是否正确

2. 释放 Sub 的旧空间

3. 判断子串是否是空串,若是,则将 Sub 置为空串

4. 为新串 Sub 申请新的空间,并进行连接

bool SubString(HString& Sub, HString S, int pos, int len) {
	//用Sub返回串S的第pos个字符起长度为len的子串
	//1<=pos<=StrLength(S)且0<=len<=StrLength(S)-pos+1
	if (pos <= 0 || pos > S.length || len<0 || len>S.length - pos + 1)
		return false;//参数不正确
	if (Sub.ch)
		free(Sub.ch);//释放旧空间
	if (!len) {//空子串
		Sub.ch = NULL;
		Sub.length = 0;
	}
	else {//完整子串
		Sub.ch = (char*)malloc(len * sizeof(char));
		for (int k = pos - 1;k < pos + len - 1;k++)//Sub.ch[pos-1..pos+len-2]->str
			Sub.ch[k - pos + 1] = S.ch[k];
		Sub.length = len;
	}
	return true;
}

链式表示

       串结构的每个数据元素是一个字符,用链表存储串值时,存在“结点大小”的问题,即每个结点可以存放一个字符,也可以存放多个字符。当结点大小大于 1 时,由于串长不一定是结点大小的整数倍,故链表中的最后一个结点不一定全被串值栈满,此时通常补上“#”或其他的非串值字符(通常“#”不属于串的字符集,是一个特殊的符号)。

       为了便于进行串的操作,当以链表存储串值时,除头指针外还可附设一个尾指针指示链表中的最后一个结点,并给出当前串的长度,称这样定义的串存储结构为块链结构。

       一般情况下,对串进行操作时,只需要从头向尾顺序扫描,则对串值不必建立双向链表。设尾指针的目的是为了便于进行联结操作,应注意联结时需处理第一个串尾的无效字符。

存储密度 = 串值所占的存储位 / 实际分配的存储位

串的块链存储表示

#define CHUNKSIZE 80//块的大小
typedef struct Chunk {
	char ch[CHUNKSIZE];
	struct Chunk* next;
}Chunk;
typedef struct {
	Chunk* head, * tail;//串的头和尾指针
	int curlen;//串的当前长度
}LString;

链串

声明链串结点类型

typedef struct snode {
	char data;
	struct snode* next;
}LinkStrNode;

初始化

1. s 是链表的头结点,为头结点申请存储空间,初始时,头指针和尾指针同时指向头结点

2. 使用尾插法建立单链表,以此建立链串

//初始化,字符串常量cstr赋给串s
void StrAssign(LinkStrNode*& s, char cstr[]) {
	LinkStrNode* r, * p;
	s = (LinkStrNode*)malloc(sizeof(LinkStrNode));
	r = s;//r始终指向尾结点
	for (int i = 0;cstr[i] != '\0';i++) {
		p = (LinkStrNode*)malloc(sizeof(LinkStrNode));
		p->data = cstr[i];//尾插法
		r->next = p;
		r = p;
	}
}

销毁串

销毁串,即销毁链表。从头结点开始,依次向后扫描,挨个遍历。

void DestroyStr(LinkStrNode*& s) {
	LinkStrNode* pre = s, * p = s->next;//pre指向p的前驱前驱结点
	while (p != NULL) {//扫描链串s
		free(pre);//释放pre结点
		pre = p;//pre,p同步向后移一个结点
		p = pre->next;
	}
	free(pre);//循环结束时,p为NULL,pre指向尾结点,释放它
}

复制串

依次将串 t 的每一个结点复制到串 s 中,将每一个结点使用尾插法插入到串 s 中。

void StrCopy(LinkStrNode*& s, LinkStrNode* t) {//串t复制给串s
	LinkStrNode* p = t->next;//使用p对原串进行遍历
	LinkStrNode* q, * r;
	s = (LinkStrNode*)malloc(sizeof(LinkStrNode));//为头结点申请空间
	r = s;//r始终指向新串尾结点
	while (p != NULL) {//将t的所有结点复制到s
		q = (LinkStrNode*)malloc(sizeof(LinkStrNode));
		q->data = p->data;
		r->next = q;//尾插法
		r = q;
		p = p->next;
	}
	r->next = NULL;
}

判串相等

依次比较两个串的每个结点,若两个串的长度和对应结点元素都相等时,说明串相等。

bool StrEqual(LinkStrNode* s, LinkStrNode* t) {
	LinkStrNode* p = s->next;//借助p和q遍历两个串
	LinkStrNode* q = t->next;
	while (p != NULL && q != NULL && p->data == q->data) {//比较串
		p = p->next;
		q = q->next;
	}
	if (p == NULL && q == NULL)//退出循环时,串s和串q都到达串尾,说明两个串相等
		return true;
	else
		return false;
}

求串长

与求链表长度的操作相同。

int StrLength(LinkStrNode* s) {
	int i = 0;
	LinkStrNode* p = s->next;
	while (p != NULL) {//p和i要对应
		i++;
		p = p->next;
	}
	return i;
}

串连接

依次遍历每个串,将原串的每个结点采用尾插法插入到新串中。

LinkStrNode* Concat(LinkStrNode* s, LinkStrNode* t) {
	LinkStrNode* str;//新串的头结点
	LinkStrNode* p = s->next;//p是用于遍历结点的指针
	LinkStrNode* q, * r;//q是新串的新结点
	str = (LinkStrNode*)malloc(sizeof(LinkStrNode));
	r = str;//初始时,尾指针指向头结点
	while (p != NULL) {//将s的所有结点复制到str
		q = (LinkStrNode*)malloc(sizeof(LinkStrNode));
		q->data = p->data;
		r->next = q;//尾插法
		r = q;
		p = p->next;
	}
	p = t->next;
	while (p != NULL) {//将t的所有结点复制到str
		q = (LinkStrNode*)malloc(sizeof(LinkStrNode));
		q->data = p->data;
		r->next = q;
		r = q;
		p = p->next;
	}
	r->next = NULL;
	return str;
}

求子串

       首先,需要判断参数,如果参数不正确,直接返回空串;若参数正确,找到第 i 个结点,然后将第 i 个结点开始的 j 个结点复制到新串中。

LinkStrNode* SubStr(LinkStrNode* s, int i, int j) {
	int k;
	LinkStrNode* str;
	LinkStrNode* p = s->next;
	LinkStrNode* q, * r;
	str = (LinkStrNode*)malloc(sizeof(LinkStrNode));
	str->next = NULL;
	r = str;//r指向新串的尾结点
	if (i <= 0 || i > StrLength(s) || j<0 || i + j - 1>StrLength(s))
		return str;//参数不正确时返回空串
	for (k = 0;k < i - 1;k++)//找到第i个结点
		p = p->next;
	for (k = 1;k <= j;k++) {//将s的第i个结点开始的j个结点复制到str
		q = (LinkStrNode*)malloc(sizeof(LinkStrNode));
		q->data = p->data;
		r->next = q;//尾插法
		r = q;
		p = p->next;
	}
	r->next = NULL;
	return str;
}

插入子串

       在原串的第 i 个结点之前插入子串。首先判断参数是否正确,若不正确,直接返回空串。先将原串的前 i-1 个结点复制到新串中,然后再将子串的每个结点复制到新串中,最后将原串剩余的结点复制到新串中。

LinkStrNode* InsStr(LinkStrNode* s, int i, LinkStrNode* t) {//在串s的第i个结点之前插入串t
	int k;
	LinkStrNode* str;
	LinkStrNode* p = s->next;
	LinkStrNode* p1 = t->next;
	LinkStrNode* q, * r;
	str = (LinkStrNode*)malloc(sizeof(LinkStrNode));
	str->next = NULL;
	r = str;
	if (i <= 0 || i > StrLength(s) + 1)
		return str;//参数不正确时返回空串
	for (k = 1;k < i;k++) {//将s的前i-1个结点复制到str
		q = (LinkStrNode*)malloc(sizeof(LinkStrNode));
		q->data = p->data;
		r->next = q;//尾插法
		r = q;
		p = p->next;
	}
	while (p1 != NULL) {//将t的所有结点复制到str
		q = (LinkStrNode*)malloc(sizeof(LinkStrNode));
		q->data = p1->data;
		r->next = q;
		r = q;
		p1 = p1->next;
	}
	while (p != NULL) {//将s剩余的结点复制到str
		q = (LinkStrNode*)malloc(sizeof(LinkStrNode));
		q->data = p->data;
		r->next = q;
		r = q;
		p = p->next;
	}
	r->next = NULL;
	return str;
}

删除子串

       删除原串从第 i 个结点开始的 j 个结点。首先判断参数是否正确,不正确返回空串。先将原串的前 i-1 个结点复制到新串,然后跳过 j 个结点,将原串剩余的结点复制到新串。 

LinkStrNode* DelStr(LinkStrNode* s, int i, int j) {
	int k;
	LinkStrNode* str;
	LinkStrNode* p = s->next;
	LinkStrNode* q, * r;
	str = (LinkStrNode*)malloc(sizeof(LinkStrNode));
	str->next = NULL;
	r = str;//r指向新串的尾结点
	if (i <= 0 || i > StrLength(s) || j<0 || i + j - 1>StrLength(s))
		return str;//参数不正确返回空串
	for (k = 0;k < i - 1;k++) {//将s的前i-1个结点复制到str
		q = (LinkStrNode*)malloc(sizeof(LinkStrNode));
		q->data = p->data;
		r->next = q;
		r = q;
		p = p->next;
	}
	for (k = 0;k < j;k++)//p沿next跳j个结点
		p = p->next;
	while (p != NULL) {//将结点p及其后的结点复制到str
		q = (LinkStrNode*)malloc(sizeof(LinkStrNode));
		q->data = p->data;
		r->next = q;
		r = q;
		p = p->next;
	}
	r->next = NULL;
	return str;
}

替换子串

       将原串的第 i 个结点开始的 j 个结点用子串 t 替换。首先判断参数是否正确,若不正确,直接返回空串。先将原串的前 i-1 个结点复制到新串,跳过 j 个结点,将子串 t 的全部结点复制到新串,然后将原串的剩余结点复制到新串。

LinkStrNode* RepStr(LinkStrNode* s, int i, int j, LinkStrNode* t) {
	int k;
	LinkStrNode* str;
	LinkStrNode* p = s->next;
	LinkStrNode* p1 = t->next;
	LinkStrNode* q, * r;
	str->next = NULL;
	r = str;
	if (i <= 0 || i > StrLength(s) || j<0 || i + j - 1>StrLength(s))
		return str;//参数不正确时返回空串
	for (k = 0;k < i - 1;k++) {//将s的前i-1个结点复制到str
		q = (LinkStrNode*)malloc(sizeof(LinkStrNode));
		q->data = p->data;
		q->next = NULL;
		r->next = q;
		r = q;
		p = p->next;
	}
	for (k = 0;k < j;k++)//p沿next跳j个结点
		p = p->next;
	while (p1 != NULL) {//将t的所有结点复制到str
		q = (LinkStrNode*)malloc(sizeof(LinkStrNode));
		q->data = p1->data;
		q->next = NULL;
		r->next = q;
		r = q;
		p1 = p1->next;
	}
	while (p != NULL) {//结点p及其的结点复制到str
		q = (LinkStrNode*)malloc(sizeof(LinkStrNode));
		q->data = p->data;
		q->next = NULL;
		r->next = q;
		r = q;
		p = p->next;
	}
	r->next = NULL;
	return str;
}

输出串

与输出链表的操作相同。

void DispStr(LinkStrNode* s) {
	LinkStrNode* p = s->next;
	while (p != NULL) {
		printf("%c", p->data);
		p = p->next;
	}
	printf("\n");
}

字符串匹配算法

朴素算法

       两层循环,双指针算法,外层控制主串的遍历,内层控制子串遍历,若子串与主串不匹配,则将主串起点向后移动一位,子串从头开始遍历。

char S[N], P[M]; // S 长字符串,P 短字符串

for (int i = 1;i <= N;i++) { // 循环长字符串,i 从1或0开始均可

   bool falg = true; //falg 表示匹配状态

   for (int j = 1;j <= M;j++) {

      if (S[i] != P[j]) {

         flag = false;

         break;

      }

   }

}

BF 算法

Index(S,T,pos)

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

  • 若相等,继续逐个比较后续字符
  • 若不等,从主串的下一字符起,重新与模式串的第一个字符比较

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

时间复杂度 O(nm)

int Index_BF(SString S,SString T,int pos){
    int i=pos,j=1;
    while(i<=S.length&&j<=T.length){
        if(s.ch[i]==t.ch[j]){//主串和子串依次匹配下一个字符
            ++i;
            ++j;
        }
        else{//主串,子串指针回溯重新开始下一次匹配,回溯
            i=i-j+2;//i=(i-j+1)+1 i-j+1回到原来的位置,+1后移动到下一个位置
            j=1;
        }
    }
    if(j>=T.length)
        return i-T.length;//返回匹配的第一个字符的下标
    else
        return 0;//模式匹配不成功
}

KMP 算法

优化思路

       很多步骤是不必要的,在不匹配时完全可以不移动主串的指针,仅移动子串的指针.将子串指针向前移动,即将子串整体向后移动,使得主串对应位置改变,也能保证匹配的正确性.KMP算法的关键是将子串指针移动到何处.

数据结构——串_第1张图片

       上图蓝色为主串,红色为子串,当匹配到子串开头对应绿色标记时,假如绿色圆圈和红色圆圈出现不同.此时应将子串向后移动一段距离,如下图[蓝色标记与绿色标记在同一垂直线上]

【标记指的是竖杠,不是圆圈】

       使得棕色标记【开头】到蓝色标记之间的内容 与 棕色标记到绿色标记之间的内容相同,且棕色蓝色的距离尽可能最大。

       即找到子串绿色标记前的内容,前缀子串与后缀子串相等且前后缀起点不重复的最大长度。   【“abcd”的前缀子串为a开头的子串,后缀子串为以d结尾的子串】

       而最大长度便是子串该点匹配失败后指针要移动到的位置【下标从1开始】。将每一个点的失败后指针移动到的位置都求出来,就可以在失败后很快的找到下一次匹配的起点,继续匹配。

而这些预先求出来的位置其实就是kmp的next数组。

next

特殊点处理:第一个点就是特殊点,赋值0,表示退无可退

如何计算next[i]?以i为终点的后缀与以1开始的前缀最大相等,即P[1,j]=P[i-j+1,i]

P abababab 下标分别为1~8

next[1]=0 next[2]=0 next[3]=1 next[4]=2 next[5]=3 next[6]=4 next[7]=5 next[8]=6

next[6]!=6,最大前缀不可以是它本身

      求next过程,其实就是将自己当作主串和子串进行错开匹配,主串从第2个字符开始匹配【因为第1个点是特殊点,next 值已知】,子串从开头开始匹配。使用dp思想,从原来的状态推导出新的状态。

       主串使用 i 指针,子串使用 j 指针,由于每次回退都是回退到当前字符的上一个字符的 next 位置,而字符下标是从1开始的,所以j从0开始,每次匹配都是 i 与 j+1 匹配。

       匹配过程中若是当前主串【i】与子串【j+1】字符匹配不了,则退回到子串的上一个字符的             next 所指向的位置【next[j]】,若是匹配成功,那就说明在使用该字符串匹配时若主串位置的下一个字符匹配失败时,应该回退到当前子串的位置。即next数组的值应该为当前子串的指针位置【next[i] = j + 1】

       若是第一个字符也匹配不了,则说明退无可退,只能令下一个主串字符从子串开头开始匹配,因为每次匹配是 i 与 j+1 匹配,所以给 next 赋值为0,而此时 j 为0

       所以next赋值为 j

for (int i = 2, j = 0;i <= n;i++) {
		while (j && p[i] != p[j + 1])
			j = ne[j];
		if (p[i] == p[j + 1])
			j++;
		ne[i] = j;
	}

匹配过程

数据结构——串_第2张图片

       当 S 的第 i 个字符与 P 的第 j+1 个字符不匹配,移动 P,移动的距离是以 j 为终点的后缀与以 1 开始的前缀相等的最大字符数,移动之后,接下来的字符若相同,则继续进行匹配,若不同则继续移动(递归操作)。

for (int i = 1, j = 0;i <= m;i++) {//每次匹配的是s[i]与p[j+1],所以j前移一位
		while (j && s[i] != p[j + 1])
			j = ne[j];//移动,保证移动之后相等,就可以继续从下一个位置开始匹配,直到j已经移到开头(j退无可退,现在使用的是P的第一个字符与S进行匹配,仍然未匹配成功),或者已经匹配成功,循环停止
		if (s[i] == p[j + 1])//如果匹配成功,j就可以移动到下一个位置
			j++;
		if (j == n) {//匹配成功
			printf("%d ", i - n + 1);
			j = ne[j];//匹配成功之后向后退一步,继续进行匹配,可以不加
		}
	}

例题

给定一个模式串S,以及一个模板串P,所有字符串中只包含大小写英文字母以及阿拉伯数字。

模板串P在模式串S中多次作为子串出现。

求出模板串P在模式串S中所有出现的位置。

输入格式

第一行输入整数N,表示字符串P的长度。

第二行输入字符串P。

第三行输入整数M,表示字符串S的长度。

第四行输入字符串S。

输出格式

共一行,输出所有出现位置的第一个字符的位置(位置从1开始计数),整数之间用空格隔开。

数据范围

1≤N≤10^5

1≤M≤10^6

输入样例

3

aba

5

ababa

输出样例

1 3

#include
using namespace std;
const int N = 10010, M = 100010;
int n, m;
char p[N], s[M];
int ne[N];
int main() {
	cin >> n >> p + 1 >> m >> s + 1;
	//求 next[] 过程
	for (int i = 2, j = 0;i <= n;i++) {
		while (j && p[i] != p[j + 1])
			j = ne[j];
		if (p[i] == p[j + 1])
			j++;
		ne[i] = j;
	}
	//匹配过程
	for (int i = 1, j = 0;i <= m;i++) {//每次匹配的是s[i]与p[j+1],所以j前移一位
		while (j && s[i] != p[j + 1])
			j = ne[j];//移动,保证移动之后相等,就可以继续从下一个位置开始匹配,直到j已经移到开头(j退无可退,现在使用的是P的第一个字符与S进行匹配,仍然未匹配成功),或者已经匹配成功,循环停止
		if (s[i] == p[j + 1])//如果匹配成功,j就可以移动到下一个位置
			j++;
		if (j == n) {//匹配成功
			printf("%d ", i - n + 1);
			j = ne[j];//匹配成功之后向后退一步,继续进行匹配,可以不加
		}
	}
	return 0;
}

练习题

一、选择题

1 . 串是一种特殊的线性表,其特征体现在 (   ) 

A 可以顺序存储 B  数据元素是一个字符 C 可以链接存储 D 数据元素可以使多个字符 

2 . 设有两个串 pq,qp中首次出现的位置的运算称作 (   )

A 连接 B 模式匹配 C 求串长 D  求子串 

3 . 设字符串 S1=“ABCDEFG”S2=“PQRST”,则运算: 

S=CONCATSUBSTRS12LENS2));SUBSTRS1LENS2),2));后的串值为 (   )

A  A BCDEF  B  BCDEFG  C   BCDPQRST    D  BCDEFER

二、判断题 

1 .  空格串和孔串的长度均为 1 (    )

2 . 串是一种特殊的线性表,其特殊性体现在数据元素可以使多个字符。 (    )

3 . 判断两个串是否相等,只需要判断这两个串是否包含相同的字符即可。 (    )

三、简答题:

1. S1 =“Data Structure Course”S2 =“Structure”S3 =“Base”,求:

1Length(S1) 2Compare(S2, S3)

3Insert(S1, 5, S3) 4Delete(S1, 5, 9)

5SubString(S1, 5, 9, T) 6Search(S1, 0, S2)  7Replace(S1, 0, S2, S3)
2 . 什么叫串?串和字符在存储方法上有什么不同?空串和空格串是否相同,为什么?

3 . 串是由字符组成的,长度为1的串和字符是否相同。为什么?

4 . 串是不定长的,表示串的长度有几种方法?C语言中的串采用哪种方法?

5 . 可以说串是数据类型固定为字符类型的线性表,但是串操作和线性表操作的主要不同之处在哪里?

6 . 可以用几种存储方法存储串?

7 . 分别写出串的静态数组存储结构和串的动态数组存储结构的结构体定义。

8 . 为什么动态数组结构下串的实现要增加撤消函数?

9 . t1=“aaab”, t2=“abcabaa”, t3=“abcaabbabcabaacba”,试分别求出他们的next[j]值。

四、算法设计题

1. 设串采用静态数组存储结构,编写函数实现两个串的比较Compare(S, T)。要求比较结果有等于和不等于两种情况。

2 . 设串采用静态数组存储结构,编写函数实现两个串的比较Compare(S, T)。要求比较结果有大于、等于和小于三种情况。

3 . 设串采用静态数组存储结构,编写函数实现串的替换Replace(S, start, T, V),即要求在主串S中,从位置start开始查找是否存在子串T,若主串S中存在子串T,则用子串V替换子串T,且函数返回1;若主串S中不存在子串T,则函数返回0

4 . 设字符串采用单字符的链式存储结构,编写删除串s从位置i开始长度为k的子串的算法。

你可能感兴趣的:(数据结构,数据结构)