数据结构(C++)笔记:04.字符串与多维数组

Posted oldmao_2000

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了数据结构(C++)笔记:04.字符串与多维数组相关的知识,希望对你有一定的参考价值。

文章目录

4.1 串

4.1.1 串的逻辑结构

  1. 串的定义
    串是零个或多个字符组成的有限序列,只包含空格的串称为空格串。串中所包含的字符个数称为串的长度,长度为0的串称为空串,记作 " ",一个非空串通常记作:
    S = " s 1 s 2 … … s n " S="s_1 s_2 …… s_n" S="s1s2sn"
    其中,S是串名,双引号是定界符,不属于串的内容,双引号(有些书中也用单引号)引起来的部分是串值, s i ( 1 ≤ i ≤ n ) s_i(1≤i≤n) si1in是一个任意字符(可以是字母、数字或其他字符), s i s_i si在串中出现的序号称为该字符在串中的位置。串中的字符数目n称为串的长度,定义中谈到“有限”是指长度n是一个有限的数值。零个字符的串称为空串(null string),它的长度为零,可以直接用两双引号“”””表示,也可以用希腊字母“ ϕ \\phi ϕ”来表示。所谓的序列,说明串的相邻字符之间具有前驱和后继的关系。空格串,是只包含空格的串。注意它与空串的区别,空格串是有内容有长度的,而且可以不止一个空格。
    子串与主串,串中任意个数的连续字符组成的子序列称为该串的子串,相应地,包含子串的串称为主串。
    子串在主串中的位置就是子串的第一个字符在主串中的序号。
    例如:“over”、“end”、“lie”其实可以认为是“lover”、“friend”、“believe”这些单词字符串的子串。
  2. 串的抽象数据类型定义
ADT String 
Data
   串中的数据元素仅由一个字符组成,相邻元素具有前驱和后继关系
Operation
       StrLength
     前置条件:串S已存在
     输入:无
     功能:求串S的长度
     输出:串S中的字符个数
     后置条件:串S不变
       StrAssign 
     前置条件:无
     输入:串T
     功能:串赋值,将T的串值赋值给串S
     输出:串S
     后置条件:串T不变
       StrConcat
     前置条件:无
     输入:串S,串T
     功能:串连接,将串T放在串S的后面连接成一个新串S
     输出:串S
     后置条件:串T不变
StrSub
     前置条件:串S已存在
     输入:位置i,长度len
     功能:求子串,返回从串S的第i个字符开始长为 len 的子串
     输出:S的一个子串
     后置条件:串S不变
StrCmp
     前置条件:无
     输入:串S,串T
     功能:串比较
     输出:若S=T,返回0;若S<T, 返回-1;若S>T, 返回1
     后置条件:串S和T不变
       StrIndex
     前置条件:串S已存在
     输入:串T
     功能:子串定位
     输出:子串T在主串S中首次出现的位置
     后置条件:串S和T不变
StrInsert
     前置条件:串S已存在
     输入:串T,位置i
     功能:串插入,将串T插入到串S 的第i个位置上
     输出:串S
     后置条件:串T不变
StrDelete
     前置条件:串S已存在
     输入:位置i,长度len
     功能:串删除,删除串S中从第i个字符开始连续len个字符
     输出:串S
     后置条件:串S的长度减少了len
StrRep
     前置条件:串S已存在
     输入:串T,串R
     功能:串替换,在串S中用串R 替换所有与串T相等的子串
     输出:串S
     后置条件:串T和R不变
endADT

