分治算法
divide and conquer
将复杂问题分为两个或多个相同或相似的子问题,再把子问题分成更小的子问题,直到最后的子问题可以简单的直接求解,原问题的解即子问题的解的合并。应用:快速排序、归并排序、傅立叶变换、汉诺塔
基本步骤:
分治算法在每一层递归上都有三个步骤
- 分解:将原问题分解为若干个规模较小,互相独立,与原问题形式相同的子问题
- 解决:若子问题规模较小而容易被解决则直接解,否则递归地解各个子问题
- 合并:将各个子问题的解合并为原问题的解
汉诺塔问题
- 如果有一个盘,A - C
- 如果有两个或以上的盘,可以看作是两个盘:最下边的盘,上面的盘
- 先把最上面的盘从 A 移动到 B
- 把最下边的盘从 A 移动到 C
- 把 B 塔的所有盘从 B 移动到 C
package divideandconquer;
public class Hanoitower {
public static void main(String[] args) {
hanoiTower(5,'A','B','C');
}
// hanoitower move
// divide and conquer
public static void hanoiTower(int num,char a, char b, char c){
// if only have one plate
if(num == 1){
System.out.println("the first plate move "+a+" -> "+c);
} else{
// 1. put all upper plates from A-B, use C for help
hanoiTower(num-1,a,c,b);
// 2. put the lowest plate from A-C
System.out.println("the " + num +" move from "+a+" -> "+c);
// 3. move all upper plates from B-C, A for help
hanoiTower(num-1,b,a,c);
}
}
}
动态规划
Dynamic Programming:将大问题划分为小问题进行解决,从而一步步获取最优解的处理算法
与分治算法的不同:适用于动态规划求解的问题,经分解得到子问题往往不是相互独立的(下一子阶段的求解是建立在上一子阶段的解的基础上,进行下一步的求解
可以通过填表进行逐步推导
背包问题
01背包:放入背包的物品不可以重复
完全背包:每件物品都有无限件可用
思路分析
每次遍历到第 i 个物品,根据 w[i](第 i 个商品的重量) 和 v[i](第 i 个商品的价值) 来确定是否需要将该物品放入背包中。C 为背包的容量,令 v/[i]/[j] 表示在前 i 个物品中能够装入容量为 j 的背包中的最大价值
-
v/[i]/[0] = v/[0]/[j] = 0; 表示填入表的第一行和第一列都是0
-
当 w[i] > j 时,v/[i]/[j] = v/[i-1]/[j]; 当准备加入新增的商品的容量大于当前背包的容量时,就直接使用上一个单元格所使用的装入策略
-
当 j >= w[i] 时,v[i]/[j] = max{v[i-1]/[j] , v[i-1]/[j-w[i]] + v[i]}; 当准备加入的新增的商品的容量小于等于当前背包的容量,装入的方式为求最大值:
v[i-1]/[j] :上一个单元格装入的最大值策略
v[i] : 当前商品的价值
v[i-1]/[j-w[i]] :装入 i-1 个商品到剩余空间 j-w[i] 的最大值
package algorithm.dynamic;
public class KnapSackProblem {
public static void main(String[] args) {
int[] w = {1,4,3}; // weight of items
int[] val = {1500,3000,2000}; // value of items
int m = 4; // capacity
int n = val.length; // number of items
int[][] v = new int[n+1][m+1];
// v[i][j] means the max value of pack with a capacity of j among the first i items
// 2-d array for recording items position
int[][] path = new int[n+1][m+1];
// initialize first row and col
for (int i = 0; i < v.length; i++) {
v[i][0] = 0; // set first col to 0
}
for (int i = 0; i < v[0].length; i++) {
v[0][i] = 0; // set first row to 0
}
// dynamic programming
for (int i = 1; i < v.length; i++) { // ignore the first row
for (int j = 1; j < v[0].length; j++) { // ignore the first col
if(w[i-1]>j){ // i is from 1, so w[i] should be w[i-1]
v[i][j] = v[i-1][j];
} else{
// v[i][j] = Math.max(v[i-1][j],val[i-1]+v[i-1][j-w[i-1]]);
// index of val and w are from 0, but in this loop, i is start from 1
// so val[i] need to be val[i-1] and w[i] should be w[i-1]
// if-else
if(v[i-1][j] < val[i-1]+v[i-1][j-w[i-1]]){
v[i][j] = val[i-1]+v[i-1][j-w[i-1]];
path[i][j] = 1;
} else{
v[i][j] = v[i-1][j];
path[i][j] = 1;
}
}
}
}
for (int i = 0; i < v.length; i++) {
for (int j = 0; j < v[0].length; j++) {
System.out.print(v[i][j]+" ");
}
System.out.println();
}
int i = path.length - 1; // max index of row
int j = path[0].length - 1; // max index of col
while(i>0 && j>0){
if(path[i][j] == 1){
System.out.printf("the %d item in bag\n",i);
j -= w[i-1];
}
i--;
}
}
}
字符串匹配算法
字符串匹配问题
暴力匹配算法
假设现在str1匹配到i位置,子串str2匹配到j位置,则:
- 如果当前字符匹配成功(即str1[i] == str2[j]),则 i++,j++,继续匹配下一个字符
- 如果失配(即str1[i] != str2[j]),令 i = i - (j - 1),j = 0 相当于每次匹配失败时,i回溯,j被置为0
- 用暴力方法解决的话就会有大量的回溯,每次只能移动一位,若是不匹配,移动到下一位接着判断,浪费了大量的时间
package algorithm.kmp;
public class ViolenceMatch {
public static void main(String[] args) {
String str1 = "i love you like what you do";
String str2 = "like";
int index = violentMatch(str1,str2);
System.out.println("index "+index);
}
public static int violentMatch(String str1,String str2){
char[] s1 = str1.toCharArray();
char[] s2 = str2.toCharArray();
int s1Len = s1.length;
int s2Len = s2.length;
int i = 0; // index points to s1
int j = 0; // index points to s2
while(i<s1Len && j<s2Len){ // not out of bound when matching
if(s1[i] == s2[j]){ // match
i++;
j++;
} else{ // not match
i = i - (j - 1);
j = 0;
}
}
// whether match successfully
if(j == s2Len){
return i-j;
} else{
return -1;
}
}
}
KMP算法
利用之前判断过的信息,通过一个next数组,保存模式串中前后最长公共子序列的长度,每次回溯时,通过next数组找到,前面匹配过的位置,省去了大量的计算时间
部分匹配表
搜索词 | A | B | C | D | A | B | D |
---|---|---|---|---|---|---|---|
部分匹配值 | 0 | 0 | 0 | 0 | 1 | 2 | 0 |
后移位数公式:移动位数 = 已匹配的字符数 - 对应的部分匹配值
str1 = BBC ABCDAB ABCDABCDABDE
str2 = ABCDABD
部分匹配值:前缀和后缀的最长共有元素的长度
package algorithm.kmp;
import java.util.Arrays;
public class KmpDemo {
public static void main(String[] args) {
String str1 = "BBC ABCDAB ABCDABCDABDE";
String str2 = "ABCDABD";
int[] next = kmpNext(str2);
System.out.println(Arrays.toString(next));
int index = kmpSearch(str1,str2,next);
System.out.println("index "+ index);
}
// kmp search
/**
* kmp search
* @param str1 source string
* @param str2 string to be searched
* @param next partial match table of str2
* @return first index matched, or return -1
*/
public static int kmpSearch(String str1,String str2,int[] next){
// traverse str1
for (int i = 0,j=0; i<str1.length();i++){
// need to handle with str1.charAt(i) != str2.charAt(j)
while(j>0 && str1.charAt(i) != str2.charAt(j)){
j = next[j-1];
}
if(str1.charAt(i) == str2.charAt(j)){
j++;
}
if(j == str2.length()){
return i-j+1;
}
}
return -1;
}
// get partial match table of a string(substring)
public static int[] kmpNext(String dest){
// create an array store partial match value
int[] next = new int[dest.length()];
next[0] = 0; // if length of string is 1, partial match value is 0
for(int i = 1,j = 0; i < dest.length(); i++){
// we need to get new j from next[j-1] when dest.charAt(i) != dest.charAt(j)
// quit when found dest.charAt(i) == dest.charAt(j)
while(j>0 && dest.charAt(i) != dest.charAt(j)){
j = next[j-1];
}
if(dest.charAt(i) == dest.charAt(j)){
j++;
}
next[i] = j;
}
return next;
}
}