第四章 串和数组(数据结构python)

一、串

1.1、串的基本概念:

串是由零个或多个字符组成的有限序列。记作str="a0a1…an-1"(n≥0)。

串中所包含的字符个数n称为串长度,当n=0时,称为空串。

一个串中任意连续的字符组成的子序列称为该串的子串。

包含子串的串相应地称为主串。 若两个串的长度相等且对应字符都相等,则称两个串相等。

1.2、串的基本运算

   StrAssign(cstr):由字符串常量cstr创建一个串,即生成其值等于cstr的串。
   StrCopy():串复制,返回由当前串复制产生一个串。
   getsize():求串长,返回当前串中字符个数。
   geti(i):返回序号i的字符。
   seti( i,x):设置序号i的字符为x。
   Concat(t):串连接,返回一个当前串和串t连接后的结果。
   SubStr(i,j):求子串,返回当前串中从第i个字符开始的j个连续字符组成的子串。
   InsStr(i,t):串插入,返回串t插入到当前串的第i个位置后的子串。
   DelStr(i,j):串删除,返回当前串中删去从第i个字符开始的j个字符后的结果。
   RepStr(i,j,t):串替换,返回用串t替换当前串中第i个字符开始的j个字符后的结果。
   DispStr():输出字符串。

1.3、串的存储结构

1、顺序串

用一个data数组和一个整型变量size来表示一个顺序串,size表示data数组中实际字符的个数。为了简单,data数组采用固定容量为MaxSize(可以模仿顺序表改为动态容量方式)。

顺序串类SqString
MaxSize=100             			#假设容量为100
class SqString:         			#顺序串类
  def __init__(self):   			#构造方法
    self.data=[None]*MaxSize	        	#存放串中字符
    self.size=0   			#串中字符个数
  

eg:设计一个算法Strcmp(s,t),以字典顺序比较两个英文字母串s和t的大小,假设两个串均以顺序串存储。

def Strcmp(s,t):				#比较串s和t的算法
  minl=min(s.getsize(),t.getsize())     	#求s和t中最小长度
  for i in range(minl):			#在共同长度内逐个字符比较
    if s[i]>t[i]: return 1
    elif s[i]<t[i]: return -1
  if s.getsize()==t.getsize():         	#s==t
    return 0
  elif s.getsize()>t.getsize():		#s>t
    return 1
  else: return -1			#s<t

2、链串

用带头结点的单链表表示链串

75f3600fce7840669f66a920dd5806f5.png

 

#链串的结点类型LinkNode
class LinkNode:			#链串结点类型
  def __init__(self,d=None):    	#构造方法
    self.data=d                 	#存放一个字符
    self.next=None              	#指向下一个结点的指针
#一个链串用一个头结点head来唯一标识,链串类LinkString
class LinkString:               	#链串类
  def __init__(self):          	#构造方法
    self.head=LinkNode()		#建立头结点
    self.size=0

 eg:串插入:链串在序号i位置插入串t

实现:先创建一个空串s,当参数正确时,采用尾插法建立结果串s:

(1)将当前链串的前i个结点复制到s中。

(2)将t中所有结点复制到s中。

(3)再将当前串的余下结点复制到s中。 

def InsStr(self,i,t):	                	#串插入运算的算法
  s=LinkString()                     	#新建一个空串
  assert i>=0 and i<self.size         	#检测参数
  p,p1=self.head.next, t.head.next
  r=s.head				#r指向新建链表的尾结点
  for k in range(i):			#将当前链串的前i个结点复制到s
    q=LinkNode(p.data)
    r.next=q; r=q			#将q结点插入到尾部
    p=p.next
  while p1!=None:	   		#将t中所有结点复制到s
     q=LinkNode(p1.data)
     r.next=q;				#将q结点插入到尾部
     p1=p1.next
   while p!=None:	   		#将p及其后的结点复制到s
     q=LinkNode(p.data)
     r.next=q; r=q	   		#将q结点插入到尾部
     p=p.next
  s.size=self.size+t.size
  r.next=None	    			#尾结点的next置为空
  return s				#返回新建的链串

1.4、串的模式匹配

设有两个串s和t,串t定位操作就是在串s中查找与子串t相等的子串。 通常把串s称为目标串,把串t称为模式串,因此定位也称作模式匹配。 模式匹配成功是指在目标串s中找到一个模式串t。 不成功则指目标串s中不存在模式串t。

1、BF算法

第1趟:从s0/t0开始比较,若相等,则继续逐个比较后续字符。如果对应的字符全部相同且t的字符比较完,说明t是s的子串,返回t在s中的起始位置,表示匹配成功;如果对应的字符不相同,说明第一趟匹配失败。

第2趟:从s1/t0开始比较,若相等,则继续逐个比较后续字符。如果对应的字符全部相同且t的字符比较完,说明t是s的子串,返回t在s中的起始位置,表示匹配成功;如果对应的字符不相同,说明第一趟匹配失败。 依次类推。只要有一趟匹配成功,则说明t是s的子串,返回t在s中的起始位置。如果i超界都没有匹配成功,说明t不是s的子串,返回-1。

