串的基础

引言:字符串是由字符组成的有限序列,从逻辑结构看,串是一种“特殊的线性表”,其特殊性在于线性表中的每个元素是一个字符。作为
一种抽象数据类型,串有自己的一组操作,其操作特点与线性表不同。

1.串的基本概念
a.串定义:串(string)是由零个或多个字符组成的有限序列,又叫字符串。一般记为 s=”a1a2...an”( n≥0 )。串中的字符数目n成为串的长度,有限。零个字符的串成为空串。一个字符在串中的位置值该字符在串中的“序号”,约定串中第一个字符的序号为0,-1表示该字符不在指定串中。
注意:空串和空格串是不一样的,空串一个元素都没有,空格串只有空格元素。

b.子串:由串s中任意连续字符组成的一个子序列叫做s的子串。比如“ta”是“data”的子串。
(注意:1.空串是任意串的子串 2.任意串s都是它自身的子串 3.除自身外,其余的子串都是其s的真子串 4.子串的序号指该子串的第一个字符在主串中的序号)

c.串比较
两个串的比较依次由对应位置的字符进行比较,如果在对应位置上,有不同,则两个串的大小由对应位置的第一个不同字符的大小决定,如果串长度不一样而对应位置的字符相同时,较长的串较大,如果对应的字符和长度都相同,则两个串相等。

2.串抽象数据类型
a.串的基本操作有:创建一个串,求串长度,读取或设置字符,求子串,插入,删除,连接,判断相等,查找,替换等。
(注意:求子串,插入,查找等操作以子串为单位,一次操作处理多个字符)

b.串抽象数据类型的SString接口声明:
public interface SString
{
int length(); //返回串的长度
char charAt(int i) //返回第i(i大于等于0)个字符
SString concat(SString str);//返回当前串与str串连接生成的新串
SString substring(int begin,int end); //返回串中字符序号从begin至end-1的子串
void setCharAt(int i,char ch); //设置第i个字符为ch
SString insert(int i,SString str);//在第i个字符处插入str串
SString delete(int begin,int end);//删除从begin到end-1的子串
int indexOf(SString pattern); //返回模式串pattern在串中的首次匹配位置
}

3.串的实现

1)说明:其有顺序存储和链式存储两种存储结构。其方式有以下两种:

a..串的顺序存储结构:串的顺序存储结构采用”字符数组”将串中的字符序列依次连续存储在数组的相邻单位中。
其使用字符数组有两种方案:
a.数组容量等于串长度。这种方式串不易增加字符,通常用于存储串常量。
b.数组容量大于串长度。这种方式串便于增加字符,通常用于存储串变量,此时还有要一个整型变量记载串的长度。
(优点:串具有随机存储功能,存取指定未知字符的时间复杂度为O(1);
缺点:1.删除或者插入元素时需要移动元素,平均移动数据量是串长度的一半
2.当数组容量不够时,需要重新申请一个更大的数组,并复制原数组中的所有元素。

b..串的链式存储结构
.种类
1.单字符链表:每个结点的数据域只包含一个字符的单链表(缺:占用存储空间太多)
2.块链表:每个结点的数据域包含若干字符的单链表。(缺:插入和删除操作需要移动元素,效率较低)

 

2)串的具体实现(串一般是顺序表实现的,所以链表的实现这边就不说了)

接口准备:

 

package org.Stone6762.MString;

