赞
踩
概念:二分查找算法只适用于从有序序列中进行查找,比如(数字和字母等),将数列排序后在进行查找。二分查找运行的时间复杂度为O(log2N)
代码实现
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){
left=mid+1;
}else if (arr[mid]>target){
right=mid-1;
}
}
return -1;
}
概念:分治算法的字面解释是“分而治之”,就是把一个复杂的问题分解成两个或更多的相同或相似的子问题,在把子问题分解成更小的子问题,直到最小的子问题可以直接求解。如快速排序,归并排序、汉诺塔
算法步骤:
分解:将原问题分解成若干个规模较小,相互独立,与原问题形式相同的子问题
解决:若子问题规模较小,则可以直接求解,否则递归的解各个子问题
合并:将各个子问题合并为原问题的解
代码实现,以汉诺塔为例
public static void hanoitower(int num ,char a,char b,char c){
//只有一个盘
if(num==1){
System.out.println("第1个盘从"+a+"->"+c);
}else {
//如果n>=2,我们总可以把整体看做两个盘,:最下边的盘A和除下边的所有盘B
//1、先把最上面的所有盘A->B,移动过程会使用到C
hanoitower(num-1,a,c,b);
//2、把最下边的盘A->C
System.out.println("第"+num+"个盘从"+a+"->"+c);
//把B塔中的所有盘B->C,移动过程中使用到a塔
hanoitower(num-1,b,a,c);
}
}
概念:动态规划是一类题目的总称,并不是某个固定的算法,其核心思想和分治算法相似,都是将一个大问题分解成小问题,通过对小问题求解从而得到整体问题的求解。与分治算法不同的是。分治算法分解出的子问题是互不相关的,动态规划分解出的子问题是互相关联的。
动态规划的应用-背包问题
背包问题是指一个给定容量的背包、若干具有一定价值的和重量的物品,如何选择物品放入背包使得包中的物品价值最大。
背包问题又可分为01背包和完全背包(完全背包指的是每种物品都可以有无限键使用),该案例使用的是01背包
图解
物品 | 数量 | 价格 |
---|---|---|
吉他 | 1 | 1500 |
音响 | 4 | 3000 |
电脑 | 3 | 2000 |
物品 | 0磅 | 1磅 | 2磅 | 3磅 | 4磅 |
---|---|---|---|---|---|
0 | 0 | 0 | 0 | 0 | |
吉他 | 0 | 1500 | 1500 | 1500 | 1500 |
音响 | 0 | 1500 | 1500 | 1500 | 3000 |
电脑 | 0 | 1500 | 1500 | 2000 | 35000 |
思路分析
算法的主要思想是利用动态规划来解决,每次遍历到第i个物品,根据w[i]和v[i]来确定是否需要将物品放入到背包中。即对于给定的n个物品,设v[i]、w[i]分别为第i个物品的价值和重量,C为背包的容量,v[i] [j] 表示在前i个物品中能够装入容量为j的背包中的最大值。
v[i] [0]=v[0] [j];表示填入的第一行和第一列均为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]的最大值
代码实现
public class KnapackProblem { public static void main(String[] args){ int[] w={1,4,3};//背包的重量 int[] val={1500,3000,2000};//物品的价值 int m=4;//背包容量 int n=val.length;//物品的个数 //创建二维数组;v[i][j]表示在前i个物品中能够装入容量为j的背包中的最大值 int[][] v=new int[n+1][m+1]; //为了记录放入商品的情况,我们定了一个二维数组path 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++){//不处理第一行,i是从1开始的 for(int j=1;j<v[0].length;j++){ //公式 if(w[i-1]>j){//因为i是从1开始的,所以需要从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]]); //为了得到具体的放置,引入了二维数组path if(v[i-1][j]<val[i-1]+v[i-1][j-w[i-1]]){ path[i][j]=1; v[i][j]=val[i-1]+v[i-1][j-w[i-1]]; }else { v[i][j]=v[i-1][j]; } } } } //输出显示 int i=path.length-1;//行的最大值 int j=path[0].length-1;//列的最大值 while (i>0&&j>0){ if(path[i][j]==1){ System.out.printf("第%d个商品放入到背包\n",i); j-=w[i-1]; } i--; } } }
kmp算法是暴力匹配算法的优化
假设用字符串str2匹配字符创str1
- 如果当前字符串匹配成功(即str1[i]==str2[j]),则i++,j++,继续匹配下一个字符
- 如果不匹配,即str1[i]!=str2[j],令i=i-(j-1),j=0。相当于每次匹配失败时,i回溯,j被置为0
- 采用暴力匹配法会有大量的回溯,每次只移动一位,若不匹配,移动到下一位接着判断,这样的算法浪费大量的时间
代码实现
public static int violenceMatch(String arr1,String arr2){ char[] s1=arr1.toCharArray(); char[] s2=arr2.toCharArray(); int len1=s1.length; int len2=s2.length; int i=0; int j=0; while (i<len1&&j<len2){ if (s1[i]==s2[j]){ i++; j++; }else {//若果没有匹配成功 i=i-(j-1); j=0; } } //判断是否匹配成功 if(j==len2){ return i-j; }else { return -1; } }
概念:KMP算法利用next数组,next数组中保存了模式串中前后最长的公共子序列的长度。每次回溯时。通过next数组找到找到最大后移位置。
kmp算法的核心是跳转表next的实现
public static int[] kmpNext(String dest){ //创建一个next数组保存部分匹配值 int[] next =new int[dest.length()]; next[0]=0;//如果字符串长度为1部分匹配值就是0 for(int i=1,j=0;i<dest.length();i++){ //当dest.chatAt(i)!=des.cahrAt(j),我们需要从next[j-1]获取新的j //直到我们发现有dest.chatAt(i)==des.cahrAt(j)成立才退出 //kmp算法的核心核心点 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; }
Kmp查找算法代码实现
public static int kmpSerach(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; }
概念:贪心算法(贪婪算法)在对问题求解时,在每一步中都选择最好或者最优(即最有利)的选择,从而希望能够导致的结果是最好的或者最优的。
note:贪心算法所得到的结果不一定是最优的结果(有时候会是最优解),但都是对近似(接近)最优解的结果。
案列
集合覆盖问题,假设存在以下付费的广播电台,让广播台的信号可以覆盖所有地区,如何选择最少的广播台,让所有的地区都可以接受到信号。
广播台 | 覆盖地区 |
---|---|
K1 | 北京,上海,天津 |
K2 | 广州,北京,深圳 |
K3 | 成都,上海,杭州 |
K4 | 上海,天津 |
K5 | 杭州,大连 |
算法描述:
- 遍历所有的广播电视台,找到一个覆盖了最多未覆盖地区的电台(此电台可能包含一些已覆盖的地区)
- 将这个电台加入到一个集合中,并把该电台覆盖的地区在下次比较时去掉
- 重复第一步直到覆盖所有的地区
代码实现
public class GreedAlgorithm { public static void main(String[] args){ //创建广播电视台,放入到Map HashMap<String, HashSet<String>> broadcasts=new HashMap<String, HashSet<String>>(); //将各个电视台放入到broadcastszh HashSet<String> hashSet1=new HashSet<>(); hashSet1.add("北京"); hashSet1.add("上海"); hashSet1.add("天津"); HashSet<String> hashSet2=new HashSet<>(); hashSet2.add("广州"); hashSet2.add("北京"); hashSet2.add("深圳"); HashSet<String> hashSet3=new HashSet<>(); hashSet3.add("成都"); hashSet3.add("上海"); hashSet3.add("杭州"); HashSet<String> hashSet4=new HashSet<>(); hashSet4.add("上海"); hashSet4.add("天津"); HashSet<String> hashSet5=new HashSet<>(); hashSet5.add("杭州"); hashSet5.add("大连"); //加入到map broadcasts.put("k1",hashSet1); broadcasts.put("k2",hashSet2); broadcasts.put("k3",hashSet3); broadcasts.put("k4",hashSet4); broadcasts.put("k5",hashSet5); //创建一个allAreas,存放所有的地区 HashSet<String> allAreas=new HashSet<>(); allAreas.add("北京"); allAreas.add("上海"); allAreas.add("天津"); allAreas.add("广州"); allAreas.add("深圳"); allAreas.add("成都"); allAreas.add("杭州"); allAreas.add("大连"); //创建ArrayList,存放选择电台的集合 ArrayList<String> selects=new ArrayList<>(); //定义一个临时的集合,在遍历过程中存放电台覆盖的地区和还没有覆盖地区的交集 HashSet<String> tempSet=new HashSet<String>(); //定义maxKey,保存在一次遍历过程中能够覆盖最大的未覆盖地区的电台key //如果maxKey,不为null,则会加入到selects String maxKey =null; while (allAreas.size()!=0){ //每进行一次循环, maxKey=null; //遍历所有的broadcast,找出对应的key for (String key:broadcasts.keySet()){ //没进行一次for tempSet.clear(); //当前这key能够覆盖的地区 HashSet<String> areas=broadcasts.get(key); tempSet.addAll(areas); //求出tempSet和allAreas的交集,交集会付给tempSet tempSet.retainAll(allAreas); //如果当前这个集合包含的未覆盖的地区数量,比maxKey指向的地区还多,就需要重置maxKey if(tempSet.size()>0&&(maxKey==null||tempSet.size()>broadcasts.get(maxKey).size())){ maxKey=key; } } //maxKey!=null,就应该将maxKey加入selects if(maxKey!=null){ selects.add(maxKey); //将maxKey的指向广播电台的覆盖地区。从allAreas去掉 allAreas.removeAll(broadcasts.get(maxKey)); } } System.out.println(selects); } }
概念
普利姆算法本质上是求最小生成树(Minimum Cost Spanning Tree)MST,也就是在包含n个顶点的连通图中,找出只有(n-1)条边包含的所有n个顶点的连通子图。
最小生成树:给定一个带权的无向连通图,选取一个生成树,使树上所有边上权的总和为最小
无向图
最小生成树
- N个顶点,一定有N-1条边
- 包含全部顶点
- N-1条边都在图中
算法描述
- 设G=(V,E)是连通网,T=(U,D)是最小生成树,V,U是顶点的集合,E,D是边的集合
- 若从顶点u开始构造最小生成树,则从集合v中取出顶点u放入集合U中,并标记顶点visited[u]=1
- 若集合U中的顶点ui与集合V-U中的顶点vi存在边,则寻找这些边中权值最小的边,但不能构成回路,将顶点vj加入集合U中,将边(ui,vj)加入到集合D中,并标记visited[vj]=1
- 重复步骤②,直到U与V相等,即所有顶点都被标记为访问过,此时D中有n-1条边
案列(修路问题)
有7个村庄(A,B,C,D,E,F,G),现在需要修路把7个村庄连通,使得修路的总里程最短
代码实现
public void prime(Mgraph mgraph,int v){ //标记被访问结点的数组 int visited[]=new int[mgraph.vertexs]; int minWeight=10000;//将两节点间的权重设置为较大的值 int h1=-1; int h2=-1; int total=0; visited[v]=1;//将起始结点标记被访问 for (int k=1;k<mgraph.vertexs;k++){//N个顶点的最小生成树含有(N-1)条边 //已经被访问过的结点的循环 for (int i=0;i<mgraph.vertexs;i++){ //与访问过的结点;邻近结点 for (int j=0;j<mgraph.vertexs;j++){ if (visited[i]==1&&visited[j]==0&&mgraph.weight[i][j]<minWeight){ minWeight=mgraph.weight[i][j]; h1=i; h2=j; } } } total+=minWeight; System.out.println("边<"+mgraph.data[h1]+","+mgraph.data[h2]+">权值"+minWeight); //将当前这个结点标记为已访问 visited[h2]=1; minWeight=10000; } System.out.printf("总路程数为"+total); }
定义:克鲁斯卡尔(Kruskal)算法是用来求加权连通图的最小生成树的算法,它是按照权值从小到大的选择选择(n-1)条边并且不构成回路。
案列(公交车路线)
描述:对公交车站点(A、B、C、D、E、F、G)进行连通,并且使得修建公路的总里程数最小。
图解
代码实现
public void krusal() { int index = 0;//表示最后结果数组的索引 int[] ends = new int[edgeNum];//用于保存”已有最小生成树“中的每个顶点在最小生成树中的终点 //创建结果数组,保存最小生成树 Edata[] rets=new Edata[edgeNum]; //获取图中的所有边的集合,一共有12个边 Edata[] edges=getEdges(); System.out.println("图的边的集合="+Arrays.toString(edges)+"共"+edges.length);//12 //按照边的权值进行大小排序 sortEdges(edges); //遍历edges数组,将边添加到最小生成树中时,判断准备加入的边是否形成了回路,如果没有,就加入rests,否则不能加入 for (int i=0;i<edgeNum;i++){ //获取到第i条边的第一个顶点 int p1=getPsiton(edges[i].start); //获取到第i条边的第2个顶点 int p2=getPsiton(edges[i].end); //获取p1这个顶点在已有最小生成树中的终点 int m=getEnd(ends,p1); //获取p2这个顶点在已有最小生成树的终点 int n=getEnd(ends,p2); //是否构成回路 if (m!=n){ ends[m]=n; rets[index++]=edges[i];//有一条边加入到rests数组中 } } System.out.println("最小生成树为="+Arrays.toString(rets)); }
概念:迪杰斯特拉算法是典型的最短路径算法,用于计算一个结点到其它结点的最短路径。它的主要特点是以起始点为中心向外层层扩展(广度优先搜索的思想),直到扩展到终点为止。
算法分析
设置出发顶点为v,顶点集合V(v1,v2,vi…),v到V中顶点的距离构成距离集合Dis,Dis{d1,d2,di…},Dis集合记录着v到图中各个顶点的距离(到自身可以看做是0,v到vi的距离对应为di)
- 从Dis中选择最小的dis并移除Dis集合,同时移出V集合中对应的顶点vi,此时的v到vi为最短路径
- 更新Dis集合,更新规则为:比较v到V集合中顶点的距离值,与v通过vi到V集合中顶点的距离值,保留较小的一个(同时也应该更新顶点的前驱结点为vi,表明是通过vi到达的)
- 重复执行两步步骤,直到最短路径顶点为目标顶点即可结束。
案例(最短路径)
邮差送邮件,从G点开始,分别把邮件送到A、B、C、D、E、F、G六个地点,计算G点到各个地点的最短距离。
代码实现
概念:和迪杰斯特拉一样,弗洛伊德算法也是求给定加权图中顶点间最短路径的算法。但是迪杰斯特拉算法是通过选定被访问的结点,求出从出发访问结点到其它顶点的最短路径。而弗洛伊德算法中的每一个顶点都是出发访问点,所以需要将每一个顶点都看做被访问的顶点,求出每一个顶点到其它顶点的最短路径。
算法分析
- 设置顶点vi到顶点vk的最短路径已知为Lik,顶点vk到顶点vj的最短路径为Lkj,顶点vi到顶点vj的路径为Lij,则vi到vj的最短路径为min((Lik+Lki),Lij),vk的取值为图中的所有顶点,则可获得vi到vj的最短路径
代码实现
public void floyd(){ int len=0;//变量保存距离 //对中间顶点遍历,就是中间顶点的下标 for (int k=0;k<dis.length;k++){ for (int i=0;i<dis.length;i++){ for (int j=0;j<dis.length;j++){ //从i顶点出发,经过k顶点,到达j顶点的距离 len=dis[i][k]+dis[k][j]; if (len<dis[i][j]){ dis[i][j]=len;//更新距离 pre[i][j]=pre[k][j];//更新前驱结点 } } } } }
案列(网上的图解)
采用回溯的方法进行的,首先确定一个点的所有可以走的点,然后选择一个点(该点的下一步可走的点的数量最少),一直进行回溯,直到走完棋盘上的全部带点。
按照马走“日”字的规则进行移动,要求每个方格只进行一次,走遍棋盘上的全部64个方格
代码实现
public static void traversalChessboard(int[][] chessboard,int row,int column,int step){ chessboard[row][column]=step; visited[row*X+column]=true;//标记该位置已被访问 //获取当前位置可以走的下一位置的集合 ArrayList<Point> ps=next(new Point(column,row)); sort(ps); while (!ps.isEmpty()){ Point p=ps.remove(0);//取出下一个可以走的位置 //判断该点是否被访问过 if (!visited[p.y*X+p.x]){//说明还没有被访问过 traversalChessboard(chessboard,p.y,p.x,step+1); } } //判断马儿是否完成了任务,使用step和应该走的步数比较 //如果没有达到数量,则表示没有完成任务,将棋盘置位0 //1、棋盘到目前的位置,仍然没有走完 //2、棋盘处于一个回溯过程 if(step<X*Y&&!finished){ chessboard[row][column] = 0; visited[row*X+column]=false; }else { finished=true; } }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。