Head First 串
来源:互联网 发布:apache hadoop yarn 编辑:程序博客网 时间:2024/06/06 05:48
昨天刚学了串这一章,今天趁热在自己的学习记录里记下来好了,数据结构第三篇:串的操作与应用。和以前一样,参考严蔚敏版《数据结构》。同时,将串的匹配(重点是KMP匹配算法)单独拿出来作为一篇博文,毕竟这个比较复杂。其实主要是为了充数。
目录:
一、串的概述
二、定长顺序串的基本操作
三、堆分配串的基本操作
一、串的概述与基本操作
串说白了就是我们在很多高级语言中用到的字符串类型,这并不是基本数据类型,但这些高级语言已经将对串的操作封装的很全、很好了。所以我们用起来也比较方便。其实完全可以自己去试着简单实现一下那些方法。这是一个非常好的练习。
对串来说,我们一般就三种存储方式:定长顺序存储、堆分配存储、块链存储。
定长顺序存储:串的顺序存储方式,利用一块连续的内存空间来存储字符序列,这段内存空间的长度固定,可用宏来表示,整个串采用顺序数组存储,同时规定数组的第一个元素(0号下标)为串的长度,便于以后的操作。
常量与数据结构定义:
#define MAX_STRING_LENGTH 255 //串的最大串长typedef unsigned char SString[MAX_STRING_LENGTH + 1]; //0号单元存放串的长度
仅仅使用一个数组就可以代表一个串,如前文所述,将数组第一个单元(0号位置)设为串的长度,不用再定义新的变量。
堆分配存储:串的动态存储方法。设置一个字符指针代表串的内容,增设一个变量存储串的长度方便以后的操作。堆分配存储最大的特点就是灵活、动态。这里不会出现字符串的“截断”现象,当空间不够时重做分配,记录新空间基址即可。
/** * 串的堆分配存储表示 */typedef struct { char *ch; //若是非空串,则按串长分配存储区,否则为NULL int length; //串长}HString;对堆分配存储的串进行操作时,由于串的存储空间是在堆上的,需要我们自己管理,例如在串复制时需要首先释放原串的空间,再重新申请一块空间进行操作。
块链存储:串的链式存储。由于串结构的数据域仅为一个字符(1字节),如果为每个结点加一个指针域(4字节),则显得有些浪费空间。因此在采用链式存储时往往采用”块链“——每个结点存放多个字符。这样当串长不是结点大小的整数倍时,最后一个结点由特殊字符(例如‘#’)补满。由于连式存储较前两者而言并无太大优势,加之操作复杂,所以不做要求。
顺带一提,对于每个结点存放字符的数目,有一个概念叫做“存储密度”。存储密度 = 串值所占的存储位 / 实际分配的存储位。该值越小块链处理起来就越方便。
块链的数据结构定义:
#define CHUNK_SIZE 80typedef struct Chunk{ char ch[CHUNK_SIZE]; //数据域 struct Chunk *next; //指针域}Chunk;typedef struct { Chunk *head; //串的头指针 Chunk *tail; //串的尾指针 int curlen; //串的当前长度}LString;
二、定长顺序串的基本操作
串的基本操作我们都比较熟悉,例如StrCopy和StrCompare等。在这里提一下,关于串面试时的考点还是挺多的。例如:while (*s++ = *t++); 的作用?(赋值字符串),自己编写strcmp函数?等等。掌握这些基本操作对于应试非常有帮助。
先看一下定长顺序串的所有基本操作:
/** * 根据字符串常量生成串T * * @param T 生成的串 * @param constChars 字符串常量 */void StrAssign(SString *T, char constChars[]);/** * 由串S复制得串T */void StrCopy(SString *T, SString S);/** * 若S为空串,返回TRUE,否则返回FALSE */BOOL StrEmpty(SString S);/** * 返回串S的长度 */int StrLen(SString S);/** * 将S清为空串 */void ClearString(SString *S);/** * 用T返回将S1和S2连接成的新串 */void Concat(SString *T, SString S1, SString S2);/** * 用Sub返回串S中第pos个字符起长度为len的字串 */void SubString(SString *Sub, SString S, int pos, int len);/** * 返回在主串S中第pos个字符之后第一次出现字串T的位置,若不存在字串T,则返回0。(朴素匹配算法) */int Index(SString S, SString T, int pos);/** * 用V替换主串S中得所有与T相等的不重叠的字串 */void Replace(SString *S, SString T, SString V);/** * 在串S的第pos个位置之前插入串T */void StrInsert(SString *S, int pos, SString T);/** * 从串S中删除第pos个字符起长度为len的字串 */void StrDelete(SString *S, int pos, int len);/** * 销毁串S */void DestroyString(SString *S);/** * 比较亮字符串 * * @param S 第一个字符串 * @param T 第二个字符串 * * @return 结果为1说明S>T,结果为-1说明S<T,结果为0说明S=T */int StrCompare(SString S, SString T);/** * 打印串S * * @param S 要打印的串 */void PrintStr(SString S);
其中子串的定位函数Index即为串的模式匹配算法之一,关于串的模式匹配,单独总结。这里将其他的操作给出实现
void StrAssign(SString *T, char constChars[]){ int i = 1, j = 0; while (constChars[j] != '\0') { (*T)[i++] = constChars[j++]; } (*T)[0] = j;}void StrCopy(SString *T, SString S){ int i = 1, j = 1; while (S[j] != '\0') { (*T)[i++] = S[j++]; } (*T)[0] = S[0];}BOOL StrEmpty(SString S){ return (S[0] > 0) ? FALSE : TRUE;}int StrLen(SString S){ return S[0];}void ClearString(SString *S){ (*S)[1] = '\0'; (*S)[0] = 0;}void Concat(SString *T, SString S1, SString S2){ int s1_len = S1[0]; int s2_len = S2[0]; int k = 1; if (s1_len + s2_len <= MAX_STRING_LENGTH) { //未被截断 for (int i = 1; i <= s1_len; i++) { (*T)[k++] = S1[i]; } for (int j = 1; j <= s2_len; j++) { (*T)[k++] = S2[j]; } (*T)[0] = s1_len + s2_len; } else if (s1_len < MAX_STRING_LENGTH) { //S2被截断 for (int i = 0; i < s1_len; i++) { (*T)[k++] = S1[i]; } for (int j = 0; j < MAX_STRING_LENGTH - s1_len; j++) { (*T)[k++] = S2[j]; } (*T)[0] = MAX_STRING_LENGTH; } else { //仅取S1 for (int i = 0; i < MAX_STRING_LENGTH; i++) { (*T)[k++] = S1[i]; } (*T)[0] = MAX_STRING_LENGTH; }}void SubString(SString *Sub, SString S, int pos, int len){ if (pos < 1 || pos > S[0] || len < 0 || len > S[0] - pos + 1) { (*Sub)[0] = '\0'; return; } int k = 1; for (int i = pos; i < pos + len; i++) { (*Sub)[k++] = S[i]; } (*Sub)[0] = len;}void Replace(SString *S, SString T, SString V){ if (T[0] != V[0]) { return; } int pos = 0; while ((pos = Index(*S, T, 1)) > 0) { int k = 1; for (int i = pos; i < pos + T[0]; i++) { (*S)[i] = V[k++]; } }}void StrInsert(SString *S, int pos, SString T){ if (pos < 1 || pos > (*S)[0] || (*S)[0] + T[0] > MAX_STRING_LENGTH) { return; } int t_len = T[0]; for (int i = (*S)[0]; i >= pos; i--) { (*S)[i + t_len] = (*S)[i]; } int k = 1; for (int i = pos; i < pos + t_len; i++) { (*S)[i] = T[k++]; } (*S)[0] += T[0];}void StrDelete(SString *S, int pos, int len){ if (pos < 1 || pos + len > (*S)[0]) { return; } for (int i = pos; i < len + pos; i++) { (*S)[i] = (*S)[i + len]; } (*S)[0] -= len;}void DestroyString(SString *S){ free(S);}int StrCompare(SString S, SString T){ for (int i = 1; i <= S[0] && i <= T[0]; i++) { if (S[i] == T[i]) { continue; } else if (S[i] < T[i]) { return 1; } else { return -1; } } if (S[0] == T[0]) { return 0; } else if (S[0] < T[0]) { return 1; } else { return -1; }}void PrintStr(SString S){ printf("SString = "); for (int i = 1; i <= S[0]; i++) { printf("%c", S[i]); } printf(" length = %d", S[0]); printf("\n");}
由于定长顺序串的数组0元素位置为串长,所以在循环处理时应该注意下标值的范围:1 ≤ i ≤ S[0]。
三、堆分配串的基本操作
对比定长顺序串,可以看到堆分配串的灵活性以及方便操作性。
堆分配串的基本操作:
/** * 在串S的第pos个字符之前插入串T * * @return 若正确插入,则返回1,否则返回0 */Status HStrInsert(HString *S, int pos, HString T);/** * 删除串S的第pos个位置的字符开始len长度的字串 * * @param S 待删除的主串 * @param pos 待删除字串在主串的位置 * @param len 待删除字串的长度 * * @return 若成功删除,则返回1,否则返回0 */Status HStrDelete(HString *S, int pos, int len);/** * 生成一个其值等于串常量chars的串T * * @return 若成功生成串T,则返回1,否则返回0 */Status HStrAssign(HString *T, char *chars);/** * 返回S的元素个数,即S串的长度 * * @return 串S的长度 */int HStrLength(HString S);/** * 按字典顺序比较两个串 * * @param S 第一个串 * @param T 第二个串 * * @return 若S>T,则返回值大于零,若S=T,则返回值等于零,否则返回值小于零。 */int HStrCompare(HString S, HString T);/** * 清空串S */Status ClearHString(HString *S);/** * 用T返回由S1和S2联接而成的新串 * * @param T 联接成的新串 * @param S1 待联接的第一个串 * @param S2 待联接的第二个串 * * @return 联接成功时返回1,否则返回0 */Status ConcatHString(HString *T, HString S1, HString S2);/** * 返回串S的第pos个字符起长度为len的子串 * * @param Sub 生成的字串 * @param S 传入的串 * @param pos 子串在主串中的起始位置 * @param len 字串的长度 * * @return 若操作成功则返回1,否则返回0 */Status SubHString(HString *Sub, HString S, int pos, int len);/** * 打印串S */void PrintHString(HString S);
以及实现:
Status HStrInsert(HString *S, int pos, HString T){ if (pos < 1 || pos > (*S).length + 1) { return ERROR; } //如果T非空,则重新为S分配空间 if (T.length) { if (!((*S).ch = (char *)realloc((*S).ch, ((*S).length + T.length) * sizeof(char)))) { exit(OVERFLOW); } //插入位置后面的所有元素后移 for (int i = (*S).length - 1; i >= pos - 1; i--) { (*S).ch[i + T.length] = (*S).ch[i]; } //插入T for (int i = 0; i < T.length; i++) { (*S).ch[i + pos - 1] = T.ch[i]; } (*S).length += T.length; } return OK;}Status HStrDelete(HString *S, int pos, int len){ if (pos < 1 || pos > (*S).length || len < 0 || len > (*S).length - pos + 1) { return ERROR; } if (len) { //元素前移 for (int i = pos - 1; i < (*S).length - len + 1; i++) { (*S).ch[i] = (*S).ch[i + len]; } //长度减少 (*S).length -= len; } return OK;}Status HStrAssign(HString *T, char *chars){ //如果T本来不为空,则释放内存空间 if ((*T).ch) { free((*T).ch); } //求chars的长度 int count = 0; for (char *c = chars; *c; c++, count++); if (count == 0) { (*T).ch = NULL; (*T).length = 0; } else { if (!((*T).ch = (char *)malloc(sizeof(char) * count))) { exit(OVERFLOW); } for (int i = 0; i < count; i++) { (*T).ch[i] = chars[i]; } (*T).length = count; } return OK;}int HStrLength(HString S){ return S.length;}int HStrCompare(HString S, HString T){ for (int i = 0; i < S.length && i < T.length; i++) { if (S.ch[i] != T.ch[i]) { return S.ch[i] - T.ch[i]; } } return S.length - T.length;}Status ClearHString(HString *S){ if ((*S).ch) { free((*S).ch); (*S).ch = NULL; } (*S).length = 0; return OK;}Status ConcatHString(HString *T, HString S1, HString S2){ //释放旧空间 if ((*T).ch) { free((*T).ch); } if (!((*T).ch = (char *)malloc((S1.length + S2.length) * sizeof(char)))) { exit(OVERFLOW); } for (int i = 0; i < S1.length; i++) { (*T).ch[i] = S1.ch[i]; } for (int i = 0; i < S2.length; i++) { (*T).ch[i + S1.length] = S2.ch[i]; } (*T).length = S1.length + S2.length; return OK;}Status SubHString(HString *Sub, HString S, int pos, int len){ if (pos < 1 || pos > S.length || len < 0 || len > S.length - pos + 1) { return ERROR; } if ((*Sub).ch) { free((*Sub).ch); } if (len == 0) { (*Sub).ch = NULL; (*Sub).length = 0; } else { (*Sub).ch = (char *)malloc(len * sizeof(char)); for (int i = 0; i < len; i++) { (*Sub).ch[i] = S.ch[i + pos - 1]; } (*Sub).length = len; } return OK;}void PrintHString(HString S){ printf("HString = "); for (int i = 0; i < S.length; i++) { printf("%c", S.ch[i]); } printf("\tlength = %d\n", S.length);}
对于堆分配串的注意事项:不要忘记释放原来的空间,对于malloc的空间,如果不手动释放就会造成内存泄露。另外,与定长顺序串不同,堆分配串是从数组下标为0开始就存储数据的,所以在操作循环的下标时应该注意一下。
之前也说过,串的块链表示并不常用,所以在此就不作实现了。
- Head First 串
- Head First
- Head First
- ?head first ???? ????? ????
- Head First Design Patterns
- Head First Design Patterns
- head first ejb(finally)
- Head First Design Patterns
- Head First Java
- Head First Software Development
- Head First JavaScript [ILLUSTRATED]
- Head First Web Design
- Head First PHP & MySQL
- head first 第一章学习
- head first读书笔记2
- Head first系列图书
- Head First系列下载
- Head First 设计模式
- 深入Struts2的过滤器FilterDispatcher--中文乱码及字符编码过滤器
- 获得App Store推荐的建议和技巧
- Android如何实现模态Dialog
- 使用Datepicker使用时日期回显格式不对解决方法
- 对NSNotificationCenter的新理解
- Head First 串
- Synchronized()与wait()用法
- 键盘与上移问题
- java.lang.ClassCastException: java.math.BigDecimal cannot be cast to java.lang.Long
- xib中添加scrollview
- android 数据网络存储
- 牛顿的广义二项式定理---微积分推倒的开始
- SmartController智能控制系统
- 无线网卡监听模式