/**
* @MString
* @Stone6762
* @Description
*/
public interface MString {

/**
* @Title: clear
* @Description: TODO(将字符串清空)
*/
public void clear();

/**
* @Title: isEmpty
* @Description: TODO(判断是否为空)
* @return
*/
public boolean isEmpty();

/**
* @Title: length
* @Description: TODO(求字符串的长度)
* @return
*/
public int length();

/**
* @Title: charAt
* @Description: TODO(取出字符串中的某一个下标下的字符)
* @param index
* @return
*/
public char charAt(int index);

/**
* @Title: subString
* @Description: TODO(根据起始和结束的下标来截取某一个子串)
* @param begin要截取部分的起始部分,包括起始部分
* @param end截止到end,不包括end
* @return
*/
public MString subString(int begin, int end);

/**
* @Title: insert
* @Description: TODO(根据下标将一个字符串插入到指定的位置)
* @param offset要插入的字符串的第一个位置
* @param str
* @return
*/
public MString insert(int offset, MString str);

/**
* @Title: delete
* @Description: TODO(删除掉串中的某一部分)
* @param begin
* @param end
* @return
*/
public MString delete(int begin, int end);

/**
* @Title: concat
* @Description: TODO(将一个字符串连接到该字符串的末尾)
* @param str
* @return
*/
public MString concat(MString str);

/**
* @Title: compareTo
* @Description: TODO(两个字符串进行比较)
* @param str
* @return
*/
public int compareTo(MString str);

/**
* @Title: indexOf
* @Description: TODO(在本字符串中,从begin位置开始,检索某一个字符串第一次出现的位置)
* @param str
* @param begin
* @return
*/
public int indexOf(MString str, int begin);
}

 

串的顺序存储实现

package org.Stone6762.MString.imple;

import org.Stone6762.MString.MString;