91a19665cc3f4036a5e647c1192dd21f.png

def BF(s,t):					#BF算法
  i,j=0,0
  while i<s.getsize() and  j<t.getsize():		#两串未遍历完时循环
    if s[i]==t[j]:			  	#两个字符相同
      i,j=i+1,j+1				#继续比较下一对字符
    else:
      i,j=i-j+1,0				#i从下个位置,j从头开始匹配
  if j>=t.getsize():
    return (i-t.getsize())	            	#返回匹配的首位置
  else:
    return (-1)					#模式匹配不成功

BF算法性能

该算法在最好情况下的时间复杂度为O(m),即主串的前m个字符正好等于模式串的m个字符。 最坏情况下的时间复杂度为O(n×m)。 平均情况下的时间复杂度为O(n×m)。

2、KMP算法

消除了目标串指针的回溯,从而使算法效率有了某种程度的提高。

def GetNext(t,next):        	   	#由模式串t求出next值
  j,k=0,-1
  next[0]=-1
  while j<t.getsize()-1:
    if k==-1 or t[j]==t[k]:     		#j遍历后缀,k遍历前缀
      j,k=j+1,k+1
      next[j]=k
    else:
      k=next[k]		        		#k置为next[k]
def KMP(s,t):                      	#KMP算法
  next=[None]*MaxSize
  GetNext(t,next)			#求next数组
  i,j=0,0
  while i<s.getsize() and j<t.getsize():
    if j==-1 or s[i]==t[j]:
      i,j=i+1,j+1             		#i,j各增1
    else:
      j=next[j] 		    		#i不变,j回退
  if j>=t.getsize():
    return(i-t.getsize())	    	#返回起始序号
  else:
    return(-1)				#返回-1

KMP算法性能

设目标串s的长度为n,模式串t长度为m。 在KMP算法中求next数组的时间复杂度为O(m)。 在后面的匹配中因主串s的下标i不减即不回溯,比较次数可记为n。 KMP算法总的时间复杂度为O(n+m)。

eg 设目标串s="ababcabcacbab",模式串t="abcac"。给出KMP进行模式匹配的过程。

 

j

0

1

2

3

4

t[j]

a

b

c

a

c

next[j]

-1

0

0

0

1

 59d9a82b86e748b897eca02fb48c5226.png

 改进KMP算法

6ce601e7bfe8490fae83c76f3afe3e60.png

 0160a747d3ac438abb225fe105d5ee52.png

 8ab60205f5774eff9ab5e5758f81c025.png

首先,nextval[0]=-1

j=1:失配处为si/t1,则si≠t1。KMP算法的下一次比较si/tnext[1],而next[1]=0,并且t0=t1,说明一定有si≠tnext[1]  nextval[j]=nextval[next[j]]=-1 

 将next数组改为nextval数组,与next[0]一样,先置nextval[0]=-1。假设求出next[j]=k,现在失配处为si/tj,即si≠tj,    

(1)如果有tj=tk成立,可以直接推出si≠tk成立,没有必要再做si/tk的比较,直接置nextval[j]=nextval[k](nextval[next[j]]),即下一步做si/tnextval[j]的比较。    

(2)如果有tj≠tk,没有改进的,置nextval[j]=next[j]。

def GetNextval(t,nextval):	#由模式串t求出nextval值
  j,k=0,-1
  nextval[0]=-1
  while j<t.getsize()-1:
    if k==-1 or t[j]==t[k]:
      j,k=j+1,k+1
      if t[j]!=t[k]:
        nextval[j]=k
      else:			#t[j]=t[k]
        nextval[j]=nextval[k]
    else: k=nextval[k]
def KMPval(s,t):                       	#改进后的KMP算法
  nextval=[None]*MaxSize
  GetNextval(t,nextval)			#求nextval数组
  i,j=0,0
  while i<s.getsize() and j<t.getsize():
    if j==-1 or s[i]==t[j]:
      i,j=i+1,j+1             		#i,j各增1
    else: j=nextval[j] 		    	#i不变,j回退
  if j>=t.getsize():
     return(i-t.getsize())	    	#返回起始序号
  else:
     return(-1)				#返回-1

二、数组 

2.1、数组的基本概念

数组是一个二元组(idx,value)的集合,对每个idx,都有一个value值与之对应。idx称为下标,可以由一个整数、两个整数或多个整数构成,下标含有d(d≥1)个整数称为维数是d。 数组按维数分为一维、二维和多维数组。 一维数组A是n(n>1)个相同类型元素a0,a1,…,an-1构成的有限序列,其逻辑表示为A=(a0,a1,…,an-1),其中,A是数组名,ai(0≤i≤n-1)是数组A中序号为i的元素。 一个二维数组可以看作是每个数据元素都是相同类型的一维数组的一维数组。 以此类推。