串操作的特点:
串的逻辑结构和线性表很相似,不同之处在于串针对的是字符集,也就是串中的元素都是字符,哪怕串中的字符是“123”这样的数字组成,或者“2010-10-10“这样的日期组成,它们都只能理解为长度为3和长度为10的字符串,每个元素都是字符而已。
因此,对于串的基本操作与线性表是有很大差别的。线性表更关注的是单个元素的操作,比如查找一个元素,插入或删除一个元素,但串中更多的是查找子串位置、得到指定位置子串、替换子串等操作。
3. 串的比较
两个数字,很容易比较大小。2比1大,这完全正确,可是两个字符串如何比较?比如“silly”、“stupid”这样的同样表达“愚蠢的”的单词字符串,它们在计算机中的大小其实取决于它们挨个字母的前后顺序。它们的第一个字母都是“s”,我们认为不存在大小差异,而第二个字母,由于“i”字母比“t”字母要靠前,所以“i”<“t”,于是我们说“silly”<“stupid”。
事实上,串的比较是通过组成串的字符之间的编码来进行的,而字符的编码指的是字符在对应字符集中的序号。
计算机中的常用字符是使用标准的ASCII编码,更准确一点,由7位二进制数表示一个字符,总共可以表示128个字符。后来发现一些特殊符号的出现,128个不够用,于是扩展ASCII码由8位二进制数表示一个字符,总共可以表示256个字符,这已经足够满足以英语为主的语言和特殊符号进行输入、存储、输出等操作的字符需要了。可是,单我们国家就有除汉族外的满、回、藏、蒙古、维吾尔等多个少数民族文字,换作全世界估计要有成百上千种语言与文字,显然这256个字符是不够的,因此后来就有了Unicode编码,比较常用的是由16位的二进制数表示一个字符,这样总共就可以表示216个字符,约是65万多个字符,足够表示世界上所有语言的所有字符了。当然,为了和ASCII码兼容,Unicode的前256个字符与ASCII码完全相同。
所以如果我们要在C语言中比较两个串是否相等,必须是它们串的长度以及它们各个对应位置的字符都相等时,才算是相等。即给定两个串: s = “ a 1 a 2 … … a n ” s=“a_1a_2……a_n” s=a1a2an t = “ b 1 b 2 … … b m ” t=“b_1b_2……b_m” t=b1b2bm,当且仅当n=m,且 a 1 = b 1 , a 2 = b 2 , … , a n = b n a_1=b_1,a_2=b_2,…,a_n=b_n a1=b1a2=b2an=bn时,我们认为s=t。
给定两个串: s = “ a 1 a 2 … … a n ” s=“a_1a_2……a_n” s=a1a2an t = “ b 1 b 2 … … b m ” t=“b_1b_2……b_m” t=b1b2bm,当满足以下条件之一时,s<t。
1.n<m,且 a i = b i ; ( i = 1 , 2 , … … , n ) a_i=b_i;(i=1,2,……,n) ai=bii=12n
例如当s=“hap",t=“happy”,就有s<t。因为t比s多出了两个字母。
2.存在某个 k ≤ m i n ( m , n ) k≤min(m,n) kminmn,使得 a i = b i ; ( i = 1 , 2 , … … , k − 1 ) , a k < b k a_i=b_i;(i=1,2,……,k-1),a_k<b_k ai=bii=12k1ak<bk。例如当s=“happen",t=“happy”,因为两串的前4个字母均相同,而两串第5个字母(k值),字母e的ASCII码是101,而字母y的ASCII码是121,显然e<y,所以s<t。
有同学如果对这样的数学定义很不爽的话,那我再说一个字符串比较的应用。我们的英语词典,通常都是上万个单词的有序排列。就大小而言,前面的单词比后面的要小。你在查找单词的过程,其实就是在比较字符串大小的过程。

4.1.2 串的存储结构

1.串的顺序存储结构
串的顺序存储结构是用数组来存储串中的字符序列。
在串的顺序存储中,一般有三种方法表示串的长度:
⑴ 用一个变量来表示串的长度,如图3-13所示。

⑵ 在串尾存储一个不会在串中出现的特殊字符作为串的终结符,如图3-14所示。

⑶ 用数组的0号单元存放串的长度,串值从1号单元开始存放,如图3-15所示。

2.串的链接存储结构
串的链接存储结构有以下两种解决方案:
⑴ 非压缩形式。一个结点只存储一个字符,其优点是操作方便,但存储利用率低。
⑵ 压缩形式。为了提高存储空间利用率,一个结点可存储多个字符。这实质上是一种顺序与链接相结合的结构。这种存储结构增加了实现基本操作的复杂性,比如对改变串长的操作,可能涉及结点的增加与删除问题。
最后一个结点若是未被占满时,可以用“#”或其他非串值字符补全。

