一、二分查找(非递归)
1、非递归二分查找概述
- 二分查找法只适用于从有序的数列中进行查找(比如数字和字母等),将数列排序后再进行查找
- 二分查找法的运行时间为对数时间 O(㏒₂n),即查找到需要的目标位置最多只需要 ㏒₂n 步
2、非递归二分查找代码实现
package work.rexhao.search;
/**
* 非递归二分查找
*/
public class BinarySearchNoRecursion {
public static void main(String[] args) {
int[] num = new int[]{
10, 12, 23, 32, 45, 64, 65, 69, 83};
System.out.println(binarySearchNoRecursion(10, num));
System.out.println(binarySearchNoRecursion(65, num));
System.out.println(binarySearchNoRecursion(99, num));
}
public static int binarySearchNoRecursion(int target, int[] num) {
int left = 0;
int right = num.length - 1;
int mid;
while (left <= right) {
mid = (left + right) / 2;
if (target == num[mid]) {
return mid;
} else if (target > num[mid]) {
// 目标值比中间值大 --> 向右找
left = mid + 1;
} else {
// 目标值比中间值小 --> 向左找
right = mid - 1;
}
}
return -1;
}
}
3、补:递归二分查找代码实现
package work.rexhao.search;
import java.util.Arrays;
/**
* 二分查找
*/
public class BinarySearch {
public static void main(String[] args) {
int[] num = new int[]{
10, 12, 23, 32, 45, 64, 65, 69, 83};
System.out.println(Arrays.toString(num));
System.out.println(binarySearch(num, 69, 0, num.length - 1));
System.out.println(binarySearch(num, 99, 0, num.length - 1));
}
public static int binarySearch(int[] num, int target, int left, int right) {
if (left > right) {
return -1;
}
if (num[(left + right) / 2] == target) {
return (left + right) / 2;
} else if (num[(left + right) / 2] > target) {
return binarySearch(num, target, left, (left + right) / 2);
} else {
return binarySearch(num, target, (left + right) / 2 + 1, right);
}
}
}
二、分治算法(DC)
1、分治算法介绍
分治法(Divide-and-Conquer§)是一种很重要的算法。
字面上的解释是“分而治之”,就是把一个复杂的问题分成两个或更多的相同或相似的子问题,再把子问题分成更小的子问题……直到最后子问题可以简单的直接求解,原问题的解即子问题的解的合并。
2、分治算法的基本步骤
- 分解:将原问题分解为若干个规模较小,相互独立,与原问题形式相同的子问题
- 解决:若子问题规模较小而容易被解决则直接解,否则递归地解各个子问题
- 合并:将各个子问题的解合并为原问题的解
3、分治算法设计模式
if |p| <= n0
then return(ADHOC(P))
// 将p分解为较小的子问题p1,P2,…,Pk
for if <- 1 to k
do yi <- DC递归解决pi
T <- MERGE(y1,x2,…,vk) 合并子问题
return(T)
- 其中
|P|
表示问题p的规模 n0
为一國值,表示当问题P的规模不超过no时,问题已容易直接解出,不必再继续分解。ADHOC(P)
是该分治法中的基本子算法,用于直接解小规模的问题P。因此,当P的规模不超过n0
时直接用算法ADHOC(P)
求解。- 算法
MERGE(y1,x2,…,vk)
是该分治法中的合并子算法,用于将P的子问题p1,p2,…,pk
的相应的解y1,x2,…,vk
合并为P的解。
4、汉诺塔代码实现
package work.rexhao.algorithm.dc;
import java.util.Collections;
/**
* 汉诺塔
*/
public class hanoiDemo {
static int count = 0;
public static void main(String[] args) {
hanoiTower(5, 'A', 'B', 'C');
}
private static void hanoiTower(int num, char a, char b, char c) {
// 如果只有一个盘
if (num == 1) {
System.out.println(++count + " : 第 1 个盘从 " + a + "->" + c);
} else {
// 如果我们有 n >= 2 情况,我们总是可以看做是两个盘 1.最下边的一个盘 2. 上面的所有盘
// 1. 先把 最上面的所有盘 A->B, 移动过程会使用到 c
hanoiTower(num - 1, a, c, b);
// 2. 把最下边的盘 A->C
System.out.println(++count + " : 第 " + num + " 个盘从 " + a + "->" + c);
// 3. 把 B 塔的所有盘 从 B->C , 移动过程使用到 a 塔
hanoiTower(num - 1, b, a, c);
}
}
}
三、动态规划(DP)
1、动态规划算法介绍
- 动态规划(Dynamic Programming)算法的核心思想是:将大问题划分为小问题进行解决,从而一步步获取最优解的处理算法
- 动态规划算法与分治算法类似,其基本思想也是将待求解问题分解成若干个子问题,先求解子问题,然后从这些子问题的解得到原问题的解。
- 与分治法不同的是,适合于用动态规划求解的问题,经分解得到子问题往往不是互相独立的。(即下一个子阶段的求解是建立在上一个子阶段的解的基础上,进行进一步的求解)
- 动态规划可以通过填表的方式来逐步推进,得到最优解。
2、背包问题
1)背包问题概述
有一个背包,容量为4kg,现有如下物品。要求达到的目标为装入的背包的总价值最大,并且重量不超出装入的物品不能重复。
物品 | 重量 | 价值 |
---|---|---|
G | 1 | 15 |
S | 4 | 30 |
L | 3 | 20 |
2)背包问题思路分析
背包问题主要是指一个给定容量的背包、若干具有一定价值和重量的物品,如何选择物品放入背包使物品的价值最大。其中又分 01 背包和完全背包(完全背包指的是:每种物品都有无限件可用,且无限背包可以转化为 01 背包)
算法的主要思想,利用动态规划来解决。每次遍历到的第 i 个物品,根据 w[i]和 v[i]来确定是否需要将该物品放入背包中。即对于给定的 n 个物品,设 v[i]、w[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]+v[i-1][j-w[i]]}
当准备加入的新增的商品的容量小于等于当前背包的容量, - 装入的方式:
v[i-1][j]
: 就是上一个单元格的装入的最大值v[i]
:表示当前商品的价值v[i-1][j-w[i]]
: 装入 i-1 商品,到剩余空间 j-w[i]的最大值- 当
j>=w[i]
:v[i][j]=max{v[i-1][j], v[i]+v[i-1][j-w[i]]} :
3)背包问题代码实现
package work.rexhao.algorithm.dynamic;
/**
* 01背包问题
*/
public class KnapsackProblem {
public static void main(String[] args) {
// 1. 定义二维数组
int knapsackSize = 4; // 背包容量
int goodsType = 3; // 物品种类
int[][] value = new int[knapsackSize + 1][goodsType + 1]; // dp价值表
int[] goodsSize = {
0, 1, 4, 3}; // 每个物品的大小
int[] goodsValue = {
0, 15, 30, 20}; // 每个物品的价值
// 2. 第一行为空(空背包)且第一列为空(背包容量为0)
// 3. 遍历表
for (int i = 1; i < knapsackSize + 1; i++) {
// i:背包容量
for (int j = 1; j < goodsType + 1; j++) {
// j:物品类型
knapsackProblem(knapsackSize, i, j, value, goodsSize, goodsValue);
}
}
// 3.1 打印表
for (int i = 0; i < goodsType + 1; i++) {
for (int j = 0; j < knapsackSize + 1; j++) {
System.out.print(value[j][i] + "\t");
}
System.out.println();
}
System.out.println("--------------------");
// 4. 输出答案
int ans = 0;
for (int[] ints : value) {
for (int i : ints) {
ans = Math.max(ans, i);
}
}
System.out.println("总价值最大为: " + ans);
}
private static void knapsackProblem(int knapsackSize, int i, int j, int[][] value, int[] goodsSize, int[] goodsValue) {
if (goodsSize[j] == i) {
// 放一个正好放满 --> 放(这个或上一个品种)中价值更高的
value[i][j] = Math.max(goodsValue[j], value[i