/**
* @SeqString
* @Stone6762
* @Description
*/
public class SeqString implements MString {

/**
* @Fields strvalue : TODO(储存串中的每一个元素)
*/
private char[] strvalue;

/**
* @Fields curlen : TODO(记录当前的串的长度)
*/
private int curlen;

/**
*  @Description: TODO(构造方法一,默认的)
*
*/
public SeqString() {
this.strvalue = new char[0];
this.curlen = 0;
}

/**
*  @Description: TODO(构造方法二,设置顺序存储的串的最大长度)
*
* @param maxSize
*/
public SeqString(int maxSize) {
this.strvalue = new char[maxSize];
this.curlen = 0;
}

/**
*  @Description: TODO(构造方法三、以一个字符串构造串对象)
*
* @param str
*/
public SeqString(String str) {
strvalue = str.toCharArray();
curlen = strvalue.length;
}

/**
*  @Description: TODO(构造方法四,以一个字符数组构造串对象)
*
* @param value
*/
public SeqString(char[] value) {
this.strvalue = new char[value.length];
for (int i = 0; i < value.length; i++) {
strvalue[i] = value[i];
}
curlen = value.length;

}

public char[] getStrvalue() {
return strvalue;
}

public int getCurlen() {
return curlen;
}

@Override
public void clear() {
this.curlen = 0;
}

@Override
public boolean isEmpty() {
return this.curlen == 0;
}

@Override
public int length() {
return this.curlen;
}

@Override
public char charAt(int index) {
if (index < 0 || index >= this.curlen) {
throw new StringIndexOutOfBoundsException();
}
return strvalue[index];
}

/**
* @Description: TODO( 扩充串存储空间容量,参数指定容量 )
* @param newCapacity
*/
public void allocate(int newCapacity) {
/*
* 先将原来的数据进行保存,然后根据参数创建一个目标大小的存储空间 最后将保存的数据存储到新的空间中
*/
char[] tempCArry = this.strvalue;
strvalue = new char[newCapacity];
for (int i = 0; i < tempCArry.length; i++) {
strvalue[i] = tempCArry[i];
}
}

@Override
public MString subString(int begin, int end) {
/*
* 首先要判断想要截取的下标的合法性(不能越界,开始不能大于结束) 然后直接根据下标找到目标字符数组,然后根据字符数组创建一个顺序存储的串
*/
if (begin < 0) {
throw new StringIndexOutOfBoundsException("起始位置不能小于0");
}
if (end > curlen) {
throw new StringIndexOutOfBoundsException("结束位置不能大于串本身的长度" + curlen);
}
if (begin > end) {
throw new StringIndexOutOfBoundsException("起始位置不能大于中止位置");
}
if (begin == 0 && end == curlen) {
return this;
} else {
char[] buffer = new char[end - begin];
for (int i = 0; i < buffer.length; i++) {
buffer[i] = this.strvalue[i + begin];
}
return new SeqString(buffer);
}
}

@Override
public MString insert(int offset, MString str) {
/*
* 首先判断插入位置的合法性(小于0或大于原本串的长度) 然后判断是否能够存储,不能就扩充
* 最后,先将要插入部分的字符向后移,再将要插入的字符插入即可
*/
if (offset < 0 || offset > curlen) {
throw new StringIndexOutOfBoundsException("");
}
int strlen = str.length();
int newlength = strlen + curlen;
if (newlength > this.strvalue.length) {
allocate(newlength);
}
// 将offset后的元素都后移strlen位
for (int i = curlen - 1; i >= offset; i--) {
strvalue[strlen + i] = strvalue[i];
}
// 插入
for (int i = 0; i < strlen; i++) {
strvalue[offset + i] = str.charAt(i);
}
this.curlen = newlength;
return this;
}

@Override
public MString delete(int begin, int end) {

/*
* 首先将判断下标的合法性,起始不能小于0,结束不能超出当前串的长度,起始不能大于结束
* 然后将end以后(包括end)的字符都向前移动到起始的位置。
*/
if (begin < 0) {
throw new StringIndexOutOfBoundsException("起始位置不能小于0");
}
if (end > curlen) {
throw new StringIndexOutOfBoundsException("结束位置不能大于串本身的长度" + curlen);
}
if (begin > end) {
throw new StringIndexOutOfBoundsException("起始位置不能大于中止位置");
}
for (int i = end; i < curlen; i++) {
this.strvalue[begin + i] = this.strvalue[i];
}
curlen = curlen - (end - begin);
return this;
}

@Override
public MString concat(MString str) {
/*
* 首先判断是否能够存储
*/
int newlen = str.length() + curlen;
if (newlen > strvalue.length) {
allocate(newlen);
}
for (int i = 0; i < str.length(); i++) {
strvalue[curlen + i] = str.charAt(i);
}
return this;
}

@Override
public int compareTo(MString str) {
/*
* 首先以一个长度小的为基准,遍历比较,一旦出现不相等,返回比较值。 如果长度相同的部分都相等,返回长度比较值
*/
int len1 = curlen;
int len2 = str.length();
int n = Math.min(len1, len2);

char[] cArry1 = strvalue;
char[] cArry2 = ((SeqString) str).strvalue;
int i = 0;
char c1, c2;
while (i < n) {
c1 = cArry1[i];
c2 = cArry2[i];
if (c1 != c2) {
return c1 - c2;
}
i++;
}
return len1 - len2;
}

@Override
public int indexOf(MString str, int begin) {

return 0;
}
}

特别提醒:这边的indexof()方法未实现,因为较难,所以放在下节讲,这边特别得注意。

 

 

 

补充与扩展:

       Java语言的字符串主要有字符串常量类String,字符串变量类StringBuffer等。这两种串类都采用
顺序存储结构,能够存储任意长度的字符串,实现串的基本操作,并且能够识别序号越界等错误,对数组
占用的存储空间进行控制,具有健壮,安全性好等特点,现在我们就对这两个对象进行分析。


常量字符串类String
模拟java.lang.String类的MyString类的部分声明
(注意:MyString类是最终类,其不能被继承,实现“可比较接口”和“序列化接口”。)

public final class MyString implements Comparable<MyString>,java,io,Serializable
{
private final char[] value; //字符数组,私有最终变量,只能赋值一次。
//构造一个空串
public MyString(){ this.value=new char[0];}

//由字符串常量构造串对象,用其中的函数获得字符串中的字符数组
public MyString(java.lang.String original){ this.value=original.toCharArray();}

//以value数组中从begin开始的count个字符构造串对象
public MyString(char[] value,int begin,int count)
{
this.value=new char[count]; //当value==null时,java抛出空对象异常
for(int i=begin;i<begin+count;i++) //复制数组
this.value[i]=value[i];
}
//以value数组中字符构造串对象
public MyString(char[] value){ this(value,0,value.length); }
//拷贝构造方法,复制对象
public MyString(MyString str){ this(str.value); }
//返回字符串的长度
public int length(){ return this.value.length;}
  