1、 数组特点:

  (1)数组中各元素都具有统一的数据类型。  

  (2)d(d≥1)维数组中的非边界元素具有d个前驱元素和d个后继元素。   

  (3)数组维数确定后,数据元素个数和元素之间的关系不再发生改变,特别适合于顺序存储。

  (4)每个有意义的下标都存在一个与其相对应的数组元素值。

 2、d维数组抽象数据类型

ADT Array
{
数据对象:
    D={ 数组中所有元素 }
数据关系:
    R={r1,r2,…,rd}
    ri={ 元素之间第i维的线性关系 | i=1,…,d}
基本运算:
   Value(A,i1,i2,…,id):A是已存在的d维数组,其运算结果是返回
                              A[i1,i2,…,id]值。
   Assign(A,e,i1,i2,…,id):A是已存在的d维数组,其运算结果是
                              置A[i1,i2,…,id]=e。
    …
}

2.2、一维数组

一维数组的所有元素依逻辑次序存放在一片连续的内存存储单元中。 其起始地址为第一个元素a0的地址即LOC(a0)。 假设每个数据元素占用k个存储单元。 则任一数据元素ai的存储地址LOC(ai)就可由以下公式求出

LOC(ai)=LOC(a0)+i×k    (1≤i<n)

在Python中长度为n的一维数组{a0,a1,…,an-1}通常采用形如[a0,a1,…,an-1]的列表表示。

例如,以下语句创建一个长度为MAXN的一维数组a,初始元素值均为None:

MAXN=10 a=[None]*MAXN

2.3、d维数组 

以m行n列的二维数组Am×n=(ai,j)为例讨论(二维数组也称为矩阵)。

954f01d868404fe3b241ab6f34af9aaa.png

 按行优先存储:

 假设每个元素占k个存储单元,LOC(a0,0)表示a0,0元素的存储地址。对于元素ai,j:

ai,j前面有0~i-1共i行,每行n个元素,共有i×n个元素。 在第i行中前面有a[i,0..j-1],共j个元素。 合起来,ai,j前面有i×n+j个元素。

LOC(ai,j)=LOC(a0,0) + (i×n + j)×k

在Python中m行n列的二维数组{{a0,0,a0,1,…,a0,n-1},…,{am-1,0,am-1,1,…,am-1,n-1}}通常采用形如[[a0,0,a0,1,…,a0,n-1],…,[am-1,0,am-1,1,…,am-1,n-1]]的嵌套列表表示。

 例如,以下语句创建一个MAXM行MAXN列的二维数组a,初始元素值均为None:

MAXM,MAXN=3,4 a=[[None]*MAXN for i in range(MAXM)]

2.4、特殊矩阵的压缩存储

1、对称矩阵的压缩存储

若一个n阶方阵A的元素满足ai,j=aj,i(0≤i,j≤n-1),则称其为n阶对称矩阵。

aede5027509848b5a5483d3f61d74d0b.png

 07fe8aa6e2c34ae99d1d64dffa5e0c8c.png

 9c182db1b2ec46dab43a81dcd614cb45.png

2、三角矩阵的压缩存储

上三角矩阵:

725842af6d9247d78be77aa2fc3882b4.png

 

下三角矩阵:

d628e517a97e4817bf317b0166d7ec07.png

 

3、 对角矩阵的压缩存储

a91fb132223f400facd8027f5e89cda0.png

2.5、稀疏矩阵 

一个阶数较大的矩阵中的非零元素个数s相对于矩阵元素的总个数t十分小时,即s<<t时,称该矩阵为稀疏矩阵。  

例如一个100×100的矩阵,若其中只有100个非零元素,就可称其为稀疏矩阵。

稀疏矩阵和特殊矩阵的不同点:

特殊矩阵的特殊元素(值相同元素、常量元素)分布有规律。

稀疏矩阵的特殊元素(非0元素)分布没有规律。

 稀疏矩阵的三元组表示

4ebf3161bfd84aa786f56a411cddf5a1.png

#三元组表示中每个元素的类定义如下:
class TupElem:			#三元组元素类
  def __init__(self,r1,c1,d1):  	#构造方法
    self.r=r1                    #行号
    self.c=c1			#列号
    self.d=d1			#元素值
#设计稀疏矩阵三元组存储结构类TupClass如下:
class TupClass:			#三元组表示类
  def __init__(self,rs,cs,ns):  	#构造方法
    self.rows=rs			#行数
    self.cols=cs			#列数
    self.nums=ns			#非零元素个数
    self.data=[]			#稀疏矩阵对应的三元组顺序表

 TupClass类中包含如下基本运算方法:

CreateTup(A,m,n):由m行n列的稀疏矩阵A创建其三元组表示。

Setvalue(i,j,x):利用三元组给稀疏矩阵的元素赋值即执行A[i][j]=x。

GetValue(i, j):利用三元组取稀疏矩阵的元素值即执行x=A[i][j]。

DispTup():输出稀疏矩阵的三元组表示。

 

 

 

 

 

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值