模式匹配

给定两个串 S = " s 1 s 2 … s n " 和 T = " t 1 t 2 … t m " S="s_1s_2…s_n" 和T="t_1t_2…t_m" S="s1s2sn"T="t1t2tm",在主串S中寻找子串T的过程称为模式匹配,T称为模式。如果匹配成功,返回T在S中的位置,如果匹配失败,返回0。
假设串采用顺序存储结构,串长存放在数组的0号单元,串值从1号单元开始存放。

1.朴素的模式匹配算法

BF(Brute Force)暴力匹配算法的基本思想是:从主串S的第一个字符开始和模式T的第一个字符进行比较,若相等,则继续比较两者的后续字符;否则,从主串S的第二个字符开始和模式T的第一个字符进行比较,重复上述过程,若T中的字符全部比较完毕,则说明本趟匹配成功;否则匹配失败。

设串S长度为n,串T长度为m,在匹配成功的情况下,考虑两种极端情况:
⑴ 在最好情况下,每趟不成功的匹配都发生在串T的第一个字符。
即最好情况下的时间复杂度是O(n+m)。
分析一下,最好的情况是什么?那就是一开始就区配成功,比如“googlegood”中去找“google”,时间复杂度为O(1)。稍差一些,如果像刚才例子中第二、三、四位一样,每次都是首字母就不匹配,那么对T串的循环就不必进行了,比如“abcdefgooge”中去找“google”。那么时间复杂度为O(n+m),其中n为主串长度,m为要匹配的子串长度。根据等概率原则,平均是(n+m)/2次查找,时间复杂度为O(n+m)。
⑵ 在最坏情况下,每趟不成功的匹配都发生在串T的最后一个字符。
一般情况下,m<< n,因此最坏情况下的时间复杂度是O(n×m)。
那么最坏的情况又是什么?就是每次不成功的匹配都发生在串T的最后一个字符。举一个很极端的例子。主串为S=“00000000000000000000000000000000000000000000000001”,而要匹配的子串为T=“0000000001”,前者是有49个“0”和1个“1”的主串,后者是9个“0”和1个“1”的子串。在匹配时,每次都得将T中字符循环到最后一位才发现:哦,原来它们是不匹配的。这样等于T串需要在S串的前40个位置都需要判断10次,并得出不匹配的结论,如图5-6-6所示。直到最后第41个位置,因为全部匹配相等,所以不需要再继续进行下去,如图5-6-7所示。如果最终没有可匹配的子串,比如是T=“0000000002”,到了第41位置判断不匹配后同样不需要继续比对下去。因此最坏情况的时间复杂度为 O ( ( n − m + 1 ) ∗ m ) O((n-m+1)*m) O((nm+1)m)

2.改进的模式匹配算法

这里感觉C++版本和大话数据结构版本的描述有点不一样。。。。先记下来
用next[j]表示 t j t_j tj对应的k值(1≤j≤m),其定义如下:
− 1    j = 1 ( 大 话 数 据 结 构 这 里 用 的 是 0 ) m a x k ∣ 1 ≤ k < j 且 " t 1 t 2 … t k − 1 " = " t j − k + 1 t j − k + 2 … t j − 1 " 0    其 他 情 况 ( 大 话 数 据 结 构 这 里 用 的 是 1 ) \\left\\\\beginmatrix -1 \\space\\space j=1(大话数据结构这里用的是0)\\\\ max \\k | 1≤k<j 且"t_1t_2 … t_k -1 " ="t_j-k+1t_j-k+2 … t_j-1"\\ \\\\ 0\\space\\space 其他情况(大话数据结构这里用的是1) \\endmatrix\\right. 1  j=10maxk1kj"t1t2以上是关于数据结构(C++)笔记:04.字符串与多维数组的主要内容,如果未能解决你的问题,请参考以下文章

C/C++编程笔记:数组和字符串丨多维数组详解

C++C++自学旅程:数组与字符数组

数据结构(C++)——第四章 字符串和多维数组

C++ 使用 std::sort 处理多维字符串数组

C++多维数组逗号索引地址

在 C++ 中将 memset 与多维数组一起使用