 public char charAt(int i)      //返回第i个字符
    {
           if(i<0||i>this.value.length)
                  throw new StringIndexOutOfBoundsException(i);    //抛出字符串索引越界异常
               return this.value[i];
     }
 
 public java.lang.String toString(){ return new String(this.value);}


实现分析:
a.构造字符串
MyString s1=new MyString(); //构造一个空串
MyString s2=new MyString(“ads”); //以java.lang.String字符串常量构造串对象
char[] letters={'a','b','c','d'}; //字符数组,只能在声明时赋值,不能赋值为“abcd"
MyString s3=new MyString(letters); //以字符数组构造串对象
MyString s4=new MyString(s3); //拷贝构造方法

注意:String类和普通类有两点不同,String能够赋值为字符串变量,并且能够使用“+”运算符连接其他类型变量值。但是Mystring类没有实现这两个功能。
String a=“abc";
str=str+"a";


b.常量串中字符的只读属性
String类的字符数组声明为最终变量,所以串中各字符是只读的,String类提供charAt(int i)方法返回第i个字符,不提供setCharAt(int i,char ch)修改第i个字符

c.数组复制
串连接和求子串操作都需要复制字符数组。
方法:
1.for语句
2.java.lang.System.arraycopy(value,0,this.value,0,value.length); //复制数组,功能同上述for语句
3.this.value=java.util.Arrays.copyOf(value,value.length); //复制数组,包括申请数组空间


d.若charAt(int i)方法参数i指定序号错误,抛出字符串序号越界异常

 

以下为操作String对象的内部方法。
1.获得整数和实数字符串表示的数值。

public static int parseInt(String s) //返回整数字符串s表示的整数值
{
int x=0,i=0;
int sign=s.charAt(0)=='-'?-1:1; //符号位,记住正负数标记
if(s.charAt(0)=='+'||s.charAt(0)=='-') //跳过符位号
i++;

while(i<s.length()){
if(s.charAt(i)>='0'&&s.charAt(i)<='9')
{ x=x*10+s.charAt(i++)-'0'; } //x记住当前获得的整数值,这就是语法

else throw new NumberFormatException(s); //抛出数值格式异常
}
return x*sign; //返回整数值
}

public static double parseDouble(String s) //返回实数字符串表示的浮点数值
{
int n=s.length(), i=0;
int sign=s.charAt(0)=='-'?-1:1;
double x=0,power=10.0E0; //power表示底数为10的幂
if(s.charAt(0)=='+'||s.charAt(0)=='-') //跳过符位号
i++;

while(i<n&&s.charAt(i)>='0'&&s.charAt(i)<='9'){ //获得整数部分值
x=x*10+s.charAt(i++)-'0'; }
if(i<n&&s.charAt(i)=='.') //若是小数点
{
i++;
while(i<n&&s.charAt(i)>='0'&&s.charAt(i)<='9') //获得小数部分值
{
x+=(s.charAt(i)-'0')/power;
i++;
power*=10;
}
}
x*=sign;

if(i<n&&(s.charAt(i)=='E'||s.charAt(i)=='e')) //处理阶码
{
i++;
power=(s.charAt(i)=='-')?0.1:10; //阶码的符号位决定指数的正负及其运算
if(s.charAt(i)=='+'||s.charAt(i)=='-')
{ i++;}

int exp=0;
while(i<n&&s.charAt(i)>='0'&&s.charAt(i)<='9'){
exp=exp*10+s.charAt(i++)-'0'; } //获得指数的绝对值

for(int j=0;j<exp;j++)
x*=power;
}
return x;
}


思路:
1.获得正负号,并且跳过
2.获取整数(如果只有整数直接乘上正或者负号)
3.如果有小数点获取小数,加上整数
4.如果有阶码E,e,记住并且获得其指数的绝对值,最后第三步得出的数乘以E^指数或者e^指数。

 

2.连接串

public MyString concat(MyString str) //返回当前串与制定串str连接生成的新串

if(str==null||str.length()==) //欲连接的串为空时,返回当前串
return this;

char[] buffer=new char[this.value.length+str.length()];
int i;
for(i=0;i<this.value.length;i++) //复制当前串
buffer[i]=this.value[i];

for(int j=0;j<str.value.length;j++) //复制制定串str
buffer[i+j]=str.value[j];

return new MyString(buffer); //以字符数组构造串对象


思路:
1.如果加入的串是空的,就返回当前串
2.如果不为空,创建一个新的数组,其容量为两个数组的总和。
3.利用for循环,将各个数组复制到新的数组里面去。
注意,要被每个数组开始的数不一样。
4.返回以字符数组构造串对象


3.求子串
public MyString substring(int begin,int end) //返回串中序号从begin至end-1的子串

if(begin<0) { begin=0;}
if(end>this.value.length) { end=this.value.length;} //序号容错处理

if(begin>end) throw new StringIndexOutOfBoundsException(end-begin);

if(begin==0&&end=this.value.length) return this;

char[] buffer=new char[end-begin];
for(int i=0;i<end-length;;i++){
buffer[i]=this.value[i+begin];
}
return new MyString(buffer) ; //以字符数组构造串对象

思路:
1.对begin<0,end>this.value.length的序号容错处理。
2.begin>end,抛出异常
3.begin=0,end=this.value.length.返回本串
4.建立新的数组,容量为end-begin,利用for循环复制子串。
5.返回以字符数组构造串对象。

 

4.比较串相等
public boolean equals(Object obj) //比较当前串是否与obj引用的串相等
{
if(this==obj)
return true;

if(obj instanceof MyString)
{
MyString str=(MyString)obj;
if(this.value.length==str.value.length)
{
for(int i=0;i<this.value.length;i++){
if(this.value[i]!=str.value[i]) {return false;}
}
return true;
}
}
return false;

思路:
1.如果两者对象相等,返回true
2.如果obj是属于MyString的同种类型,强转obj,分两中情况,
如果长度相等,继续处理,长度不等,返回false。
3.继续处理为利用for循环进行遍历确保是否有元素不等,如果是,返回false,如果不是,返回true。


5.比较串的大小
public final class MyString implements Comparable<MyString>,java,io,Serializable
{
public int compareTo(MyString str)         //比较当前串与str串的大小,返回两者差值
   {
       for(int i=0;i<this.value.length&&i<str.value.length;i++){
if(this.value[i]!=str.value[i]){ return this.value[i]-str.value[i];} //返回两串第一个不同字符的差值

return this.value.length-str.value.length; //返回两串长度的差值


思路:
1.定义一个返回整数的函数
2.返回什么数由自己定义,比如这边返回的是两串第一个不同字符的差值
和返回两串长度的差值。

 

6.String的插入,删除操作。

String s1="Abcde", s2="Xyz";
int i=3;
String s3=s1.substring(o,i)+s2+s1.substring(i);
String s4=s3.substring(0,3)+s3.substring((6));

思路:
建立一个新的字符串,其构成分为几个部分,每个部分由substring从另外一个字符串得到我们想要的子串或者直接字符串,
进而完成插入和删除操作。


7.获得一个整数的二进制或十六进制形式字符串。

public static String toBinaryString(int n) //返回int整数的二进制字符串,除2取余法

String str="";
for(int i=0x80000000;i!=0;i>>>=1) //一个int占32位,右移一位,高位以0填充
str+=(n&i)==0?'0':'1';
return str; //返回字符串
}

public static String toHexString(int n) //返回正整数n的十六进制字符串

String str="";
while(n>0) //除16取余法,余法存入str字符串

int k=n%16;
str=(char)(k<=9?k+'0':k+'A'-10)+str; //讲0~9,10~15转换为'0'~’9'.'A'~'F'
n/=16;
}
return str;
}

//返回正整数n的radix进制字符串,tadix取值为2,8,16
public static String toString(int n,int radix)
{
String str="";
while(n>0) //除radix取余法,余数存入str字符串
{
int k=n%radix;
str=(char)(k<=9?k+'0':k+'A'-10)+str;
n/=radix;
}
return str;
}

 

 


变量字符串类StringBuffer
引言:其以串变量方式实现字符串功能。其字符数组容量大于串长,并且能够修改,此时用一个整型变量len记载串的长度。串变量的插入,删除操作与线性表相似,
都要移动元素,但是对串的操作一次处理一个子串。
a.StringBuffer类声明
public final class MyStringBuffer implements java.io.Serializable //字符串类
{
private char[] value; //字符数组,私有成员变量
private int len; //串长度

public MySringBuffer(int size) //构造指定容量的空串
{
this.value=new char[size<16?16:size];
this.len=0;
}

public MyStringBuffer(){this(16);} //以默认容量构造空串
public MyStringBuffer(String str) //以字符串常量构造串对象
{
this(str.length()+16);
this.append(str);
}

//返回字符串长度,value.length是数组容量
public int length(){ return this.len;}
public synchronized char charAt(int i) //返回第i(i>=0)个字符
{
if(i<0||i>=this.len)
throw new StringIndexOutOfBoundsException(i);
return this.value[i];
}

public void setCharAt(int i,char ch) //设置第i个字符为ch
{
if(i<0||i>=this.len)
throw new StringIndexOutOfBoundsException(i);

this.value[i]=ch;
}

//以字符数组value从0至len元素构造String串
public synchronized String toString(){ return new String(this.value,0,this.len); }
}

 

 

以下为内部方法
1.插入串
public synchronized MyStringBuffer insert(int i,MyStringBuffer str) //在下标为i个字符处插入str串

if(i<0) i=0;
if(i>this.len) i=this.len; //序号容错

if(str=null){ return this;}


if(this.value.length-this.len<str.len) //若当前串数组空间不足,则扩充容量

this.value=new char[this.value.length+str.len*2]; //重新申请字符数组空间
for(int j=0;j<i;j++){ //复制当前串下标为前i-1个字符
this.value[j]=temp[j];


for(int j=i;j<this.len;j++)
this.value[str.len+j]=temp[j]; //从i开始向后移动j个字符

 

for(int j=0;j<str.len;j++) this.value[i+j]=str.value[j]; //复制字符串strs

this.len+=str.len;
return this;
}

思路
1.容错处理
2.容量不足处理,扩容(注意:最好选取this.value.length+str.len*2,完成了扩容又不会浪费内存)
3.复制下标i-1个字符,复制以前的i开始以后的字符,但是要移动j个字符。最后复制strs在i到i+j下标中。串长增加。
4.返回字符串

 


删除子串
public synchronized MyStringBuffer delete(int begin,int end) //删除序号从begin到end-1的子串
{
//序号容错
if(begin=<0){begin=0;} //从开头开始删除
if(end>this.len){ end=this.len;} //删除到结尾

if(begin>end) { throw new StringIndexOutOfBoundsException(end-begin);}

for(int i=0;i<this.len-end;i++){
this.value[begin+i]=this.value[end+i]; //下标为begin之后的开始的元素变成end之后的元素。
}
this.len-=end-begin;
return this;
}


思路:
1.容错处理
2.抛出异常处理
3.进行删除操作,下标为begin之后的开始的元素变成end之后的元素
4.改变串长
5.返回串

 

posted @ 2017-07-10 17:15  鹏达君  阅读(870)  评论(0编辑  收藏  举报