1.字符串抽象数据类型
java
public class test3 {
int length()//返回串长度
char charAt(int i )//返回第i个字符
void setCharAt(int i,char ch)//返回第i个字符为ch
substring(int begin,int end)//返回序号begin至end-1的子串
concat(String s) //返回this与s串连接生成的串
String delete(int begin,int end)//删除从begin到end-1的子串
boolean equals(Object obj)//比较this和obj引用是否相等
int compareTo(String s)//比较this与s串的大小,返回两者的差值
int indexOf(String pattern)//返回首个与模式串pattern匹配的子串序号
void removeAll(String partern)//删除所有与pattern匹配的子串
}
2.字符串和整数相互转换的方法
java
//将整数转化成字符串
int number =123;
String numberAsString=String.valueOf(number);
String numberAsString2=Integer.toString(number);
一般都是使用valueOf方法来进行整数转换,应为valueOf可以接收多种类型的参数,比如(int,long,float,double )等,但是toString只能接收int类型的参数。
java
//把String转换成int型
String s ="123";
int i =Integer.parseInt(s);
3.字符串的顺序存储结构和实现
1.常量字符串
JAVA字符串类主要有String常量字符串类,StringBuffer变量字符串类等。这两种字符串类都采用顺序存储结构,能够存储任意长度的字符串,实现串的基本操作,并且能够识别序号越界等错误。
Strng字符串是一个类,属于引用数据类型。提供构造串对象,求串长度,取字符,求子串,连接串,比较相等,比较大小等操作。若charAt(i),subString(begin,end)方法参数的序号越界,则排除StringIndexOutOfBoundException字符串序号越界异常。
2.String类的特点
- String类是最终类,不能被继承
- String类是以常量串方式存储和实现字符串操作
- 声明字符数组是最终变量,串中各字符是只读的。当构造串对象时,对字符数组进行一次赋值,其后不能更改。String类只提供了取字符操作charAt(i),不提供修改字符,插入串和删除子串操作。
- 构造串,求子串和连接串的操作都是深拷贝,重新申请字符串占用的字符数组,
3.比较串大小
String类声明的compareTo()方法比较两个字符串大小,返回两者之间的差值,分别为以下三种情况:
1.若两字符串s1、s2相等,则s1.compareTo(s2)返回0。
2.若两字符串s1、s2不等,则从头开始依次将两串中的对应字符进行比较,当首次遇到两个不同字符时,s1.compareTo(s2)返回这两个不同字符的差值,
java
s1.charAt(i)-s2.charAt();//i为首次遇到两个不同字符的位置
3.两个字符串s1和s2,若s1是s2的前缀子串,或s2是s1的前缀子串,则s1.compareTo(s2)返回两者长度的差值。
例如,"abcde".compareTo("ab")返回3。
实现compareTo()方法如下,比较两个字符串的大小,返回两者之间的差值。
java
public int compareTo(MyString s){
for(int i =0;i<this.value&&i<s.value.length;++){
if(this.value[i]!=s.value[i])
return this.value[i]-s.value[i];
return this.values.length-s.value.length;
}
}
4.使用String串
使用String串的求子串和连接串操作,实现串的插入,删除功能。
1.插入串
设有String串s1.s2,在s1的i位置插入s2串,返回插入后的串,调用语句如下
java
String s1 ="a",s2 ="xyz";
int i =3;
String s3 =s1.substring(0,i)+s2+s1.substring(i);
2.删除子串
设有串s,删除串s中序号从begin到end-1子串,返回删除后的子串,调用语句如下:
java
int begin=3, end =6;
String s4 =s.substring(0,begin)+s.substring(end);
3.反转字符串的reverse的操作:
java
public class ReverseString {
public static String reverse(String str) {
if (str == null || str.isEmpty()) {
return str;
}
return reverse(str.substring(1)) + str.charAt(0);
}
public static void main(String[] args) {
String originalString = "Hello, World!";
String reversedString = reverse(originalString);
System.out.println("Original String: " + originalString);
System.out.println("Reversed String: " + reversedString);
}
}
//输出:
Original String: Hello, World!
Reversed String: !dlroW ,olleH
这就是使用递归实现的 reverse
方法。它以一种简洁的方式反转字符串,尽管这种方法在处理长字符串时可能会导致性能问题。对于长字符串,使用 StringBuilder
或 StringBuffer
通常更为高效
java
public class ReverseString {
public static void main(String[] args) {
String originalString = "Hello, World!";
String reversedString = new StringBuilder(originalString).reverse().toString();
System.out.println("Original String: " + originalString);
System.out.println("Reversed String: " + reversedString);
}
}
字符的模式串匹配
BF模式算法匹配模式
设有两个串:目标串target和模式串pattern,在target目标串中查找与pattern模式串相等的一个子串并确定改子串位置的操作称为串的模式匹配。两个子串相等是指,各对应字符相同且长度相同。匹配结果有两种:如果target中存在与pattern相等的子串,则匹配成功,获得该匹配子串在target中的位置,否则匹配失败。
KMP模式匹配算法
**KMP算法,全称Knuth-Morris-Pratt字符串搜索算法,是一种高效的字符串搜索算法。**它由Donald Knuth、Vaughan Pratt和James H. Morris在1977年共同发明。KMP算法的核心思想是利用部分匹配表(也称为前缀函数或部分匹配表)来避免在文本中重复搜索已经匹配过的字符。
本篇以主串为ababcabcacbab,模式串为abcac为例
先了解一些基本概念:
- 前缀:除最后一个字符意外,字符串的所有头部子串。
- 后缀:除第一个字符以外,字符串的所有的尾部子串。
目的:需要找到的是每个子串前缀和后缀相等的最长的前缀和后缀长度。
下面是一个例子:
|-------|---------------|----------------|
| 子串 | 前缀 | 后缀 |
| a | 无 | 无 |
| ab | a | b |
| abc | ab,a | bc,c |
| abca | abc,ab,a | bca,ca,a |
| abcac | abca,abc,ab,a | bcac,caca,ac,c |
所以,字符串abcac的最长相等前后端长度是00010,将这个长度写成数组形式,得到对应的部分匹配值[0,0,0,1,0]
模拟匹配过程
我们将模拟abcac 和主串ababcabcacba进行匹配
第一趟:
主串指针len=2,模式串指针i=2时,模式串的c和主串的a匹配失败。已经匹配的字符串是ab,查看前缀表,prefix[1]=0,说明ab前缀和后缀没有相等的,所以下一趟模式串要退回到第一个字符重新比较。
|-----|---|---|-------|---|---|---|---|---|---|---|----|----|
| 下标 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |
| 主串 | a | b | a | b | c | a | b | c | a | c | b | a |
| 模式串 | a | b | c | | | | | | | | | |
第二趟:
在第二次匹配之前,子串匹配索引j直接跳到第一匹配的相同前缀串的最长匹配长度的索引位置上即j=2,主串指针len=6,模式串指针i=4时,模式串的c和主串b匹配失败。已经匹配的字符串是abca前缀和后缀有一个字符相等,所以下一趟模式串要退回到第二个字符开始重新比较,也就是退回到模式串下标为1的位置
|-----|---|---|---|---|---|---|-------|---|---|---|----|----|
| 下标 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |
| 主串 | a | b | a | b | c | a | b | c | a | c | b | a |
| 模式串 | | | a | b | c | a | c | | | | | |
第三趟:
主串指针len=6,模式串指针i=0时,模式串的a和主串b匹配失败。查看前缀表,prefix[0]=0,说明前缀和后缀没有相等的,因为当前与主串比较的就是模式串的第一个字符,所以,将主串移动到下一个位置,与模式串的第一个字符进行比较。
|-----|---|---|---|---|---|---|---|---|---|---|----|----|
| 下标 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |
| 主串 | a | b | a | b | c | a | b | c | a | c | b | a |
| 模式串 | | | | | | a | b | c | a | c | | |
模式串全部比较完成,匹配成功,整个匹配过程中,主串始终没有回退,所以KMP算法的时间复杂度是O(m+n)。
next数组
在KMP算法中,next数组用于记录模式串中每个位子的最长相同的前缀和后缀的长度
next数组计算可以避免在搜索过程中的无效比较,因为如果当前字符不匹配,算法可以利用next数组直接跳到下一个可能匹配位置,而不是回到文本的最早位置。那么next是怎么计算的呢?
以abcabc为例:
|------------|----|---|---|---|---|---|
| j | 0 | 1 | 2 | 3 | 4 | 5 |
| 模式串 | a | b | c | a | b | c |
| 最长相同前后子串长度 | -1 | 0 | 0 | 0 | 1 | 2 |
用abcac为讲解prefix与next之间的关系:
|------|----|---|---|---|---|
| 下标 | 0 | 1 | 2 | 3 | 4 |
| 字符串 | a | b | c | a | c |
| 前缀表 | 0 | 0 | 0 | 1 | 0 |
| next | -1 | 0 | 0 | 0 | 1 |
将前缀整体右移一位,然后将空出来的第一位用-1补充,就得到了next数组。
这样子,当模式串和主串匹配失败的时候,直接查看当前匹配失败的字符的前缀表就可以了,而不是查看匹配失败字符前一个字符的前缀表了。
还是以字符串abcac与主串ababcabcacba匹配为例:
当第一趟匹配失败的时候,主串指针len=2,模式串指针i=2时,模式串的c和主串a匹配失败。查看前缀表,prefix(2)=0,说明ab前缀和后缀没有相等的,所以下一趟模式串要退回到第一个字符重新比较,也就是退到模式串pattern的下标0的位置
|-----|---|---|-------|---|---|---|---|---|---|---|----|----|
| 下标 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |
| 主串 | a | b | a | b | c | a | b | c | a | c | b | a |
| 模式串 | a | b | c | | | | | | | | | |
这里是从-1开始存储和计算next数组的没所以此时next数组的含义是指:当模式串的第i个字符与主串发生匹配失败时,就退回到模式串的next[i]重新与主串进行匹配。当然可以从0开始存储和计算next数组的。
|------|---|---|---|---|---|
| 下标 | 0 | 1 | 2 | 3 | 4 |
| 字符串 | a | b | c | a | c |
| next | 0 | 1 | 1 | 1 | 2 |
KMP算法的代码实现
java
public class KMPAlgorithm {
private int[] computeTemporaryArray(char pattern[]){
int[] lps =new int[pattern.length];//创建一个名为lps的整数数组,其长度与pattern数组相同。这个数组将用于存储最长公共前后缀的长度
int index =0;//用于在lps数组中跟踪当前的最长公共前后缀的长度。
for (int i =1;i<pattern.length;i++){//开始一个循环,从数组的第二个元素开始(索引为1),直到数组的末尾。
if (pattern[i]==pattern[index]){
lps[i]=index+1;
index++;
i++;
}else{
if (index!=0){
index =lps[index-1];
}else{
lps[i]=0;
i++;
}
}
}
return lps;
}
public boolean KMP(char[] text,char[] pattern){
int[] lps =computeTemporaryArray(pattern);
int i =0;
int j =0;
while (i<text.length&&j<pattern.length){
if (text[i]==pattern[i]){
i++;
j++;
}
}
if (j==pattern.length){
return true;
}else if(i<text.length&&text[i]!=pattern[j]){
if (j!=0){
j=lps[j-1];
}else{
i=i+1;
}
}
return false;
}
public static void main(String[] args) {
KMPAlgorithm kmp =new KMPAlgorithm();
String txt ="ABABDABACDABABCABAB";
String pat="ABABCABAB";
char[] text =txt.toCharArray();
char[] pattern=pat.toCharArray();
boolean result =kmp.KMP(text,pattern);
System.out.println("The pattern is found in the text:"+result);
}