【今日复习之KMP算法及背包问题】

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。

**
假如采用暴力匹配算法,过程如下:

  1. 起始让i=0,指向str,j=0指向dst
  2. 比较str串中 i=0 的位置的字符和dst串的 j=0 处的字符,如果相等则让i,j都往后移动,当j到达dst的末尾后一位时停止,返回i - j + 1即为子串的出现的开始位置
  3. 但若某一处出现不匹配时,如:

在这里插入图片描述
此时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”

  1. 第一个i=0的时候没有前缀和后缀串
  2. 第二个i=1的时候前缀有"A",后缀有"B",公共长度为0
  3. 第三个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 + "]";
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值