笔记依照[尚硅谷·数据结构]
二分查找
二分查找算法(非递归)
/**
* @param arr 待查找的数组 是升序排列的
* @param target 需要查找的数
* @return 返回对应的下标 , -1就是没有找到
*/
public static int binarySearch(int[] arr, int target) {
int left = 0;
int right = arr.length - 1;
while (left <= right) {
int mid = (left + right) / 2;
if (arr[mid] == target) {
return mid;
} else if (arr[mid] > target) {
right = mid - 1; //需要向左边查找
} else {
left = mid + 1; //需要向右边查找
}
}
return -1;
}
二分查找算法(递归)
/**
* @param arr 待查找的数组 是升序排列的
* @param target 需要查找的数
* @param left
* @param right
* @return 返回对应的下标 , -1就是没有找到
*/
public static int binarySearchRecursion(int[] arr, int target, int left, int right) {
while (left <= right) {
int mid = (left + right) / 2;
if (arr[mid] == target) {
return mid;
} else if (arr[mid] < target) {
return binarySearchRecursion(arr, target, mid + 1, right);
} else {
return binarySearchRecursion(arr, target, left, mid - 1);
}
}
return -1;
}
分治法
就是把一个复杂的问题分成两个或更多的相同或相似的子问题,再把子问题分成更小的子问题……直到最后子问题可以简单的直接求解,原问题的解即子问题的解的合并。这个技巧是很多高效算法的基础,如排序算法(快速排序,归并排序),傅立叶变换(快速傅立叶变换)……
汉诺塔
对于汉诺塔问题,当只移动一个圆盘时,直接将圆盘从 A 针移动到 C 针。若移动的圆盘为 n(n>1),则分成几步走:把 (n-1) 个圆盘从 A 针移动到 B 针(借助 C 针);A 针上的最后一个圆盘移动到 C 针;B 针上的 (n-1) 个圆盘移动到 C 针(借助 A 针)。每做一遍,移动的圆盘少一个,逐次递减,最后当 n 为 1 时,完成整个移动过程。
package cn.luchen.divideandconquer;
public class Hanoitower {
public static void main(String[] args) {
hanoiTowe(3, "A", "B", "C");
}
/**
* @param plates 盘子的数量
* @param a 盘子的起始位置
* @param b 辅助位置
* @param c 目标位置
*/
public static void hanoiTowe(int plates, String a, String b, String c) {
//如果只有一个盘
if (plates == 1) {
System.out.println("第1个盘从:" + a + " -> " + c);
} else {
//先把上半部的盘移到B
hanoiTowe(plates - 1, a, c, b);
System.out.println("第" + plates + "个盘从:" + a + " -> " + c);
hanoiTowe(plates - 1, b, a, c);
}
}
}
动态规划算法
定义
动态规划算法是通过拆分问题,定义问题状态和状态之间的关系,使得问题能够以递推(或者说分治)的方式去解决。
动态规划算法的基本思想与分治法类似,也是将待求解的问题分解为若干个子问题(阶段),按顺序求解子阶段,前一子问题的解,为后一子问题的求解提供了有用的信息。在求解任一子问题时,列出各种可能的局部解,通过决策保留那些有可能达到最优的局部解,丢弃其他局部解。依次解决各子问题,最后一个子问题就是初始问题的解。
基本思想和策略
由于动态规划解决的问题多数有重叠子问题这个特点,为减少重复计算,对每一个子问题只解一次,将其不同阶段的不同状态保存在一个二维数组中。
(来自百度百科)
说实话,没有动态规划的基础很难看懂,但是也能从中看出一些信息,下面我翻译成人话:
首先是拆分问题,我的理解就是根据问题的可能性把问题划分成一步一步这样就可以通过递推或者递归来实现.
关键就是这个步骤,动态规划有一类问题就是从后往前推到,有时候我们很容易知道:如果只有一种情况时,最佳的选择应该怎么做.然后根据这个最佳选择往前一步推导,得到前一步的最佳选择
然后就是定义问题状态和状态之间的关系,我的理解是前面拆分的步骤之间的关系,用一种量化的形式表现出来,类似于高中学的推导公式,因为这种式子很容易用程序写出来,也可以说对程序比较亲和(也就是最后所说的状态转移方程式)
我们再来看定义的下面的两段,我的理解是比如我们找到最优解,我们应该讲最优解保存下来,为了往前推导时能够使用前一步的最优解,在这个过程中难免有一些相比于最优解差的解,此时我们应该放弃,只保存最优解,这样我们每一次都把最优解保存了下来,大大降低了时间复杂度
背包问题
根据维基百科,背包问题(Knapsack problem)是一种组合优化的NP完全(NP-Complete,NPC)问题。问题可以描述为:给定一组物品,每种物品都有自己的重量和价格,在限定的总重量内,我们如何选择,才能使得物品的总价格最高。NPC问题是没有多项式时间复杂度的解法的,但是利用动态规划,我们可以以伪多项式时间复杂度求解背包问题。
package cn.luchen.dynamic;
public class KnapsackProblem {
public static void main(String[] args) {
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];
//在本程序中可以不处理 第一行 第一列 因为默认就是0
for (int i = 0; i < v.length; i++) {
v[i][0] = 0; // 将第一列设置成0
}
for (int i = 0; i < v[0].length; i++) {
v[0][i] = 0; //将第一行的设置成0
}
//根据前面得到的公式动态处理
for (int i = 1; i < v.length; i++) {
for (int j = 1; j < v[0].length; j++) {
//公式
if (w[i - 1] > j) {
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]]);
//为了记录商品存放到背包的情况 我们不能直接使用max公式 需要使用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
path[i][j] = 1;
} else {
v[i][j] = v[i - 1][j];
}
}
}
}
//输出v
for (int[] i : v) {
for (int j : i) {
System.out.print(j + " ");
}
System.out.println();
}
//输出最后是放入的哪些商品
//遍历path 但是这样的输出会把所有的遍历的情况都得到 我们只需要最后的一次的遍历情况
/*for (int i = 0; i < path.length; i++) {
for (int j = 0; j < path[i].length; j++) {
if (path[i][j] == 1)
System.out.printf("第%d个商品放入到背包\n", i);
}
}*/
int i = path.length - 1; // 行的最大下标
int j = path[0].length - 1; //列的最大下标
while (i > 0 && j > 0) { // 从path数组的最后开始寻找
if (path[i][j] == 1) {
System.out.printf("第%d个商品放入到背包\n", i);
j -= w[i - 1];
}
i--;
}
}
}
KMP
暴力匹配算法
package cn.luchen.kmp;
public class ViolenceMatch {
public static void main(String[] args) {
//测试暴力匹配算法
String str1 = "硅硅谷 尚硅谷你尚谷 尚硅谷 你尚硅谷你尚硅你好";
String str2 = "尚硅谷你尚硅你";
System.out.println(ViolenceMatch(str1, str2));
}
//暴力匹配算法实现
public static int ViolenceMatch(String str1, String str2) {
char[] s1 = str1.toCharArray();
char[] s2 = str2.toCharArray();
int s1Len = s1.length;
int s2Len = s2.length;
int i = 0, j = 0;
while (i < s1Len && j < s2Len) {
if (s1[i] == s2[j]) {
j++;
i++;
} else {
i = i - j + 1;
j = 0;
}
}
//判断是否匹配成功
if (j == s2Len) {
return i - j;
} else {
return -1;
}
}
}
KMP
package cn.luchen.kmp;
public class KMPAlgorithm {
public static void main(String[] args) {
String str1 = "BBC ABCDAB ABCDABCDABDE";
String str2 = "ABCDABD";
int[] next = kmpNext("AA");
}
/**
* 写出KMP的搜索算法
*
* @param str1 源字符串
* @param str2 子串
* @param next 子串对应的部分匹配表
* @return 如果是-1就是没有匹配到 , 否则返回第一个匹配的位置
*/
public static int kmpSearch(String str1, String str2, int[] next) {
//遍历
for (int i = 0, j = 0; i < str1.length(); i++) {
//需要处理str1.charAt(i) != str2.charAt(j)的情况 去调整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;
}
//获取到一个字符串(子串)的部分匹配值表
public static int[] kmpNext(String dest) {
//创建一个next数组保存部分匹配值
int[] next = new int[dest.length()];
next[0] = 0; //如果dest的长度为1他的部分匹配值就是0;
for (int i = 1, j = 0; i < dest.length(); i++) {
//当dest.charAt(i) != dest.charAt(j)的时候 我们需要从next[j-1]获取新的j 同时要满足直到我们发现有dest.charAt(i) == dest.charAt(j)成立的时候退出
while (j > 0 && dest.charAt(i) != dest.charAt(j)) {
j = next[j - 1];
}
//dest.charAt(i) == dest.charAt(j)满足的时候, 部分匹配值 就是 +1
if (dest.charAt(i) == dest.charAt(j)) {
j++;
}
next[i] = j;
}
return next;
}
}