目录
字符串是由多个字符组成的有限序列,可以视为由若干个仅包含一个字符的结点组成的特殊线性表。字符串可以用顺序存储结构和链式存储结构实现
一、串的概念及类型定义
字符串简称为串(string),它由多个字符组成的有限序列。计算机中央处理器的指令系统一般都包含支持基本的数值操作的指令,而对字符串数据的操作一般需用相应算法来实现,有的高级程序设计语言提供了某种字符串类型及一定的字符串处理功能。
字符串可以视为是由若干个仅包含一个字符的数据结点组成的特殊线性表,字符串可以用顺序存储结构和链式存储结构实现。
1:串的定义及其抽象数据类型
串是由n(n≥0)个字符组成的有限序列,记作:
String={}
其中n表示字符个数,称为串的长度,n=0称为空串。
串中所能包含的字符依赖于所使用的字符集和字符编码,C#采用unicode,而非ASCII码。
C#采用单引号将字符扩起来,用双引号将字符串扩起来。
1)字符及字符串的编码与比较
不同的字符在字符集编码中式按顺序排列编码的,字符可以按其编码次序规定它的大小,因此两个字符可以进行比较,比如:
'A'<'a',结果为true; '9'>'A',结果为false
2)子串及子串在主串中的序号
由串的所有字符组成的序列为串本身,称为主串。
而由串中若干连续的字符组成的子序列称为子串(substring)。
空串是任何串的子串;主串也是自身的子串,初主串外,串的其他子串称为真子串。
串s中的某个字符c的位置可用其在串中的位置序号整数表示,称为字符c在串s中的序号(index),串的第一个位置序号为0,如果不包含该字符,则序号为-1。
3)串的基本概念
Initialize:初始化,预分配一定的存储空间,建立一个空串
Length:长度:返回字符个数
Empty/Full:判断空/满
Get/Set:获得或设置串中指定位置的字符值
Concat:连接两个串
Substring:求满足特定条件的子串
IndexOf:查找字符或子串
2:C#中的串类
C#基础类库中定义了两个串类:类String和类StringBuilder。
String定义在System命名空间中,用于一般文本的表示,C#预设的关键字string类型和System.String类的简化的别名,他俩是一样的,string对象一旦创建就不能的修改。
C#库在System.Text,命名空间中还定义了一个字符串类StringBuilder,此类表示值为可变字符序列的对象,即StringBuilder类型的对象在创建后可通过追加、移除、替换或插入字符而修改。
string类具有如下成员实现串的各种操作
公共构造函数:
String(char[]); //初始化String类的新实例
String(char[]cs,int startIndex, int length);
string(char c,int count);
公共属性:
int length {get;} //获取串中的字符个数
char this[int index]{get;}//获取串中位于指定位置的字符
公共方法:
using System;
static int Compare(string strA, string StrB);//比较字符串的大小
int CompareTo(string StrB); //将此实例与指定的String对象进行比较
static string Concat(object a); //创建指定对象的String表示形式
int IndexOf(char c); //返回指定字符在串中的第一个匹配项的索引
int IndexOf(string str); //返回指定串在此实例中的第一个匹配项的索引
string Insert(int startIndex,string s); //在指定位置插入指定string实例
string Remove(int startIndex,int count); //在指定位置开始删除指定数目的字符
string Replace(string oldstr,string newstr);//将指定子串的所有匹配项替换为新的子串
string Substring(int startIndex,int length);//返回从指定位置开始,具有指定长度的子字符串
StringBuilder与String方法和属性类似
例1:字符与字符串的比较
using System;
namespace stringtest
{
class StringCompare
{
static void Main(string[] args)
{
char c1='A';
char c2='a';
Console.WriteLine("A<a:{0}",c1<c2);
string s1="data";
string s2="date";
Console.WriteLine("\"data\"<\"date\":{0}",s1.CompareTo(s2)<0?true:false);
}
}
}
输出结果为:
A<a:True
"data"<"date":True
例2:从身份证号码中提取出生年月日信息
using System;
namespace stringtest
{
class StringCompare
{
static void Main(string[] args)
{
string id="612728199503243014";
int y=int.Parse(id.Substring(6,4));
int m=int.Parse(id.Substring(10,2));
int d=int.Parse(id.Substring(12,2));
Console.WriteLine("出生于:{0}年{1}月{2}日",y,m,d);
DateTime dt=new DateTime(y,m,d); //dt代表将输入的时间格式转换成和系统时间格式一样的类型
TimeSpan ts=DateTime.Now-dt; //DateTime.Now代表现在的时间
Console.WriteLine("至今已活了{0}天",ts.Days);
}
}
}
输出结果为:
出生于:1995年3月24日
至今已活了9879天
二、串的顺序存储结构及实现
1:串的顺序存储结构的定义
字符串的顺序存储结构是值用一个占据连续存储空间的数组来存储字符串的内容
类中的成员变量有items和count。成员变量items为一个字符数组,将用以存储串的内容,count用于记录串的长度.
"calss SequencedString":IComparable"在形式上是说SequencedString类将实现IComparable接口,目的是将我们定义的串类设计成一种可比较的类型,这只需在类的设计中,完成方法CompareTo的具体定义
2:串的基本操作的实现
1)串的初始化
使用构造方法创建并初始化一个串对象:它为items数组申请指定大小的存储空间,将用来存放字符串的数据;设置串的初始长度为0,多种形式的构造方法编码如下:
//构造n个存储单元的空串
public SequencedString(int n)
{
items=new char[n];
count=0;
}
//构造16个存储单元的空串
public SequencedString():this(16){}
//构造包含一个指定字符的串
public SequencedString():this(16)
{
items[0]=c;
count++;
}
//以一个字符数组构造串
public SequencedString(char[]c):this(char.Length*2)
{
Array.Copy(c,itmes,c.Length);//复制数组
count=c.Length;
}
2)获取串长度
使用Length来实现,编码如下:
public int Length{get{return count;}}
3)判断串状态是否为空或已满
通过分别定义bool类型的属性Empty和Full来相应地实现这两个测试操作。
当count等于0时,表明串为空状态
public bool Empty
{
get{return count==0;}
}
当count等于items.Length时,表明串为满状态
public bool Full
{
get{return count==items.Length;}
}
4)获取或设置串的第i个字符值
将这两个操作通过定义一个读写型索引器成员予以实现,它提供以类似于访问数组的方式访问串实例的机制
public char this[int i]
{
get{
if(i>=0&&i<count)
return items[i];
else
throw new IndexOutOfRangeException("Index of Range Exception in"+this.GetType());
}
set{
if(i>=0&&i<count)
items[i]=value;
else
throw new IndexOutOfRangeException("Index of Range Exception in"+this.GetType());
}
}
5)连接一个串或一个字符
方法Concat(char c)的、将指定的字符c加入串对象的尾部,但是如果串当前分配的存储空间已装满,需要调用本类中设计的私有方法DoubleCapacity重新分配更大的存储空间,并将原数组中的字符数据逐个拷贝,编码如下:
public void Concat(char c)
{
if(Full)
DoubleCapacity();
this.items[count]=c;
count++;
}
private void DoubleCapacity()
{
int len=Length;
int capacity=2*items.Length;
char[]copy=new char[capacity];
for(int i =0;i<len;i++)
copy[i]=items[i];
items=copy;
}
6)连接两个串
方法Concat(StringObject)链接两个串实例的内容,依次将参数指定的串的每个字符连接到当前串对象。也可以通过对运算符"+"关于类SequencedString重载,连接两个串str1和str2,这样两个串的连接操作可以表示为:
str1.Concat(str2);
或者表示为:str3=str1+ste2;
相应的实现编码如下:
public void Concat(SequencedString s2)
{
if(s2!=null)
{
for(int i=0;i<s2.Length;i++)
{
this.Concat(s2[i]);
}
}
}
public static SequencedString operator +(SequencedString s1,SequencedString s2)
{
SequencedString newstr=new SequencedString(s1.Length+s2.Length+8);
newstr.Concat(s1);
newstr.Cpmcat(s2);
return newstr;
}
7)获取串的子串
Substring方法返回当前串实例中从序号i开始的长度为n的子串。
public SequencedString Substring(int i,int n)
{
int j=0;
if(i>=0&&n>0&&(i+n<=this.Length))
{
SequencedString sub=new SequencedString(n*2);
while(j<n)
{
sub.items[j]=this.items[i+j];
j++;
}
sub.count=j;
return sub;
}
else
{
return null;
}
}
8)查找子串
IndexOf方法在当前串实例中查找与参数sub指定的串有相同内容的子串,查找成功,返回序列,查找失败,则返回-1
public int IndexOf(SequencedString sub)
{
int i=0,j; bool found=false;
if(sub.Length==0)return 0;
while(i<=count-sub.Length)
{
j=0;
while(j<sub.Length&&this.items[i+j]==sub[j])
j++;
if(j==sub.Length)
{
found=true;
break;
}
else i++;
}
if(found)
return i;
else
return -1;
}
9)输出串
Show方法将字符串对象的内容显示在控制台,ToCharArray方法将串对象的内容转化为一个字符数组,而重写(override)的ToString方法将这里定义的字符串类型转换为C#内在的string类型
public void Show()
{
for(int i=0;i<count;i++)
Console.Write(items.[i]);
}
public char[] ToCharArray()
{
char[] temp= new char[count];
for(int i=0;i<count;i++)
{
temp[i]=items[i];
}
return temp;
}
public override string ToString()
{
string s=new String(ToCharArray());
return s;
}
10)串的比较
将串定义为可比较的类型,只需将串类定义为实现ICompareable接口,这需在SequencedString类的设计中,在方法CompareTo中具体定义串的比较,编码如下:
public int CompareTo(object other)
{
if(this.Equals(other))
return 0;
SequencedString o= otheras SequencedString
if (o==null)
{
throw new ArgumentException("Not SequencedString);
}
int c1=this.Length
int c2=o.Length;
int result=0;
int c=c1<=c2?c1:c2;
for(int i=0;i<c&&result==0;i++)
{
result=items[i].CompareTo(o[i]);
}
if(result==0)
{
if(c1<c2)
result=-1;
else if(c1>c2)
result=1;
}
return result;
}
3:串的其他操作及实现
1)串的插入
在字符串的指定位置插入另一个串,方法签名如下:
public SequencedString Insert(int i,SequencedString s2);
它将参数s2代表的串插入到当前串实例的位置i处
该方法的实现算法描述如下:
①用substring操作将当前串分成两个子串s1和s2
②用Concat操作将sub1,s1,s2依次连接起来构成一个新串newstr
算法代码如下:
public SequencedString Insert(int i,SequencedString s2)
{
SequencedString sub1,sub2; //将原字符串分成两个字符串
sub1=this.Substring(0,i); //sub1字符串长度为0,i
sub2=this.Substring(i,this.Length-i); //sub2字符串长度为i,尾部
SequencedString newstr= new SequencedString(items.Length+s2.Length+8); //所以为什么要+8
newstr.Concat(sub1);
newstr.Concat(s2);
newstr.Concat(sub2);
return newstr;
}
在字符串的指定位置插入一个字符,方法签名如下:
public SequencedString Insert(int i,char c);
它将参数c代表的字符插入当前串实例的位置i处,方法的实现算法如下:
public SequencedString Insert(int i,char c)
{
SequencedString sub1,sub2;
sub1=this.Substring(0,i);
sub2=this.Substring(i,this.Length-i);
SequencedString newstr= new SequencedString(items.Length+8);
newstr.Concat(sub1);
newstr.Concat(c);
newstr.Concat(sub2);
return newstr
}
2)串的删除
删除串中指定位置开始的一段字串,方法签名如下:
public SequencedString Remove(int i,int n);
它删除当前串实例中从位置i开始的长度为n的子串,该方法的实现算法描述如下:
①用Substring操作将当前串分成三个子串sub1,sub2和sub3,前i个字符组成sub1,从第i个字符开始的长度为n的子串sub2,后this.Length-i-n个字符组成新串sub3。
②用Concat操作将sub1和sub3依次连接起来构成一个新串newstr。
串的删除算法的完整实现代码如下:
public SequencedString Remove(int i,int n)
{
SequencedString sub1,sub2,sub3;
sub1=this.Substring(0,i);
sub2=this.Substring(i,n);
sub3=this.Substring(i+n,this.Length-i-n);
SequencedString newstr= new SequencedString(items.Length);
newstr=Concat(sub1);
newstr=Concat(sub3);
return newstr;
}
3)串的替换
将串中的指定的子串替换成新的子串,方法签名如下:
public SequencedString Replace(SequencedString oldsub,SequencedString newsub);
它将当前串实例中oldsub子串的首次出现替换成newsub子串,实现算法的描述如下:
①用IndexOf操作找到oldsub子串在当前串实例中的位序i
②用Substring操作当前串实例分成三个子串sub1,sub2,sub2,前i个字符组成子串sub1,,中间的子串sub2与参数oldsub串相同,它之后的子串组成sub3
③用Concat操作将sub1,newsu,sub3依次连接起来构成一个新串newstr
串的替代算法如下:
public SequencedString Replace(SequencedString oldsub,Sequenced String newsub)
{
int i,n;
SequencedString sub1,sub3;
SequencedString newstr=new SequencedString(items.Length+newstr.Length);
i=this.IndexOf(oldsub);
if(i!=-1)
{
sub1=this.Substring(0,i);
n=oldsub.Length;
sub3=this.Substring(i+n,this.Length-i-n);
newstr.Concat(sub1);
newstr.Concat(newstr);
newstr.Concat(sub3);
}
return newstr;
}
4)串的逆转
将串中字符序列逆转,方法签名如下:
public SequencedString Reverse();
逆转算法描述如下:
①初始化newstr为空串
②初始化i为原串最后一个字符的位置
③进入循环,循环次数为串的长度
④取得串中第i个字符c。
⑤用Concat操作将字符c连接到串newstr之后,i自减1
串的逆转算法的代码实现如下:
public SequencedString Reverse()
{
int i;
SequencedString newstr= new SequencedString(items.Length);
for(i=this.Length-1;i>=0;i--)
{
newstr.Concat(this.items[i]);
}
return newstr;
}
三、串的链式存储结构及其实现
1:串的链式存储结构的定义
实现串的链式存储结构需要定义一个结点类,它与一般单向链表的结点类相似,只是数据元素的类型已确定为字符型,链式串的结点类StringNode声明如下:
public class StringNode
{
private char item;
private StringNode next;
public char Item
{
get
{
return item;
}
set
{
item=value;
}
}
public StringNode Next
{
get
{
return next;
}
set
{
next=value;
}
}
public StringNode(char c)
{
item=c;
next=null;
}
}
我们定义如下的LinkedString类来实现串的链式存储结构,该类声明如下:
public class LinkedString
{
private StringNode head,rear;//单向链表的头结点,尾结点引用
private int count=0;//记载串的实际长度
}
LinkedString类用单向链表的方式实现串的链式存储结构,成员变量有head,rear,count
2:串的链式存储结构基本操作的实现
1)串的初始化
用缺省的构造方法创建并初始化一个串对象,它创建一个仅包含头结点的空串。重载的带参数的构造方法可以构造含一个字符的串或一个字符数组构成串,多种形式的构造方法编码如下:
public LinkedString()
{
head=new StringNode('>');
rear=head;
count=0;
}
//构造一个字符的串
public LinkedString(char c):this()
{
StringNode q=new StringNode(c);
this.rear.Next=q;
this.rear=q;
this.count=1;
}
//以一个字符数组构造串
public LinkedString(char[]c):this()
{
StringNode p,q;
p=this.rear;
for(int i=0;i<c.Length;i++)
{
q=new StringNode(c[i]);
p.Next=q;
p=q;
}
rear=p;
count=c.Length;
}
2)获取长度
使用Length属性来实现该操作,编码如下:
public int Length{get{return count;}}
3)判断串状态是否为空或已满
使用Empty属性来实现该操作
public bool Empty{get{return count==0;}}
LinkedString类采用动态分配方式为每个结点分配内存空间,程序中可以认为系统提供的可用空间足够大, 因此不必判断基于链表的串是否已满
4)获取或设置串的第i个字符值
将这两个操作通过定义一个读写型索引器成员予以实现,它提供对串对象进行类似于数组的访问
public char this[int i]
{
get
{
if(i>=0&&i<count)
{
StringNode q=head.Next;
int j=0;
while(j<i)
{
j++;
q=q.Next;
}
return q.Item;
}
else
throw new IndexOutOfRangeException("Index Out Of Range Exception in"+this.GetType());
}
set
{
if(i>=0&&i<count)
{
StringNode q=head.Next;
int j=0;
while(j<i)
{
j++;
q=q.Next;
}
q.Item=value;
}
else
throw new IndexOutOfRangeException("Index Out Of Range Exception in"+this.GetType());
}
}
5)连接一个串或一个字符
方法Concat(c)将指定的字符加入当前串对象的尾部,首先构造一个包含字符c的结点,加入串表尾,再封信表尾rear指向新结点,count+1,编码如下:
public void Concat(char c)
{
StringNode q= new StringNode(c);
rear.Next=q;
rear=q;
count++;
}
6)连接两个串
方法Concat(s2)连接两个串对象,依次将参数s2指定的串实例的每个字符连接到本串实例上,也可以通过对运算符"+"重载,连接两个串s1与s2,这样两个串的连接可以表示为s1.Concat(s2),或者表示为LinkeString s3=s1+s2;相应的编码如下:
public void Concat(LinkedString s2)
{
if(s2!=null)
{
StringNode q= new s2.head.Next;
while(q!=null)
{
this.Concat(q.Item);
q=q.Next;
}
}
}
重载运算符"+",连接两个串s1和s2
public static LinkedString operator+(LinkedString s1,LinkedString s2)
{
LinkedString newstr=new LinkedString();
newstr.Concat(s1);
newstr.Concat(s2);
return newstr;
}
7)获取串的子串
Substring方法返回串中从序号i开始的长度为n的子串,本串的长度为this.Length
public LinkedString Substring(int i,int n)
{
int j;
StringNode q;
if(i>=0&&n>0&&(i+n<=this.Length))
{
LinkedString sub= new LinkedString();
j=0;
q=GetNode(i);
while(j<n)
{
sub.Concat(q.Item);
j++;
q=q.Next;
}
return sub;
}
else
return null;
}
GetNode方法获得串的第i个结点,编码如下:
public StringNode GetNode(int i)
{
if(i>0&&i<conut)
{
StringNode q= head.Next;
int j=0;
while(j<i)
{
j++;
q=q.Next;
}
return q;
}
else
throw new IndexOutOfRangeException("Index Out Of Rangge Exception in"+this.GetType());
}
8)查找子串
IndexOf(sub)方法在串中查找与串sub内容相同的子串,若查找成功,返回子串序号,失败则返回-1;子串的查找算法描述编码如下:
public int IndexOf(LinkedString sub)
{
int i=0,j;
bool found=false;
StringNode q;
if(sub.Length==0)
return 0l
while (i<count-sub.Length)
{
j=0;
q=GetNode(i);
while(j<sub.Length&&q.Item==sub[j])
{
j++;
q=q.Next;
}
if(j==sub.Length)
{
found=true;
break;
}
else
i++;
}
if(found)
return i;
else
return -1;
}
9)输出串
Show方法将字符串对象的内容显示在控制台,编码如下:
public void Show()
{
StringNode q=head.Next;
while(q!=null)
{
Console.Write(q.Item);
q=q.Next;
}
Console.WriteLine();
}
ToCharArray方法将字符串对象的内容转化成一个字符数组,而重写(override)的ToSting方法将这里定义的字符串类型转换成C#内在的string类型
对串的插入,删除,替换,逆转等其他操作,都可以调用前面的操作予以实现,还可以参考SequenceString类实现这些操作的方法,
例:LinkedString串类的应用
using System;
namespace StringTest
{
public class LinkedStringTest{
public static void Main(string[] args)
{
char[] a={'H','e','l','l','o'};
LinkedString s1=new LinkedString(a);
s1.Reverse().Show();
char[] b={'W','o','r','l','d'};
LinkedString s2=new LinkedString(b);
LinkedString s0=new LinkedString();
s0.Concat(s1);
s0.Concat(' ');
s0.Concat(s2);
s0.Show();
(s1+s2).Show();
Console.WriteLine("{0}at{1},{2}at{3}of{4}",s1,s0.IndexOf(s1),s2,s0.IndexOf(s2),s0);
LinkedString s3=s0.Substring(s0.IndexOf(s2),s2.Length);
s3.Show();
char[] c={'C','h','i','n','a'};
LinkedString s4=new String2(c);
LinkedString s5=s0.Replace(s2,s4);
s5.Show();
LinkedString s6=s0.Remove(s1.Length+1,s4.Length);
s5.Show();
}
}
}
程序运行结果如下:
Hello
olleH
Hello World
HelloWorld
Hello at 0,Wordl at 6 of Hello World
World
Hello China
Hello