数据结构(三)串
(一)定义
串(string)是由零个或者多个字符组成的有限序列,又名叫字符串。
串中的字符数目n称为串的长度
串是有限序列:所谓序列,说明相邻字符之间具有前驱和后继的关系
零个字符的串称为空串,注意:空串不是空格串,空格串有内容,有长度,空串无内容和长度
子串和主串:串中任意个数的连续字符组成的子序列称为该串的子串。相应的,包含子串的串称为主串
子串在主串中的位置就是子串的第一个字符在主串中的序号
(二)串的比较
通过组成串的字符之间的编码来进行的,而字符的编码是指字符在对应字符集中的序号
与strcmp一样,按顺序比较,只有出现第一个字符不同,比较其大小,就决定了这个串的大小:cmp>cMp比较到第二个字符就结束了
(三)串的抽象数据类型
串的逻辑结构与线性表相似。不同之处在于串中的数据是针对字符集,而线性表可以针对多种。
更大的区别在于:线性表更关注单个元素的操作,如查找一个元素,删除一个元素,插入一个元素,但是串更多是查找子串位置,得到指定位置的子串,替换子串等操作
ADT 串(string) Data 串中的元素仅由一个字符组成,相邻元素具有前驱和后继更新 Operation //生成串相关 StrAssign(T,*chars):生成一个其值等于字符串常量的chars的串T StrCopy(T,S):串S存在,由串S复制得到串T Concat(T,S1,S2):用T返回由S1和S2连接而成的新串 //基础操作相关 ClearString(S):串S存在,将串清空 StringEmpty(S):若串为空,返回true,否则false StringLength(S):返回串S的元素个数,长度 //比较串,索引串相关 StrCompare(S,T):若S>T返回>0,=返回0,<返回<0 SubString(Sub,S,pos,len):串S存在,返回S由pos起,长度为len的子串到Sub Index(S,T,pos):主串S,子串T,返回T在S中位置 //增删改相关 Replace(S,T,V):串S,T和V存在,T非空,用V替换主串S中T串 StrInsert(S,pos,T):在主串S中的pos位置插入串T StrDelete(S,pos,len):串S存在,从串S中删除第pos个字符串起长度为len的子串 endADT
还有其他扩展:转小写,转大写,左查找子串,右查找子串,去空格...
(四)串的存储结构
串同线性表:分为两种存储结构,顺序存储和链式存储
1.串的顺序存储结构
还是使用数组或者堆分配一个固定长度的存储区。一般使用定长数组来定义,不过,数组存在溢出错误,或者截断现象。 另外注意:我们将串的长度保存在下标为0处位置,方便计算。其他方法来标记也可以:比如在结尾使用\0,但是对于我们获取串长度,需要遍历,而且也是需要占用一个空间
2.串的链式存储结构
链式存储结构,一个结点需要消耗一个指针域空间,若是字符串过多,消耗大,所以可以考虑使用一个结点存放多个字符,对于最后的结点未占满,可以使用'#'等其他非串值字符补齐
3.比较
链式存储结构除了在连接串与串操作时有一定的方便之外,总的来说不如顺序存储灵活,性能也不如顺序存储结构好。
(五)代码实现(顺序结构)
#define _CRT_SECURE_NO_WARNINGS #include <stdio.h> #include <stdlib.h> #include <string.h> #define OK 1 #define ERROR 0 #define TRUE 1 #define FALSE 0 #define MAXSIZE 40 typedef int ElemType; typedef int Status; //设置串的存储结构 typedef char String[MAXSIZE+1]; //生成串相关 Status StrAssign(String S,char *chars); //生成一个其值等于字符串常量的chars的串T Status StrCopy(String T, String S); //串S存在,由串S复制得到串T Status Concat(String T, String S1, String S2); //用T返回由S1和S2连接而成的新串 //基础操作相关 Status ClearString(String S); //串S存在,将串清空 Status StringEmpty(String S); //若串为空,返回true,否则false int StringLength(String S); //返回串S的元素个数,长度 //比较串,索引串相关 int StrCompare(String S, String T); //若S > T返回 > 0, = 返回0, < 返回 < 0 Status SubString(String Sub, String S,int pos,int len); //串S存在,返回S由pos起,长度为len的子串到Sub int Index(String S, String T,int pos); //主串S,子串T,返回T在S中位置 //增删改相关 Status Replace(String S, String T, String V); //串S,T和V存在,T非空,用V替换主串S中T串 Status StrInsert(String S, int pos, String T); //在主串S中的pos位置插入串T Status StrDelete(String S,int pos,int len); //串S存在,从串S中删除第pos个字符串起长度为len的子串 void PrintStr(String S); //生成串相关 //生成一个其值等于字符串常量的chars的串T Status StrAssign(String S, char *chars) { int i; if (strlen(chars) > MAXSIZE) return ERROR; else { S[0] = strlen(chars); for (i = 1; i <= S[0];i++) S[i] = *(chars+i-1); return OK; } } //串S存在,由串S复制得到串T Status StrCopy(String T, String S) { int i; for (i = 0; i <= S[0];i++) T[i] = S[i]; return OK; } //用T返回由S1和S2连接而成的新串,若是超出,会截断,但是会进行连接,返回FALSE Status Concat(String T, String S1, String S2) { int i,j,interLen; //interLen是截断后S2剩余长度 if (S1[0] + S2[0] > MAXSIZE) interLen = MAXSIZE - S1[0]; else interLen = S2[0]; T[0] = S1[0] + S2[0]; for (i = 1; i <= S1[0]; i++) T[i] = S1[i]; for (j = 1; j <= interLen; j++) T[i+j-1] = S2[j]; if (interLen != S2[0]) return ERROR; return OK; } //基础操作相关 //串S存在,将串清空 Status ClearString(String S) { S[0] = 0; return OK; } //若串为空,返回true,否则false Status StringEmpty(String S) { if (S[0] != 0) return FALSE; return TRUE; } //返回串S的元素个数,长度 int StringLength(String S) { return S[0]; } //比较串,索引串相关 //若S > T返回 > 0, = 返回0, < 返回 < 0 int StrCompare(String S, String T) { int i; for (i = 1; i <= S[0] && i <= T[0]; i++) if (S[i] != T[i]) return S[i] - T[i]; return S[0]-T[0]; //若是相同比较长度即可 } //串S存在,返回S由pos起,长度为len的子串到Sub Status SubString(String Sub, String S, int pos,int len) { int i; if (pos<1 || len<0 || pos + len - 1 > S[0] || pos>S[0]) return ERROR; for (i = 0; i < len;i++) Sub[i + 1] = S[pos + i]; Sub[0] = len; return OK; } //主串S,子串T,返回T在S中位置,pos代表从pos开始匹配 //或者一次截取一段进行比较为0则找到 int Index(String S, String T, int pos) { int i, j; i = pos; j = 1; while (i<=S[0]-T[0]+1&&j<=T[0]) { if (S[i]==T[j]) { j++; i++; } else { i = i - j + 2; //注意这个索引的加2,因为j是从1开始的,导致我们i-j会小一,所以我们要在到下一个位置的基础上i-j+1再加1,补上去 j = 1; } } if (j > T[0]) return i - T[0]; return 0; } //增删改相关 //串S,T和V存在,T非空,用V替换主串S中T串 Status Replace(String S, String T, String V) { int idx=1; if (StringEmpty(T)) return ERROR; while (idx) { idx = Index(S, T, idx); if (idx) { StrDelete(S, idx, StringLength(T)); StrInsert(S, idx, V); idx += StringLength(V); } } return OK; } //在主串S中的pos位置插入串T,注意:若是串满,则只插入部分 Status StrInsert(String S, int pos, String T) { int i,interLength; if (S[0] + T[0] > MAXSIZE) //长度溢出 interLength = MAXSIZE - S[0]; else interLength = T[0]; for (i = S[0]; i >= pos;i--) S[interLength + i] = S[i]; //将后面的数据后向后移动 //开始插入数据 for (i = 1; i <= interLength; i++) S[pos + i - 1] = T[i]; S[0] += interLength; if (interLength != T[0]) return ERROR; return OK; } //串S存在,从串S中删除第pos个字符串起长度为len的子串 Status StrDelete(String S, int pos,int len) { int i; if (pos < 1 || len<1 || pos + len - 1>S[0]) return ERROR; //将数据前移 for (i = pos+len; i <= S[0];i++) S[i-len] = S[i]; S[0] -= len; return OK; } void PrintStr(String S) { int i; for (i = 1; i <= StringLength(S);i++) { printf("%c", S[i]); } printf("\n"); }
int main() { int i, j; String s1,s2,t; char *str = (char*)malloc(sizeof(char) * 40); memset(str, 0, 40); printf("enter s1:"); scanf("%s", str); if (!StrAssign(s1, str)) printf("1.string length is gt %d\n", MAXSIZE); else printf("1.string StrAssign success\n"); printf("enter s2:"); scanf("%s", str); if (!StrAssign(s2, str)) printf("1.string length is gt %d\n", MAXSIZE); else printf("1.string StrAssign success\n"); i = StrCompare(s1, s2); printf("print All String:\n"); PrintStr(s1); PrintStr(s2); printf("2.string StrCompare test\n"); if (i < 0) printf("s1 < s2\n"); else if (i > 0) printf("s1 > s2\n"); else printf("s1 = s2\n"); printf("3.string Concat test\n"); if (Concat(t,s1,s2)) printf("string Concat success\n"); else printf("string Concat interrupt\n"); PrintStr(t); printf("4.string StrCopy test\n"); StrCopy(s1, t); printf("copy t to s1,print s1:"); PrintStr(s1); printf("5.string s1 length:%d\n",StringLength(s1)); printf("6.substring test\n"); SubString(t, s1, 5, 4); printf("get s1 from 5 to 9-->t:"); PrintStr(t); printf("7.Index func test\n"); i = Index(s1, t, 1); printf("find t in s1 index:%d", i); printf("8.replace test\n"); printf("please enter string to match:"); scanf("%s", str); StrAssign(s2, str); printf("please enter string to replace:"); scanf("%s", str); StrAssign(t, str); Replace(s1, s2, t); PrintStr(s1); printf("9.please enter string to insert:"); scanf("%s", str); StrAssign(t, str); printf("please enter pos to insert:"); scanf("%d", &j); StrInsert(s1, j, t); PrintStr(s1); printf("10.please enter pos to delete:"); scanf("%d", &i); printf("please enter length to delete:"); scanf("%d", &j); StrDelete(s1, i, j); PrintStr(s1); ClearString(s1); printf("11.string ClearString s1 test\n"); printf("12.s1 has Cleaned,is empty:%d", StringEmpty(s1)); system("pause"); return 0; }
(六)总结
串的存储结构最好使用顺序存储,而且使用数组是较好,较明了的方法。
对于使用链式存储,若是一个结点存储一个字符,会占用大量的空间去存放指针域。
若是使用一个结点存储多个字符,如同上面图像所描绘,虽然占的空间消耗小了,但是相对于数组,依旧需要太多的额外空间,最不好的是,这种方法会时链表的快速插入删除操作的优点消失,甚至复杂了。
所以使用顺序存储的方法是更加好的。

