KMP算法深刻分析
先上代码
public class KmpDemo {
public static void main(String[] args) {
String str = "ABABCABABA";
String dst = "ABABA";
// System.out.println(Arrays.toString(getStrNext(str)));
System.out.println(kmp(str, dst, getStrNext(dst)));
}
public static int kmp(String str, String dst, int[] next) {
for (int i = 0, j = 0; i < str.length(); i++) {
while(j > 0 && str.charAt(i) != dst.charAt(j)) {
j = next[j - 1];
}
if(str.charAt(i) == dst.charAt(j)) {
j ++;
}
if(j == dst.length()) {
return i - j + 1;
}
}
return -1;
}
public static int[] getStrNext(String str) {
int[] next = new int[str.length()];
next[0] = 0;
for (int i = 1 , j = 0; i < str.length(); i++) {
while(j > 0 && str.charAt(i) != str.charAt(j)) {
j = next[j - 1];
}
if(str.charAt(i) == str.charAt(j)) {
j ++;
}
next[i] = j;
}
return next;
}
}
这篇博客适合已经知道kmp思想但是不是特别清晰的同学,0基础还是别看了叭~~
首先要清楚的认识到我们的需求
**
- 有一个字符串str=“ABABCABABA”,现在我们有需要查找的字串dst=“ABABA”,如果该子串存在于str中,返回开始出现的位置的下标,若不存在返回-1。
**
假如采用暴力匹配算法,过程如下:
- 起始让i=0,指向str,j=0指向dst
- 比较str串中 i=0 的位置的字符和dst串的 j=0 处的字符,如果相等则让i,j都往后移动,当j到达dst的末尾后一位时停止,返回i - j + 1即为子串的出现的开始位置
- 但若某一处出现不匹配时,如:
此时i=4,j=4,但是此处两个字符不等,因此常规思路就是将字串右移一位,从头开始匹配
显然这样的效率是很低的,那么就引出了KMP算法,希望尽可能的移动的多一点,使得效率更高
在下面这个匹配的过程中我们知道str的0,1位置和dst的0,1位置的字符都相同
即:这两个位置相同
那么因为我们是在i=4的时候发现的str.charAt(4) != dst.charAt(4),因此str的0–>3的位置都已经确定了与dst的0–>3的位置元素相同,如果str的0–>i - 1(也就是0–>3)这个字串的0,1位置与2,3位置也相同,那么我们可以直接将dst串起始位置移动到i=2的位置
如图:
红框为i=4之前的str字串记为childStr,0,1位置的AB为childStr的一个前缀,2,3位置的AB为childStr的一个后缀,这也是childStr这个字符串的前缀后缀能匹配的最大长度。
那么因为0,1位置 = 2,3位置,且dst的0,1也等于str的0,1位置,于是dst的0,1位置就等于str的2,3位置,接下来我们就可以把dst移动更多的位置
比较i=4的与j=2处的元素是否相等
不相等于是再重新改动子串的位置,因为仍然想用上述的方法来减少字串的移动次数。
此时j指向的是2,而j=2之前只有两个元素,因此让j = next[j - 1],即j = next[1],j = 0
这个next数组是str字符串的最长前缀后缀匹配长度
那么下面的比较就会是
发现不相等于是i + 1,j不变
那么经过比较到最后就找到了最终的结果
那么next数组要怎么获得呢?
举个例子str1 = “ABABC”
- 第一个i=0的时候没有前缀和后缀串
- 第二个i=1的时候前缀有"A",后缀有"B",公共长度为0
- 第三个i=2的时候前缀有"A",“AB”,后缀有"BA",“A”,公共长度为1
- …
我们所求的next数组是src串的
其中next[0] = 0,str.charAt(1) != str.charAt(0),故next[1] = 0
- str.charAt(2)== str.charAt(0),故next[2] = 1 (小的那个下标是j) (=j + 1)
- str.charAt(3)== str.charAt(1),故next[2] = 2
当执行到i = 4的时候,此时比较j = 2,发现str.charAt(4) != str.charAt(2),因此要让 j 回溯,让j往前一个位置 然后因为next[1] = 0,即没有前缀后缀的匹配,所以j此时为0,又重新开始比较str.charAt(i) ? str.charAt(j)
而当到如下位置时,即j = 4,i = 9,此时因为5-->8的位置与0-->3的位置相等,那么因为next[3] = 2,即2,3位置等于0,1位置,而又2,3位置等于7,8位置,所以0,1位置等于7,8位置,故将j重置为 j = next[3],即 j = 2,进而再比较str.charAt(9) ?str.charAt(2),发现相等,于是next[9] = 3(即j + 1)
大家细品细品!我自己也才今天下午刚大致搞明白,于是分享一下做个记录,能力有限,有误之处还望大家指出~~
背包问题
没有支持double重量的功能,只适用于整数~~
public class PackageDemo {
public static void main(String[] args) {
Shop shop = new Shop(5); // 设定为只有三件商品
shop.addItem(new Item("水", 10, 3));
shop.addItem(new Item("书", 3, 1));
shop.addItem(new Item("食物", 9, 2));
shop.addItem(new Item("夹克", 5, 2));
shop.addItem(new Item("相机", 6, 1));
calculate(shop, 5);
}
public static void calculate(Shop shop, int maxV) {
Item[] items = shop.getItems();
int[][] path = new int[shop.getNums()][maxV]; // 记录放了哪些物品在背包里
double[][] dp = new double[shop.getNums() + 1][maxV + 1];
for (int i = 0; i < dp.length; i++) {
dp[i][0] = 0;
}
for (int i = 0; i < dp[0].length; i++) {
dp[0][i] = 0;
}
for (int i = 1; i < dp.length; i++) { // 遍历行
for (int j = 1; j < dp[0].length; j++) { // j是背包的当前容量
int remain = j - items[i - 1].getWeight(); // 当前商品装的下
double pri = (remain >= 0)?(items[i - 1].getPrice() + dp[i - 1][remain]):0;
// dp[i][j] = Math.max(dp[i - 1][j], pri);
if(dp[i - 1][j] < pri) {
path[i - 1][j - 1] = 1;
dp[i][j] = pri;
}
else {
dp[i][j] = dp[i - 1][j];
}
}
}
printArr(dp, items);
System.out.printf("背包的最大容量为 %d 磅,最大能装走商品的价值为 %8.2f $\n", maxV, dp[shop.getNums()][maxV]);
printRes(path, items, maxV);
}
public static void printRes(int[][] path, Item[] items, int maxV) {
int i = path.length - 1;
int j = path[0].length - 1;
while(i >= 0 && j >=0) {
if(path[i][j] == 1) {
System.out.printf("装走了%s,重量为%d磅,价值为%f$\n", items[i].getName(), items[i].getWeight(), items[i].getPrice());
j = j - items[i].getWeight();
}
i --;
}
}
public static void printArr(double[][] dp, Item[] items) {
for (int i = 1; i < dp.length; i++) {
System.out.printf("%-6s", items[i - 1].getName());
for (int j = 1; j < dp[0].length; j++) {
System.out.printf("%8.2f ", dp[i][j]);
}
System.out.println();
}
}
}
class Shop {
private Item[] items;
private int num; // 商品的数量
public Shop(int n) {
items = new Item[n];
for (int i = 0; i < n; i++) {
items[i] = new Item();
}
}
public void addItem(Item item) {
items[num ++] = item;
}
public int getNums() {
return num;
}
public Item[] getItems() {
return items;
}
}
class Item {
private String name; // 商品的名字
private double price; // 商品的价格
private int weight; // 商品的重量
public Item() {}
public Item(String name, double price, int weight) {
this.name = name;
this.price = price;
this.weight = weight;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
public int getWeight() {
return weight;
}
public void setWeight(int weight) {
this.weight = weight;
}
@Override
public String toString() {
return "Item [name=" + name + ", price=" + price + "]";
}
}