写在前面
二分查找算法(非递归)
- 只适用于从有序队列中进行查找
- 核心是修改mid的取值
/**
* @param arr 待查找的数组
* @param target 目标数
* @return 返回对应下标,-1为不存在
*/
public static int binarySearch(int[] arr, int target) {
int left = 0;
int right = arr.length - 1;
int mid;
while (left <= right) {
mid = (left + right) / 2;
if (arr[mid] == target) {
return mid;
} else if (arr[mid] > target) {
right = mid - 1;
mid = (left + right) / 2;
} else {
left = mid + 1;
mid = (left + right) / 2;
}
}
return -1;
}
汉诺塔(分治算法)
- 分治算法,把一个复杂的问题分成两个或更多的子问题,再把子问题分成更小的小问题,再将所有小问题的解合并,就是原题的解
- a,b,c->A塔,B塔,C塔;num为数量
- 3个盘举例
abc为形参
例如如果要移动3个盘,上面两个盘的目标就是移动到B塔,进入第二层,这个递归中abc->ACB
第一步,进入第三层,此时abc->ABC,第一个盘A->C
第二步,跳出第三层,此时abc->ACB,第二个盘A->B
第三步,进入第三层,此时abc->CAB,第一个盘C->B
第四步,跳出第三层,跳出第二层,回到第一层,此时abc->ABC,第三个盘A->C
第五步,进入第二层,B的两个盘的目标是移动到C塔,进入递归后abc->BAC
第六步,进入第三层,此时abc->BCA,第一个盘B->A
第七步,跳出第三层,回到第二层,此时abc->BAC,第二个盘B->C
第八步,进入第三层,此时abc->ABC,第一个盘A->C,完成
第九步,跳出三二一层
public static void hanoitower(int num, char a, char b, char c) {
//只有一个
if (num == 1) {
System.out.println("第1个盘从 " + a + "->" + c);
} else {
//如果有n>=2的情况,看作两个盘 1最下面一个盘 2上面的所有盘
//把上面的所有盘A->B,移动的过程使用C,对于这个目标来说,B为目标塔,C为中间塔
hanoitower(num - 1, a, c, b);
//把最下面的盘A->C
System.out.println("第" + num + "个盘从 " + a + "->" + c);
//把B塔所有盘从B->C,移动过程使用A,对于这个目标来说,C塔为目标,A塔为中间塔
hanoitower(num -1 , b, a, c);
}
}
小结
塔的转换,举例如果有四个盘,最上面的三个盘为一组最终目标是B塔,但是此时对于最上面的两个盘的阶段目标却是C塔,因为只有将最上面的两个盘移动到了C塔,才能保证第三个盘到B塔,那么也就意味着前两个盘要先从A->C,再从C->B;相对应的,如果上面两个盘目标为C,那么第一个盘的目标为B,这也就说明第一步递归时,要交换
bc的次序hanoitower(num - 1, a, c, b);进入最后一层递归,完成第一个盘的移动后,就要移动第二个盘A->C,并且还要再将第一个盘B->C,完成第二个盘的目标,此时hanoitower(num -1 , b, a, c),也就完成了四个盘的例子中,最上面两个盘的第一阶段任务A->C
根据上面一段话,可以总结,汉诺塔的分解,就是对于不同盘的数量,每个盘有不同的任务,针对不同的盘,对ABC塔有不同的顺序,就比如说,四个盘中的最上面两个盘的移动,第一阶段中A->C时,顺序为ABC,B是中间塔,让第一个盘先移动到B;但是第二个阶段,当顶部三个盘全部到达B后,顶部两个盘需要从B->A,顺序为BCA,第一个盘需要先到C,第二个盘才能到A;但是,对于三个盘中最上面两个盘的移动,上面两个盘,第一阶段为A->B,顺序为ACB,C是中间塔,需要让第一个盘先移动到C;第二阶段B->C,对于B上的两个盘来说,顺序为BAC,A为中间塔,第一个盘需要先从A,第二个盘才能到C
以上的具体步骤,其实并不能把这两个递归过程讲清楚,但是可以肯定的是,对于不同数量的盘,和不同层的盘来说,目标不同,塔也不同,通过传递形参的方式调整不同盘的对应的顺序
将一堆盘分成两块,第一块为上面的所有盘,第二块为最下面的一个盘,第一块作为形参来说,任务就是
a->b,b->c,而最后一块的任务就是a->c,那么通过不断的递归,对于不同的盘abc自然会对应不同的塔
背包问题(动态规划)
- 动态规划,将待求解的问题分解成若干个问题,先求子问题,下一个子阶段的解是建立在上一个子阶段的解的基础上;用填表的方式,逐步推进
int[] w = {1,4,3}; //重量
int[] val = {1500, 3000, 2000}; //物品
int m = 4; //背包重量
int n = val.length; //物品的数两
//二维数组,用填表推导
int[][] v = new int[n+1][m+1];
//记录商品的情况
int[][] path = new int[n+1][m+1];
//v[0][j] = 0 v[i][0] = 0
for (int i = 0; i < v.length; i++) {
v[i][0] = 0;
}
for (int i = 0; i < v[0].length; i++) {
v[0][i] = 0;
}
//动态规划
for (int i = 1; i < v.length; i++) {
for (int j = 1; j < v[i].length; j++) {
if (w[i-1] > j) { //w的下标为从0开始,而填表的部分是从1开始
//如果物品重量大于当前背包重量,表明这个物品放不进这个重量的背包,直接获取上面的价值
v[i][j] = v[i-1][j];
} else { //如果添加的部分重量 w[i-1] <= j 表示可以填多个
//val下标从0开始,填表的部分从1开始
//上一个物品的同重量的价值 vs 新物品的价值+剩下背包重量的上一个物品的价值之和
// v[i-1][j] val[i-1] + v[i-1][j-w[i-1]]
//举例说明,如果此时 物品重量为3 背包重量为4
//那么就有两种可能性,第一种上一轮为背包重量为4的价值
//第二种,物品重量为3的价值+背包重量为1的价值之和,重量为1指的是上一轮的这个重量的价值
//因为,每次计算这个位置,都会进行比较,就能明确这个位置是最大的价值
//如果是新物品的价值+剩下背包重量的上一个物品的价值之和,那就是新的放法,需要标记
//v[i][j] = Math.max(v[i-1][j], val[i-1] + v[i-1][j-w[i-1]]);
//为了记录背包情况,使用if-else处理
if (v[i-1][j] > (val[i-1] + v[i-1][j-w[i-1]])) {
v[i][j] = v[i-1][j];
} else {
//此时说明这个重量最大价值进行更新,且是新放的方式,不是直接获取相同重量的上一个放法
v[i][j] = val[i-1] + v[i-1][j-w[i-1]];
path[i][j] = 1;
}
}
}
}
for (int i = 0; i < v.length; i++) {
for (int j = 0; j < v[i].length; j++) {
System.out.print(v[i][j] + " ");
}
System.out.println();
}
for (int i = 0; i < path.length; i++) {
for (int j = 0; j < path[i].length; j++) {
System.out.print(path[i][j] + " ");
}
System.out.println();
}
int i = path.length - 1;
int j = path[0].length - 1;
//倒叙遍历
while (i > 0 && j > 0) {
if (path[i][j] == 1) {
//先遍历最大重量时,获取最大价值的物品放法
//例如,这时背包重量为4,放物品3+1时,更新了最大价值
//倒叙扫描,发现了path[3][4]==1,那么说明物品3被放进了背包
//但是此时还剩下背包重量1,那么就要再遍历背包重量1
//找到背包重量1时,发现path[1][1],发现背包重量为1是,物品1被放进了背包,且是更新了背包重量为1的放法
System.out.println(i);
j -= w[i-1];
//由于这个物品最新的价值放法已经找到,因此可以排除这一行,直接在上两行找
}
i --;
}