串的基本概念
字符串也叫串,是由字符组成的有限序列,是一种常用的非数值数据;
两个串相等是指串长度相同并且各对应位置上的字符也相同。两个串的大小由对应位置上的首个不同字符的大小决定,字符比较次序是从头开始依次向后。当两个串的长度不等而对应位置上的字符都相同时较长的串定义为较大;
通常采用顺序存储结构存储。
串的抽象数据类型描述
public interface IString {
public void clear();//将字符串置成空串
public boolean isEmpty();//判断是否为空串
public int length();//返回串的长度
public char charAt(int i) throws Exception;//读取并返回串中第i个数据元素
public void allocate(int newCapacity);//扩充字符串存储空间容量为newCapacity
public IString subString(int begin,int end);//返回位序号从begin到end-1的子串
public void insert(int i,IString str) throws Exception;//在第i个字符之前插入子串str
public void delete(int begin,int end);//删除位序号从begin到end-1的子串
public void concat(IString str) throws Exception;//将str连接到字符串的后面
public int compareTo(IString str) throws Exception;//比较str和当前字符串的大小
public int indexOf(IString str,int begin);//从位序号为begin的字符开始搜索与str相等的子串
}
串的Java接口的实现方法主要有以下两种:
- 基于顺序存储的实现:顺序串;
- 基于链式存储的实现:链串;
顺序串
顺序串与顺序表的逻辑结构相同,存储结构类似,均可用数组来存储数据元素。但串具有独特的不同于线性表的操作,属于特殊类型的线性表。
public class SqString implements IString{
private char[] strValue;//字符数组存放串值
private int curLen;//当前串的长度
//构造空串
public SqString(){
strValue=new char[0];
curLen=0;
}
//以字符串常量构造串
public SqString(String str){
char[] p=str.toCharArray();
strValue=p;
curLen=p.length;
}
//以字符数组构造串
public SqString(char[] str){
strValue=new char[str.length];
for(int i=0;i<str.length;i++){
strValue[i]=str[i];
}
curLen=str.length;
}
//将串变为空串
public void clear() {
curLen=0;
}
//判断是否为空串
public boolean isEmpty() {
return curLen==0;
}
//返回串的长度
public int length() {
return curLen;
}
//返回位序号为i的字符
public char charAt(int i) throws Exception {
if(i<0||i>=curLen)
throw new StringIndexOutOfBoundsException(i);
return strValue[i];
}
//将串的长扩充为newCapacity
public void allocate(int newCapacity) {
char[] tmp=strValue;
strValue=new char[newCapacity];
for(int i=0;i<tmp.length;i++){
strValue[i]=tmp[i];
}
}
//返回位序号从begin到end-1的子串
public IString subString(int begin, int end) {
if(begin<0||begin>=end||end>curLen)//判断参数是否合法
throw new StringIndexOutOfBoundsException("参数不合法");
char []tmp=new char[end-begin];
for(int i=begin;i<end;i++)//复制子串
tmp[i-begin]=strValue[i];
return new SqString(tmp);
}
//在第i个字符之前插入子串str
public void insert(int i, IString str) throws Exception {
if(i<0||i>curLen)//判断参数i是否合法
throw new StringIndexOutOfBoundsException("插入位置不合法");
int len=str.length();
int newCapacity=len+curLen;
allocate(newCapacity);//重新分配存储空间
for(int j=curLen-1;j>=i;j--){//移动数据元素
strValue[j+len]=strValue[j];
}
for(int j=i;j<i+len;j++){//插入
strValue[j]=str.charAt(j-i);
}
curLen=newCapacity;
}
//删除位序号从begin到end-1的子串
public void delete(int begin, int end) {
if(begin<0||end>curLen||begin>=end){//判断参数是否合法
throw new StringIndexOutOfBoundsException("参数不合法");
}
for(int i=begin;i<=end-1;i++){//向前移动元素
strValue[i]=strValue[i+end-begin];
}
curLen=curLen-end+begin;
}
//将str连接到字符串的后面
public void concat(IString str) throws Exception {
insert(curLen,str);
}
//比较str和当前字符串的大小
public int compareTo(IString str) throws Exception {
int n=Math.min(curLen,str.length());
for(int i=0;i<n;i++){
if(strValue[i]>str.charAt(i))
return 1;
if(strValue[i]<str.charAt(i))
return -1;
}
return 0;
}
public int[] next(IString p) throws Exception{
int[] next=new int[p.length()];
int k=0;
next[0]=-1;
next[1]=0;
for(int i=2;i<p.length();i++){//对串中的每个字符求next值
if(p.charAt(i-1)==p.charAt(k)){//匹配
next[i]=next[i-1]+1;
k++;
}
else if(k==0){
next[i]=0;
}
else {//未匹配
k=next[k];
}
}
return next;
}
public int KMP(IString p,int begin) throws Exception{
int[] next=next(p);//计算next值
int j=0;
for(int i=0;i<=curLen-p.length();){//i为主串的字符指针
if(j==-1||strValue[i]==p.charAt(j)){//比较的字符相等或者比较主串的下一个字符
i++;
j++;
}
else {//比较的字符不等
j=next[j];
}
if(j==p.length()-1)//匹配
return i-p.length();
}
return -1;
}
@Override
public int indexOf(IString str, int begin) {
// TODO Auto-generated method stub
return 0;
}
}
顺序串基本操作的实现
1:求字串操作
步骤:
- 检查参数 begin 和 end 是否满足 0<=begin<=n-1 和 begin<end<=n,若不满足抛出异常;
- 返回序列号为 begin 到 end-1 的字符串序列;
//返回位序号从begin到end-1的子串
public IString subString(int begin, int end) {
if(begin<0||begin>=end||end>curLen)//判断参数是否合法
throw new StringIndexOutOfBoundsException("参数不合法");
char []tmp=new char[end-begin];
for(int i=begin;i<end;i++)//复制子串
tmp[i-begin]=strValue[i];
return new SqString(tmp);
}
2:插入操作
步骤:
- 判断参数 i 是否满足 0<=i<=n,若不满足,则抛出异常;
- 重新分配存储空间为 n+m,m为插入的字符串 str 的长度;
- 将第 i 个及之后的数据元素向后移动 m 个存储单元;
- 将 str 插入到字符串从 i 开始的位置;
//在第i个字符之前插入子串str
public void insert(int i, IString str) throws Exception {
if(i<0||i>curLen)//判断参数i是否合法
throw new StringIndexOutOfBoundsException("插入位置不合法");
int len=str.length();
int newCapacity=len+curLen;
allocate(newCapacity);//重新分配存储空间
for(int j=curLen-1;j>=i;j--){//移动数据元素
strValue[j+len]=strValue[j];
}
for(int j=i;j<i+len;j++){//插入
strValue[j]=str.charAt(j-i);
}
curLen=newCapacity;
}
3:删除操作
步骤:
- 判断参数 begin 和 end 是否满足 0<=begin<=curLen-1 和 begin<end<=curLen,若不满足,则抛出异常;
- 将字符串位序号为 end 的数据元素及其之后的数据元素向前移动到位序号为 begin 的位置;
- 将字符串的长度减小 end-begin ;
//删除位序号从begin到end-1的子串
public void delete(int begin, int end) {
if(begin<0||end>curLen||begin>=end){//判断参数是否合法
throw new StringIndexOutOfBoundsException("参数不合法");
}
for(int i=begin;i<=end-1;i++){//向前移动元素
strValue[i]=strValue[i+end-begin];
}
curLen=curLen-end+begin;
}
4:连接操作
concat(IString str) 是将串 str 插入到字符串的尾部,此时调用 insert(curLen,str) 即可实现:
//将str连接到字符串的后面
public void concat(IString str) throws Exception {
insert(curLen,str);
}
5:比较操作
步骤:
- 确定需要比较的字符的个数 n 为两个字符串长度的较小值;
- 从下标 0 至 n-1 依次进行比较;
//比较str和当前字符串的大小
public int compareTo(IString str) throws Exception {
int n=Math.min(curLen,str.length());
for(int i=0;i<n;i++){
if(strValue[i]>str.charAt(i))
return 1;
if(strValue[i]<str.charAt(i))
return -1;
}
return 0;
}
链串
链串采用链式存储结构。链串由一系列大小相同的结点组成,每个结点用数据域存放字符,指针域存放指向下一个结点的指针。
每个结点的数据域可以是一个字符或者多个字符。若每个结点的数据域为一个字符,这种链表称为单字符链表;若每个结点的数据域为多个字符,则称为块链表。
在块链表中的每个结点的数据域不一定被字符占满,可通过添加空字符或者其他非串值字符来简化操作。
串的链式存储结构中,单字符链表的插入、删除操作较为简单,但存储效率低。
块链表虽然存储效率较高,但插入、删除操作需要移动字符,较为复杂。此外,与顺序串相比,链串需要从头部开始遍历才能访问某个位置的元素。