二分查找算法
可以用循环或递归
分治算法
把一个复杂的问题分成两个或更多的相同或相似的子问题,再把子问题分成更小的子问题……直到最后子问题可以简单的直接求解,原问题的解即子问题的解的合并。
分治算法可以求解的一些经典问题
二分搜索
大整数乘法
棋盘覆盖
合并排序
快速排序
线性时间选择
最接近点对问题
循环赛日程表
汉诺塔
分治法在每一层递归上都有三个步骤:
分解:将原问题分解为若干个规模较小,相互独立,与原问题形式相同的子问题
解决:若子问题规模较小而容易被解决则直接解,否则递归地解各个子问题
合并:将各个子问题的解合并为原问题的解。
例:面试题 08.06. 汉诺塔问题
我的做法:(不对,,没查出来哪里不对)
public static void hanota(List<Integer> A, List<Integer> B, List<Integer> C) {
move(A,B,C);
}
public static void move(List<Integer> A, List<Integer> B, List<Integer> C){
if(A.size() == 2){
B.add(A.remove(1));
C.add(A.remove(0));
C.add(B.remove(0));
return;
}
move(A.subList(1,A.size()),C,B);
C.add(A.remove(0));
move(B,A,C);
}
别人解法:
public static void hanota(List<Integer> A, List<Integer> B, List<Integer> C) {
move(A.size(), A, B, C);
}
private static void move(int n, List<Integer> a, List<Integer> b, List<Integer> c) {
if (n == 0) return;
move(n - 1, a, c, b);
c.add(a.get(a.size() - 1));
a.remove(a.size() - 1);
move(n - 1, b, a, c);
}
回溯算法
回溯法思路的简单描述是:把问题的解空间转化成了图或者树的结构表示,然后使用深度优先搜索策略进行遍历,遍历的过程中记录和寻找所有可行解或者最优解。
基本思想类同于:
图的深度优先搜索
二叉树的后序遍历
详细的描述则为:
回溯法按深度优先策略搜索问题的解空间树。首先从根节点出发搜索解空间树,当算法搜索至解空间树的某一节点时,先利用剪枝函数判断该节点是否可行(即能得到问题的解)。如果不可行,则跳过对该节点为根的子树的搜索,逐层向其祖先节点回溯;否则,进入该子树,继续按深度优先策略搜索。
回溯法的基本行为是搜索,搜索过程使用剪枝函数来为了避免无效的搜索。剪枝函数包括两类:1. 使用约束函数,剪去不满足约束条件的路径;2.使用限界函数,剪去不能得到最优解的路径。
问题的关键在于如何定义问题的解空间,转化成树(即解空间树)。解空间树分为两种:子集树和排列树。两种在算法结构和思路上大体相同。
- 子集树
所给的问题是从n个元素的集合S中找出满足某种性质的子集时,相应的解空间成为子集树。
如0-1背包问题,从所给重量、价值不同的物品中挑选几个物品放入背包,使得在满足背包不超重的情况下,背包内物品价值最大。它的解空间就是一个典型的子集树。 - 排列树
所给的问题是确定n个元素满足某种性质的排列时,相应的解空间就是排列树。
如旅行售货员问题,一个售货员把几个城市旅行一遍,要求走的路程最小。它的解就是几个城市的排列,解空间就是排列树。
public class Solution {
public static void main(String[] args) {
int[] v = new int[]{6,4,1,5,10,10,10}; //价值
int[] w = new int[]{10,8,5,6,3,3,2}; //重量
int c=18;
int[] bestPath = check(v,w,c);
System.out.println(JsonHelper.toJson(bestPath));
}
public static int[] check(int[] v,int[] w,int c){
int len = v.length;
int[] path = new int[len];
int[] bestPath = new int[len];
int maxV = 0;
int curV = 0;
int curW = 0;
check(v,w,c,curW,path,bestPath,curV,0);
return bestPath;
}
private static int maxV=0;
public static void check(int[] v,int[] w,int c,int curW,int[] path,int[] bestPath,int curV,int index){
if(index>v.length-1){
if(curV>maxV){
maxV = curV;
System.out.println(curV);
System.arraycopy(path,0,bestPath,0,bestPath.length);
System.out.println(JsonHelper.toJson(bestPath));
}
} else {
for(int i=0;i<=1;i++){
if(i==1){
if(curW+w[index]<=c){
path[index] =1;
check(v,w,c,curW+w[index],path,bestPath,curV+v[index],index+1);
clear(path,index);
}
} else {
check(v,w,c,curW,path,bestPath,curV,index+1);
clear(path,index);
}
}
}
}
public static void clear(int[] path,int index){
for(int i=index+1;i<path.length;i++){
path[i]=0;
}
}
}
if(index>v.length-1)
表示树走到叶子结点,,该比较赋值了
for(int i=0;i<=1;i++)
相当于在某个节点,左右子树
if(i==1)时表示放入该物品
if(curW+w[index]<=c)表示该物品可放入,然后继续走
否则不可放入,且尚未走到叶子结点,不做处理,直接抛弃。
if(i==0)不放入该物品,继续下走。
path记录ok的路径,,clear放在check之后,,表示回溯到某个节点,,清除其孩子(已经走过了)的值。
public class Solution {
public static void main(String[] args) {
String s = "cbbbcc";
List<List<String>> lists = partition(s);
System.out.println(lists);
}
public static List<List<String>> partition(String s) {
List<List<String>> result = new ArrayList<>();
List<String> list = new ArrayList<>();
check(s,result,list);
return result;
}
public static void check(String str,List<List<String>> result,List<String> list){
if(str.isEmpty()){
result.add(list) ;
return;
}
int len = str.length();
for(int i=1;i<=len;i++){
String part1 = str.substring(0,i);
String part2 = str.substring(i,len);
if(isHui(part1)){
list.add(part1);
int index = list.size()-1;
List<String> copy = new ArrayList<>(list);
check(part2,result,copy);
list.remove(index);
}
}
}
public static boolean isHui(String s){
if(s.length()==1){
return true;
}
char[] chars = s.toCharArray();
int len = chars.length/2;
for(int i=0;i<len;i++){
if(chars[i] != chars[chars.length-i-1]){
return false;
}
}
return true;
}
}
动态规划算法
动态规划(Dynamic Programming)算法的核心思想是:将大问题划分为小问题进行解决,从而一步步获取最优解的处理算法
动态规划算法与分治算法类似,其基本思想也是将待求解问题分解成若干个子问题,先求解子问题,然后从这些子问题的解得到原问题的解。
与分治法不同的是,适合于用动态规划求解的问题,经分解得到子问题往往不是互相独立的。 ( 即下一个子阶段的求解是建立在上一个子阶段的解的基础上,进行进一步的求解 )
动态规划可以通过填表的方式来逐步推进,得到最优解.
算法的主要思想,利用动态规划来解决。每次遍历到的第i个物品,根据w[i]和v[i]来确定是否需要将该物品放入背包中。即对于给定的n个物品,设v[i]、w[i]分别为第i个物品的价值和重量,C为背包的容量。再令v[i][j]表示在前i个物品中能够装入容量为j的背包中的最大价值。则我们有下面的结果:(1) v[i][0]=v[0][j]=0; //表示 填入表 第一行和第一列是0
(2) 当w[i]> j 时:v[i][j]=v[i-1][j] // 当准备加入新增的商品的容量大于 当前背包的容量时,就直接使用上一个单元格的装入策略
(3) 当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]]}
解法:
public class KnapsackProblem {
public static void main(String[] args) {
// TODO Auto-generated method stub
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];
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[0].length; j++) {
if(w[i-1]> j) {
v[i][j]=v[i-1][j];
} 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];
}
}
}
}
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();
}
System.out.println("============================");
int i = path.length - 1;
int j = path[0].length - 1;
while(i > 0 && j > 0 ) {
if(path[i][j] == 1) {
System.out.printf("放入背包的商品\n", i);
j -= w[i-1];
}
i--;
}
}
}
动态规划:无限背包问题
我的做法:
public int coinChange(int[] coins, int amount) {
//动态规划的特点就是找关系公式,一般是填数组,数组长度amount,,d[i]表示amount=i时所需最小硬币数
//硬币的面额可能是coins中的任意值
// d[i] = Math.min(coins里面遍历j(1+d[i-coins[j]]))
//初始化数据
if(amount==0){
return 0;
}
int[] d = new int[amount+1];
for(int i=1;i<=amount;i++){
d[i]=amount+1;
}
for(int coin:coins){
if(coin<=amount){
d[coin]=1;
}
}
if(amount ==1){
return d[1]==1?1:-1;
}
for(int i=2;i<=amount;i++){
if(d[i] == amount+1){
for(int coin:coins){
if(coin<=i){
d[i] = Math.min(d[i],1+d[i-coin]) ;
}
}
}
}
return d[amount]>=amount+1?-1:d[amount];
}
KMP算法
应用场景-字符串匹配问题
字符串匹配问题:
有一个字符串 str1= “ABCBDCE”,和一个子串 str2=“BDC”
现在要判断 str1 是否含有 str2, 如果存在,就返回第一次出现的位置, 如果没有,则返回-1
KMP算法介绍
KMP方法算法就利用之前判断过信息,通过一个next数组,保存模式串中前后最长公共子序列的长度,每次回溯时,通过next数组找到,前面匹配过的位置,省去了大量的计算时间
参考资料:https://www.cnblogs.com/ZuoAndFutureGirl/p/9028287.html
public class KMPAlgorithm {
public static void main(String[] args) {
// TODO Auto-generated method stub
String str1 = "BBC ABCDAB ABCDABCDABDE";
String str2 = "ABCDABD";
//String str2 = "BBC";
int[] next = kmpNext("ABCDABD"); //[0, 1, 2, 0]
System.out.println("next=" + Arrays.toString(next));
int index = kmpSearch(str1, str2, next);
System.out.println("index=" + index);
}
public static int kmpSearch(String str1, String str2, int[] next) {
for(int i = 0, j = 0; i < str1.length(); i++) {
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) {
int[] next = new int[dest.length()];
next[0] = 0;
for(int i = 1, j = 0; i < dest.length(); i++) {
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;
}
}
贪心算法
贪心算法介绍
贪婪算法(贪心算法)是指在对问题进行求解时,在每一步选择中都采取最好或者最优(即最有利)的选择,从而希望能够导致结果是最好或者最优的算法
贪婪算法所得到的结果不一定是最优的结果(有时候会是最优解),但是都是相对近似(接近)最优解的结果。
假设存在下面需要付费的广播台,以及广播台信号可以覆盖的地区。 如何选择最少的广播台,让所有的地区都可以接收到信号。
public class GreedyAlgorithm {
public static void main(String[] args) {
HashMap<String,HashSet<String>> broadcasts = new HashMap<String, HashSet<String>>();
HashSet<String> allAreas = new HashSet<String>();
ArrayList<String> selects = new ArrayList<String>();
HashSet<String> tempSet = new HashSet<String>();
String maxKey = null;
while(allAreas.size() != 0) {
maxKey = null;
for(String key : broadcasts.keySet()) {
tempSet.clear();
HashSet<String> areas = broadcasts.get(key);
tempSet.addAll(areas);
tempSet.retainAll(allAreas);
if(tempSet.size() > 0 &&
(maxKey == null || tempSet.size() >broadcasts.get(maxKey).size())){
maxKey = key;
}
}
if(maxKey != null) {
selects.add(maxKey);
allAreas.removeAll(broadcasts.get(maxKey));
}
}
}
}
普里姆算法
有胜利乡有7个村庄(A, B, C, D, E, F, G) ,现在需要修路把7个村庄连通
各个村庄的距离用边线表示(权) ,比如 A – B 距离 5公里
问:如何修路保证各个村庄都能连通,并且总的修建公路总里程最短?
package com.atguigu.prim;
import java.util.Arrays;
public class PrimAlgorithm {
public static void main(String[] args) {
char[] data = new char[]{'A','B','C','D','E','F','G'};
int verxs = data.length;
int [][]weight=new int[][]{
{10000,5,7,10000,10000,10000,2},
{5,10000,10000,9,10000,10000,3},
{7,10000,10000,10000,8,10000,10000},
{10000,9,10000,10000,10000,4,10000},
{10000,10000,8,10000,10000,5,4},
{10000,10000,10000,4,5,10000,6},
{2,3,10000,10000,4,6,10000},};
MGraph graph = new MGraph(verxs);
MinTree minTree = new MinTree();
minTree.createGraph(graph, verxs, data, weight);
minTree.showGraph(graph);
minTree.prim(graph, 1);
}
}
class MinTree {
public void createGraph(MGraph graph, int verxs, char data[], int[][] weight) {
int i, j;
for(i = 0; i < verxs; i++) {
graph.data[i] = data[i];
for(j = 0; j < verxs; j++) {
graph.weight[i][j] = weight[i][j];
}
}
}
public void showGraph(MGraph graph) {
for(int[] link: graph.weight) {
System.out.println(Arrays.toString(link));
}
}
public void prim(MGraph graph, int v) {
int visited[] = new int[graph.verxs];
// for(int i =0; i <graph.verxs; i++) {
// visited[i] = 0;
// }
visited[v] = 1;
int h1 = -1;
int h2 = -1;
int minWeight = 10000;
for(int k = 1; k < graph.verxs; k++) {
for(int i = 0; i < graph.verxs; i++) {
for(int j = 0; j< graph.verxs;j++) {
if(visited[i] == 1 && visited[j] == 0 && graph.weight[i][j] < minWeight) {
minWeight = graph.weight[i][j];
h1 = i;
h2 = j;
}
}
}
visited[h2] = 1;
minWeight = 10000;
}
}
}
class MGraph {
int verxs;
char[] data;
int[][] weight;
public MGraph(int verxs) {
this.verxs = verxs;
data = new char[verxs];
weight = new int[verxs][verxs];
}
}
克鲁斯卡尔算法
克鲁斯卡尔(Kruskal)算法,是用来求加权连通图的最小生成树的算法。
基本思想:按照权值从小到大的顺序选择n-1条边,并保证这n-1条边不构成回路
具体做法:首先构造一个只含n个顶点的森林,然后依权值从小到大从连通网中选择边加入到森林中,并使森林中不产生回路,直至森林变成一棵树为止。
public class KruskalCase {
private int edgeNum;
private char[] vertexs;
private int[][] matrix;
private static final int INF = Integer.MAX_VALUE;
public static void main(String[] args) {
char[] vertexs = {'A', 'B', 'C', 'D', 'E', 'F', 'G'};
int matrix[][] = {
/*A*//*B*//*C*//*D*//*E*//*F*//*G*/
/*A*/ { 0, 12, INF, INF, INF, 16, 14},
/*B*/ { 12, 0, 10, INF, INF, 7, INF},
/*C*/ { INF, 10, 0, 3, 5, 6, INF},
/*D*/ { INF, INF, 3, 0, 4, INF, INF},
/*E*/ { INF, INF, 5, 4, 0, 2, 8},
/*F*/ { 16, 7, 6, INF, 2, 0, 9},
/*G*/ { 14, INF, INF, INF, 8, 9, 0}};
KruskalCase kruskalCase = new KruskalCase(vertexs, matrix);
kruskalCase.print();
kruskalCase.kruskal();
}
public KruskalCase(char[] vertexs, int[][] matrix) {
int vlen = vertexs.length;
this.vertexs = new char[vlen];
for(int i = 0; i < vertexs.length; i++) {
this.vertexs[i] = vertexs[i];
}
this.matrix = new int[vlen][vlen];
for(int i = 0; i < vlen; i++) {
for(int j= 0; j < vlen; j++) {
this.matrix[i][j] = matrix[i][j];
}
}
for(int i =0; i < vlen; i++) {
for(int j = i+1; j < vlen; j++) {
if(this.matrix[i][j] != INF) {
edgeNum++;
}
}
}
}
public void kruskal() {
int index = 0;
int[] ends = new int[edgeNum];
EData[] rets = new EData[edgeNum];
EData[] edges = getEdges();
sortEdges(edges);
for(int i=0; i < edgeNum; i++) {
int p1 = getPosition(edges[i].start); //p1=4
int p2 = getPosition(edges[i].end); //p2 = 5
int m = getEnd(ends, p1); //m = 4
int n = getEnd(ends, p2); // n = 5
if(m != n) {
ends[m] = n;
rets[index++] = edges[i];
}
}
for(int i = 0; i < index; i++) {
System.out.println(rets[i]);
}
}
public void print() {
for(int i = 0; i < vertexs.length; i++) {
for(int j=0; j < vertexs.length; j++) {
System.out.printf("%12d", matrix[i][j]);
}
}
}
private void sortEdges(EData[] edges) {
for(int i = 0; i < edges.length - 1; i++) {
for(int j = 0; j < edges.length - 1 - i; j++) {
if(edges[j].weight > edges[j+1].weight) {
EData tmp = edges[j];
edges[j] = edges[j+1];
edges[j+1] = tmp;
}
}
}
}
private int getPosition(char ch) {
for(int i = 0; i < vertexs.length; i++) {
if(vertexs[i] == ch) {
return i;
}
}
return -1;
}
private EData[] getEdges() {
int index = 0;
EData[] edges = new EData[edgeNum];
for(int i = 0; i < vertexs.length; i++) {
for(int j=i+1; j <vertexs.length; j++) {
if(matrix[i][j] != INF) {
edges[index++] = new EData(vertexs[i], vertexs[j], matrix[i][j]);
}
}
}
return edges;
}
private int getEnd(int[] ends, int i) { // i = 4 [0,0,0,0,5,0,0,0,0,0,0,0]
while(ends[i] != 0) {
i = ends[i];
}
return i;
}
}
class EData {
char start;
int weight;
public EData(char start, char end, int weight) {
this.start = start;
this.end = end;
this.weight = weight;
}
@Override
public String toString() {
return "EData [<" + start + ", " + end + ">= " + weight + "]";
}
}
深度优先搜索
广度优先搜索
贪心算法
找2~3个题目