计算机上的非数值处理对象基本上是字符串数据。在较早的程序设计中,字符串是作为输入和输出的常量出现。
字符串一般简称为串。在汇编和语言的编译程序中,源程序和目标程序都是字符串数据。
串的定义和实现
定义
串(string)是零个或多个字符组成的有限序列,记为
str='abcd...'
。串中字符数目是串的长度,0 个字符称为空串,其长度为 0。
子串
串中任意连续的字符组成称为该串的子串
主串
包含子串的串相应的称为主串
空格串
由一个或多个空格组成的串‘’称为空格串。
定长的顺序串实现
数据结构
#include <stdio.h> #include <string.h> #include "Status.h" //顺序串相关操作 #define maxstrlen 255 typedef unsigned char sstring[maxstrlen+1];
串我们直接用字符数组去表示就行。
注意我们定义的串 sstring[0]存放的是串的长度
串的赋值
Status strassignsq(sstring T,const char *chars){ int i, len; len = strlen(chars); if(len>maxstrlen) return ERROR; else { T[0] = len; for(i=1; i<=len; i++) T[i] = chars[i-1]; return OK; } };//生成一个其值等于常量chars的串T(串赋值)
对目标串赋值,串入一个常量串,先判断常量串长度是否超过串结构的最大规模,再进行循环赋值。
串的复制,判空,长度,清空
void strcopysq(sstring T,sstring S){ int i; for(i=0; i<=S[0]; i++) T[i] = S[i]; }; Status stremptysq(sstring S){ if(S[0]==0) return TRUE; else return FALSE; }; int strlensq(sstring S){ return S[0]; }; void clearstrsq(sstring S){ S[0] = 0; };
略
串比较
int strcomparesq(sstring S,sstring T){ int i = 1; while(i<=S[0] && i<=T[0]) { if(S[i]==T[i]) i++; else return S[i] - T[i]; } return S[0] - T[0]; };若S>T,返回值>0;若S<T,返回值<0;否则,返回值=0
对两个串进行判断是否一样,若为 0,则串等。
串连接
Status concatsq(sstring T,sstring S1,sstring S2){ int i; for(i=1; i<=S1[0]; i++) T[i] = S1[i]; if(S1[0]+S2[0]<=maxstrlen) { for(i=1; i<=S2[0]; i++) T[S1[0]+i] = S2[i]; T[0] = S1[0]+S2[0]; return OK; } else { for(i=1; S1[0]+i<=maxstrlen; i++) T[S1[0]+i] = S2[i]; T[0]=maxstrlen; return ERROR; } };//用T返回由S1和S2联接而成的新串
传入串 T,s1,s2;这里用 T 来输出连接后的串。这里要处来连接后的长度超出了规模,因为是定长串,所以无法变长。
若连接后不超出规模,则在 s1 后面接上 s2 就行;若超出,则循环的上界需要变为 maxstrken。
子串
Status substrsq(sstring sub,sstring S,int pos,int len){ int i; if(pos<1 || pos>S[0] || len<0 || pos+len-1>S[0]) return ERROR; for(i=1; i<=len; i++) sub[i] = S[pos+i-1]; sub[0] = len; return OK; }//用Sub返回串S的第pos个字符起长度为len的子串
传入值是串 sub(用于返回子串),主串 S,子串长度 len 和位次 pos。
首先对传入值进行判断是否合理。之后对子串进行截取。
串的模式匹配
int indexsq1(sstring S,sstring T,int pos){ int s, t; sstring sub; if(pos>0) { s = strlensq(S); t = strlensq(T); if(s && t) { while(pos+t-1<=s) { substrsq(sub, S, pos, t); if(!strcomparesq(sub, T)) return pos; pos++; } } } return 0; };//返回T在S中第pos个字符后第一次出现的位置,不存在则返回0。 int indexsq2(sstring S,sstring T,int pos){ int i = pos; int j = 1; if(pos<1) return 0; while(i<=S[0] && j<=T[0]) { if(S[i]==T[j]) { i++; j++; } else { i = i - (j-1) +1; j = 1; } } if(j>T[0] && T[0]) return i-T[0]; else return 0; };//串的模式匹配算法,不依赖其他操作的匹配算法
第一个比较简单,通过已有的功能函数,根据 T 串的长度在 S 生成对应位次的子串然后对比即可。
我们主要看第二个。传入的是串 S,T 和位次。
判断 T 是否是 S 的子串,不使用其他操作的情况下,只能通过对比串元素来得到答案。
对位次进行合理性判断,然后进入循环(判断是否存在子串关系);
以位次 1 为例(从第一位进行判断),若相等,则更新位次为下一位;再次判断。
若不等,则需要重来(T 串与 S 串若第一个字母相等而第二个不等则需要重头开始判断,所以 j 需要重写为 1)。
那么若 T,S 前 2 个字母相等,最后一个字母不懂,则又需要重新判断,我们称 i 是无效的更新。需要回到原来的后一位在次判断。猫猫愚钝,是穷举才明白一点这个关系。这也是编码的魅力,用最简洁的话就能完成复杂的操作,构筑有力的工具!
这里可能有点难度,需要揣摩一下。
删除,插入与输出
Status strdelsq(sstring S,int pos,int len){ int i; if(pos<1 || pos+len-1>S[0] || len<0) return ERROR; for(i=pos+len; i<=S[0]; i++) S[i-len] = S[i]; S[0] -= len; return OK; };//从串S中删除第pos个字符起长度为len的子串。 Status strinsertsq(sstring S,int pos,sstring T){ int i; if(pos<1 || pos>S[0]+1 || S[0]+T[0]>maxstrlen) return ERROR; for(i=S[0]; i>=pos; i--) S[i+T[0]] = S[i]; for(i=pos; i<=pos+T[0]-1; i++) S[i] = T[i-pos+1]; S[0] += T[0]; return OK; };//在串S的第pos个字符之前插入串T。可以完全插入返回OK,否则返回ERROR void strprintsq(sstring S){ int i; for(i=1; i<=S[0]; i++) printf("%c", S[i]); };
略
替换
Status replacesq(sstring S,sstring T,sstring V){ int i; i = indexsq2(S, T, 1); //寻找第一个匹配的位置 while(S[0]-T[0]+V[0]<=maxstrlen && i) //有匹配的字符串且可以被完全替换 { strdelsq(S, i, T[0]); //删除T strinsertsq(S, i, V); //插入V i += V[0]; //i切换到下一个位置 i = indexsq2(S, T, i); //定位下一个匹配的字符串 } if(i==0) //S中的T已全部被替换 return OK; else //S中尚有T,但是V已经插不进去了 return ERROR; };//用V替换主串S中出现的所有与T相等的不重叠的子串,可以被完全替换才返回OK
对之间功能函数的组装。
测试
#include <stdio.h> #include "sequncestring.h" int main() { char *chars = "abcdef g"; char *t = "** *"; char *v = "^^^ ^"; sstring S, Tmp, T, V, Sub; int i,j; printf("strassignsq 测试...\n"); { printf("为顺序串 Tmp 赋值...\n"); strassignsq(Tmp, chars); printf("\n"); } PressEnter; printf("stremptysq 测试...\n"); { stremptysq(Tmp) ? printf(" Tmp 为空!!\n") : printf(" Tmp 不为空!\n"); printf("\n"); } PressEnter; printf("strlensq 测试...\n"); { i = strlensq(Tmp); printf(" Tmp 的长度为 %d \n", i); printf("\n"); } PressEnter; printf("strprintsq 测试...\n"); { printf(" Tmp 中的元素为:Tmp = "); strprintsq(Tmp); printf("\n\n"); } PressEnter; printf("strcopysq 测试...\n"); { printf("复制 Tmp 到 S ...\n"); strcopysq(S, Tmp); printf(" S 中的元素为:S = "); strprintsq(S); printf("\n\n"); } PressEnter; printf("strcomparesq 测试...\n"); { printf("比较字符串 Tmp 、 S ...\n"); i = strcomparesq(Tmp, S); i==0 ? printf("Tmp==S!!\n") : (i<0 ? printf("Tmp<S!!\n") : printf("Tmp>S!!\n")); printf("\n"); } PressEnter; printf("strinsertsq 测试...\n"); { printf("将 \"***\" 赋给T...\n"); strassignsq(T, t); printf("在 S 的第 5 个字符前插入T...\n"); strinsertsq(S, 5, T); printf(" S 中的元素为:S = "); strprintsq(S); printf("\n\n"); } PressEnter; printf("indexsq1 测试...\n"); { printf("获取字符串 \"***\" 在 S 中从第1个字符算起的第一次出现的位置...\n"); i = indexsq1(S, T, 1); printf(" \"***\" 在 S 中第1个字符后第一次出现的位置为%d\n", i); printf("\n"); } PressEnter; printf("substrsq 测试...\n"); { printf("用 Sub 返回 S 中第 5 个字符起的 3 个字符...\n"); substrsq(Sub, S, 5, 3); printf(" Sub 中的元素为:Sub = "); strprintsq(Sub); printf("\n\n"); } PressEnter; printf("replacesq、indexsq2 测试...\n"); { printf("将 \"^^^^\" 赋给V...\n"); strassignsq(V, v); printf("用 \"^^^^\" 替换S中的 \"***\" ...\n"); replacesq(S, T, V); printf(" S 中的元素为:S = "); strprintsq(S); j = indexsq2(S, V, 1); printf(" V" 在 S 中第1个字符后第一次出现的位置为%d\n", j); printf("\n\n"); } PressEnter; printf("strdeletesq 测试...\n"); { printf("删除 S 中第 5 个字符起的 4 个字符...\n"); strdelsq(S, 5, 4); printf(" S 中的元素为:S = "); strprintsq(S); printf("\n\n"); } PressEnter; printf("clearstringsq 测试...\n"); { printf("清空 S 前:"); stremptysq(S) ? printf(" S 为空!!\n") : printf(" S 不为空!\n"); clearstrsq(S); printf("清空 S 后:"); stremptysq(S) ? printf(" S 为空!!\n") : printf(" S 不为空!\n"); printf("\n"); } PressEnter; printf("concatsq 测试...\n"); { printf("连接 T 和 V 形成 Tmp ...\n"); concatsq(Tmp, T, V); printf(" Tmp 中的元素为:Tmp = "); strprintsq(Tmp); printf("\n\n"); } PressEnter; return 0; }
串的其他储存
-
定长顺序存储表示(就是定长串如上)
-
堆分配储存表示
typedef struct{ char *ch; int len; }hstring;
堆存储任然是一组地址连续的存储单元,但他们存储空间是在执行程序中动态分配的。(鸽)
-
块链存储表示
#define chunksize 80 typedef struct chunk{ char ch[chunksize]; struct chunk* next; }chunk; typedef struct{ chunk *head,*tail; int curlen; }lstring;
类似于线性表的链式存储,也看采用链表方式存储串值。(鸽)
串的模式匹配
- 求子串位置的定位函数(顺序串已实现)
- kmp 算法(鸽)以及进一步优化
串的应用
串可以用于文本编辑以及建立词索引,有机会试试实现 🐈
思考
这篇没有思考,串的应用很多,其模式匹配在很多地方都有应用,而且这是我们利用计算机算力的有效手段,字符不仅仅是表达字面意思,更可以压缩图片,声音等信息,通过转码再呈现出来。
这是时代进步的产物,这也是未来的基石。
顺便把上节的问题回答了
- 很明显,栈与队限制条件不一样,一个先进后出,一个先进先出。
- 根据卡兰特数,(1/4)*6*5*4/(3*2*1)=5,所以有 5 中不同的排列。
ps: 摸一摸数据结构(目录)
欢迎来到这里!
我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。
注册 关于