#define _CRT_SECURE_NO_WARNINGS #include <stdio.h> #include <stdlib.h> #include <string.h> #define OK 1 #define ERROR 0 #define TRUE 1 #define FALSE 0 #define MINSIZE 5 typedef int ElemType; typedef int Status; struct StrNode { char data[MINSIZE]; struct StrNode* next; }StrNode; //设置串的存储结构 typedef struct StrNode* String; //生成串相关 Status StrAssign(String S, char *chars); //生成一个其值等于字符串常量的chars的串T Status StrCopy(String* T, String S); //串S存在,由串S复制得到串T Status Concat(String* T, String S1, String S2); //用T返回由S1和S2连接而成的新串 //基础操作相关 Status ClearString(String S); //串S存在,将串清空 Status StringEmpty(String S); //若串为空,返回true,否则false int StringLength(String S); //返回串S的元素个数,长度 void PrintStr(String S); //生成串相关 //生成一个其值等于字符串常量的chars的串T,这里是以\0结束,通过\0来计算长度 Status StrAssign(String* S, char *chars) { int i,length; String rear; String node; if (!S||!chars) return ERROR; length = strlen(chars); length = length / MINSIZE + 1; *S = (String)malloc(sizeof(StrNode)); //(*S)->next = NULL; //这里可以说没用了,因为在下面strcpy拷贝数据时,数据会覆盖掉指针,或者我们使用strncpy保证每次拷贝数据的长度也行 strcpy((*S)->data, chars); //strncpy((*S)->data, chars, 5); rear = *S; i = 1; while (i<length) { node = (String)malloc(sizeof(StrNode)); if (!strlen(chars + i*MINSIZE)) memset(node->data, 0, MINSIZE); else strcpy(node->data, chars + i*MINSIZE); node->next = rear->next; rear->next = node; rear = node; i++; } rear->next = NULL; //这里是必须要的 return OK; } //基础操作相关 //串S存在,将串清空 Status ClearString(String S) { String cur; if (!S) return OK; while (S) { cur = S; S = S->next; free(cur); } return OK; } //若串为空,返回true,否则false Status StringEmpty(String S) { if (!S) return FALSE; return TRUE; } //返回串S的元素个数,长度 int StringLength(String S) { String q,p; int length=0; int i=0; q = S; while (q->next) { length += MINSIZE; q = q->next; } while (i<5&&q->data[i]) i++; length += i; return length; } //串S存在,由串S复制得到串T Status StrCopy(String* T, String S) { int i; String q, p,rear; if (!S) return ERROR; if (StringEmpty(*T)) ClearString(*T); p = S; *T = (String)malloc(sizeof(StrNode)); memcpy(*T, p, sizeof(StrNode)); p = p->next; rear = *T; while (p) { q = (String)malloc(sizeof(StrNode)); memcpy(q, p, sizeof(StrNode)); q->next = rear->next; rear->next = q->next; rear = q; p = p->next; } return OK; } void PrintStr(String S) { int i; String q=S; while (q) { for (i = 0; i < MINSIZE;i++) { if (!q->data[i]) break; printf("%c", q->data[i]); } q = q->next; } printf("\n"); } //用T返回由S1和S2连接而成的新串,若是超出,会截断,但是会进行连接,返回FALSE Status Concat(String* T, String S1, String S2) { String cS1, cS2; cS1 = cS2 = NULL; if (!StrCopy(&cS1, S1)||!StrCopy(&cS2, S2)) { if (StringEmpty(cS1)) ClearString(cS1); if (StringEmpty(cS2)) ClearString(cS2); } //需要考虑一个结点中的多个字符是否填满,没有填满,后面数据全部需要前移,逻辑复杂且时间复杂度增加了 return OK; } int main() { int i, j; String s1; String s2=NULL; String t = NULL; char *str = (char*)malloc(sizeof(char) * 40); memset(str, 0, 40); printf("enter s1:"); scanf("%s", str); StrAssign(&s1, str); printf("1.string StrAssign s1 success\n"); printf("2.string s1 length:%d\n", StringLength(s1)); printf("3.string s2 copy from s1\n"); StrCopy(&s2, s1); PrintStr(s2); printf("4.string Concat test\n"); system("pause"); return 0; }