可以使用非递归和递归的方式进行,代码的区别在于while()
内的条件。
调用递归
public static ArrayList<Integer> binarySearch2(int[] arr, int left, int right, int findVal){
int mid = (left+right)/2;
int midVal = arr[mid];
if (left>right){
return new ArrayList<Integer>();
}
if (findVal>midVal){
//向右递归
return binarySearch2(arr,mid+1,right,findVal);
}else if (findVal<midVal){
return binarySearch2(arr,left,mid-1,findVal);
}else {
ArrayList<Integer> resIndexList = new ArrayList<>();
int temp = mid-1;
//左
while (true){
if (temp<0 || arr[temp]!=findVal){
break;
}
resIndexList.add(temp);
temp--;
}
//中
resIndexList.add(mid);
//右
temp = mid+1;
while (true){
if (temp>arr.length-1 || arr[temp]!=findVal){
break;
}
resIndexList.add(temp);
temp++;
}
return resIndexList;
}
}
不调用递归
public static int binarySearch(int[] arr,int targetVal){
int left=0;
int right = arr.length-1;
while (left<=right){
int mid = (left+right)/2;
if (targetVal==arr[mid]){
return mid;
}else if (arr[mid]>targetVal){
right=mid-1;
}else {
left=mid+1;
}
}
return -1;
}
就是把一个复杂的问题分成两个或者更多相同、相似的子问题。(汉诺塔,移动)
n=1
的时候,直接移动。
n>=2
的时候,就看做是两个盘,1是最下边的盘。2是它上面的盘。
先把上面的移动到A->B,再最下边到C,把B->C.
//汉诺塔的移动方法
public static void hanmoTower(int num,char a,char b,char c){
if (num==1){
System.out.println("第一个盘从"+a+"->"+c);
}else {
//看成最下边一个和上边所有的算一个
//1.先把最上边的所所有盘A-->B,移动过程会使用到c
hanmoTower(num - 1, a, c, b);
//2.把最下边的盘A--C
System.out.println("第" + num + "个 从" + a + "->" + c);
//3.在把所有盘从B-C
hanmoTower(num - 1, b, a, c);
}
}
将大问题划分为小问题,从而一步步获取最优解决的处理算法。
与分治算法不同的是,子问题不是相互独立的。
背包问题,【无限背包可以转为01背包】
第i个物品重量和价值–>w[i]和v[i]
。
二维数组V[i][j]
–>前i号物品放入j最大的容量的价值V[i][j]
。
①v[0][j]=v[i][0]=0.
②当w[i]>j时,v[i][j]=v[i-1][j]. 就是你装不下第i个物品。
③当w[i]<=j时,就是第i个物品可以装下的时候,判断是装这个价值大,还是按照之前的价值大。
v[i][j]=max{v[i-1][j], v[i]+v[j-w[i]]}
难点1是在判断价值大小,2是记录结果
public static void main(String[] args) {
int[] weight = {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];
int[][] path = new int[n+1][m+1];
//第一行第一列,默认为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[0].length; j++) {
//套用公式
if (weight[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-weight[i-1]]);
//因为i是从1开始的,val是从0开始的
//为了记录商品存放到背包的情况,我们不能直接使用上面的公式,需要使用if-else来判断
if (v[i-1][j]<val[i-1]+v[i-1][j-weight[i-1]]){
v[i][j] = val[i-1]+v[i-1][j-weight[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[0].length; j++) {
System.out.print(v[i][j]+" ");
}
System.out.println();
}
System.out.println("-----------------");
//输出最后我们放入的商品
// 以下的是错误的,因为需要的最终结果,中间有过程也会使得等于1.会输出所有情况。
for (int i = 0; i < path.length; i++) {
for (int j = 0; j < path[0].length; j++) {
System.out.print(path[i][j]+" ");
// if (path[i][j]==1){
// System.out.printf("第%d个商品放入背包%n",i);
// }
}
System.out.println();
}
// path可以看到,横坐标是能加入几个,纵坐标是相对于前一行对比,如果大于等于他就置1.
//事实上,我们需要寻找当给定m的容量,最下边的置1的情况返回。
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=j-weight[i-1];
//就是,这里放过背包,放过背包以后,肯定有weight[i-1]+X 重量,把weight[i-1]减掉,就是X,看X可以再存多少。
}
i--;//表示的是第i个已经放过了,现在要往前找i-1个放入容量为X的背包
}
}
①暴力匹配算法
//暴力匹配
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;
int j = 0;
while (i
}
②KMP算法
KMP方法就是利用之间判断过的信息,通过一个next数组,保存模式串中前后最长公共子序列的长度,每次回溯时,就通过next数组找到前面匹配过的位置。省去大量时间。
重点:部分匹配表。是由前缀、后缀的相同长度决定。
如:ABCDA==>[A,AB,ABC,ABCD],[BCDA,CDA,DA,A] ====A 长度为1
/**
*
* @param str1 原字符串
* @param str2 查找的字符串
* @param next 部分匹配表
* @return
*/
public static int kmpSearch(String str1,String str2, int[] next){
//遍历str1
for (int i = 0,j=0; i < str1.length(); i++) {
//需要处理不同的时候,调整j的大小 【KMP的核心】
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;
//因为j先j++而i后i++
}
}
return -1;
}
//获取一个字符串的部分匹配值表
public static int[] kmpNext(String dest){
int[] next = new int[dest.length()];
next[0]=0;//如果字符串的长度是1,匹配值就是0
int j=0;
for (int i = 1; i < dest.length(); i++) {
// 当dest.charAt(i)!=dest.charAt(j),我们需要从next[j-1]获取新的j
// 直到发现有dest.charAt(i)==dest.charAt(j)才推出【KMP的核心】
while (j>0&&dest.charAt(i)!=dest.charAt(j)){
j=next[j-1];
}
//如果发现不相等,就从部分匹配表的j-1的位置取
if (dest.charAt(i)==dest.charAt(j)){
//当dest.charAt(i)!=dest.charAt(j) 相等时,部分匹配值才加1.
j++;
}
next[i]=j;
}
return next;
}
在每一步选择中都采取最好或者最优的选择。
得到的结果不一定是最优的结果,但是都是相对近似最优解的结果。使用贪婪算法,效率高。
例子:广播电台覆盖最多的区域。将区域写成一个集合,遍历电台,遍历找到覆盖最大的,取出。将已经覆盖的区域删除,再重新遍历选择。知道所有区域都覆盖。
代码:
ArrayList<String> selects = new ArrayList<>();
//定义一个临时的集合,在遍历的过程中,存放电台区域和未覆盖区域的交集,方便判断个数
HashSet<String> tempSet = new HashSet<String>();
//定义一个maxKey,保存在遍历的过程中,覆盖数最多的key,不为空,就加入结果selects
String maxKey = null;
while (allAreas.size() != 0) {
//每次循环要maxKey置空!!!【重要】
maxKey=null;
for (String key : broadcasts.keySet()) {
//把临时的集合清空!!!【重要】
tempSet.clear();
//当前这个key能够覆盖的地区
HashSet<String> areas = broadcasts.get(key);
tempSet.addAll(areas);
//求交集.tempSet和allAreas的交集,并赋值给tempSet
tempSet.retainAll(allAreas);
//如果,交集比原来maxKey的数量还多,就替换。
if (tempSet.size() > 0 && (maxKey == null || tempSet.size() > broadcasts.get(maxKey).size())) {
maxKey=key;
}
}
if (maxKey!=null){
selects.add(maxKey);
//将存入后的地区,从All区域中去掉
allAreas.removeAll(broadcasts.get(maxKey));
}
}
System.out.println(selects);
满足都连通的情况下,怎么使得边的权值总和最小。
(最小生成树,带权的无向连通图,N个顶点,N-1条边)
普利姆算法求最小生成树:
在包含n个顶点的连通图中,找出只有n-1条边包含所有n个顶点的连通子图(极小连通子图)
就是先从一个节点开始,找与它相连的权值最小的值。
然后下一步是在这两个节点为起始点,找与他们相连的权值最小的点。
下一次,以三个为起始点...最后共找到k-1条边。权值的总和为最小,起始点不同,找到的路径不同。
public void prim(MGraph graph, int v) {
int[] visited = new int[graph.verx]; //是否被访问过,默认都是0
//把当前节点标记为已经访问
visited[v] = 1;
//记录两个顶点的下标
int h1 = -1;
int h2 = -1;
int minWeight = 10000;//将minWeight初始成一个大数,后边遍历会被替换
for (int k = 1; k < graph.verx; k++) {
//k-1条边,普利姆算法结束后,有这么多条边.相当于一条边一条边去找,
for (int i = 0; i < graph.verx; i++) {
//先找访问过的
for (int j = 0; j < graph.verx; j++) {
//再找未访问的
if (visited[i] == 1 && visited[j] == 0 && graph.weight[i][j] < minWeight) {
//替换minWeight的意义就是找最小的两个点的权值
minWeight = graph.weight[i][j];
h1 = i;
h2 = j;
}
}
}
System.out.println("找到第" + k + "条边| " + graph.data[h1] + "---" + graph.data[h2] + " 权值:" + minWeight);
visited[h2] = 1;
minWeight = 10000;
}
}
新建图类和最小生成树类代码未复制。
①所有边的权值排序,按照从小到大。
②依次选择权值小的边的两个顶点,但是要保证不构成回路。
【加入的边的两个顶点不能都指向同一个终点】
p
ublic void kruskal(){
int index = 0; //表示最后结果数组的索引
int[] ends = new int[edgeNum]; //用于保存在 “已经生成的最小生成树”中 每个顶点的终点(不重复,只有一个);
//创建结果数组.,保存最后的最小生成树
EData[] results = new EData[edgeNum];
//获取图中所有边的集合,
EData[] edges = getEdges();
System.out.println("图的边的集合"+Arrays.toString(edges)+"共多少条边"+edges.length);
sortEdge(edges);
System.out.println(Arrays.toString(edges));
//遍历edges,将边添加到最小生成树,并判断即将加入的边是否生成了回路
for (int i = 0; i < edgeNum; i++) {
//获取到第i条边的第一个顶点
int p1 = getPosition(edges[i].start);
int p2 = getPosition(edges[i].end);
//获取p1顶点在已有生成树的终点;找的是终点。A-B-C,输入A,输出C。
int m = getEnd(ends,p1);
int n = getEnd(ends,p2);
if (m!=n){
//没有构成回路
ends[m]=n;//设置m点的终点是n 在已有最小生成树的终点
results[index++] = edges[i];//有一条边加入数组
}
}
System.out.println(Arrays.toString(results));
}
/**
* 获取下标为i的顶点的终点,用于后面判断两个顶点的终点是否相同
* @param ends 就是每个点对应的终点的数组,数组是在遍历的过程中,逐步形成的
* @param i 表示传入的顶点对应的下标
* @return 返回i对应的终点的下标
*/
private int getEnd(int[] ends,int i){
while (ends[i]!=0){
i=ends[i]; //就是返回了输入顶点的终点。输入顶点找与它相连的点,再找相连的点,知道找到顶点
}
return i;
}
典型的最短路径算法,从一个节点到其他节点的最短路径。
它的主要特点是以起始点为中心向外层层扩展(广度优先搜索思想),直到扩展到终点位置。
最初的起始点到每个点的距离找到最小值,以最小距离的另一个节点为新起始点,新起始点到每个节点的距离,
加上前边一段最小的距离,再与最初的节点到现在每个节点的距离比较,选择最小的加入到距离数组。
再将新的起始点的最小距离的另一端节点为新新起始点。加上前边的距离,与直接距离进行比较,
选择最小的加入数组。直到所有节点都遍历完。
class Graph {
private char[] vertex;
private int[][] matrix;
private VisitedVertex vv;
public Graph(char[] vertex, int[][] matrix) {
this.vertex = vertex;
this.matrix = matrix;
}
public void showGraph() {
for (int[] link : matrix
) {
System.out.println(Arrays.toString(link));
}
}
/**
* @param index 出发顶点的下标
*/
public void dijkstra(int index) {
vv = new VisitedVertex(vertex.length, index);
update(index); //更新index节点到周围顶点的距离和它的前驱节点
//走完,还需要vertex.length-2条边
for (int i = 1; i < vertex.length; i++) {
index = vv.updateArr();
update(index);
}
}
private void update(int index) {
int len = 0;
//需要遍历 index的那一行 ,找到节点之间的关系
for (int i = 0; i < matrix[index].length; i++) {
//len是 出发顶点到index顶点的距离加上从index到i顶点的距离的和
len = vv.getDis(index) + matrix[index][i];
if (!vv.in(i) && len < vv.getDis(i)) {
vv.updatePre(i, index);//是把下一个节点i的前驱节点置为index。
vv.updateDis(i, len);
//是把下一个节点的,到index的距离替换到DIS,DIS原来都是最大值,现在对应的数值就是到index的距离。
}
}
System.out.println("=============");
System.out.println(Arrays.toString(vv.dis));
}
public void showDijstra(){
vv.show();
}
}
class VisitedVertex {
public int[] already_arr;//已经访问的
public int[] pre_visited;//你访问的,前一个顶点
public int[] dis; //距离
/**
* @param length 点的个数
* @param index 开始访问的顶点下标
*/
public VisitedVertex(int length, int index) {
this.already_arr = new int[length];
this.pre_visited = new int[length];
this.dis = new int[length];
//dis全置为大的数
Arrays.fill(dis, 65535);
this.already_arr[index] = 1;//初始化的时候就置自己为1
this.dis[index] = 0;//初始化dis,自己到自己为0
}
/**
* @param index
* @return 判断这个点是不是被访问过
*/
public boolean in(int index) {
return already_arr[index] == 1;
}
/**
* 更新出发顶点到index的距离
*
* @param index 更新的下标
* @param len 更新的值 (只有小于那个值才更新)
*/
public void updateDis(int index, int len) {
dis[index] = len;
}
/**
* 更新pre顶点的前驱顶点为index顶点 就是
*
* @param pre 已经访问的顶点,index与pre是相连的
* @param index 可能需要选择的节点
*/
public void updatePre(int pre, int index) {
pre_visited[pre] = index;
}
/**
* 返回出发顶点到index顶点的距离
*
* @param index
*/
public int getDis(int index) {
return dis[index];
}
//在给定出发顶点后继续走,返回下一次的新的访问节点。
/**
* 遍历所有节点,找没有访问过的节点。然后判断距离是不是最小的。
*
* @return
*/
public int updateArr() {
int min = 65535, index = 0;
for (int i = 0; i < already_arr.length; i++) {
if (already_arr[i] == 0 && dis[i] < min) {
min = dis[i];
index = i;
}
}
already_arr[index] = 1;
return index;
}
//最后结果
//那三个数组
public void show() {
System.out.println("=============");
System.out.println("already_arr");
for (int i : already_arr) {
System.out.print(i + " ");
}
System.out.println("pre_visited");
for (int i : pre_visited) {
System.out.print(i + " ");
}
System.out.println("dis");
for (int i : dis) {
System.out.print(i + " ");
}
System.out.println();
char[] vertex ={'A','B','C','D','E','F','G'};
int count=0;
for (int i:dis) {
if (i!=65535){
System.out.println(vertex[count]+"("+i+")");
}else {
System.out.println("N");
}
count++;
}
}
迪杰斯特拉算法通过选定的被访问顶点,求出从出发顶点到其他顶点的最短路径。
弗洛伊德算法是每个顶点都是出发访问点,所以需要将每个顶点都看做是被访问顶点,求出从每一个顶点到其他顶点的最短路径。(中间顶点,出发顶点,终点,三层for循环,修改距离表和前驱关系表)
核心:min{L(i,k)+L(k,j),L(i,j)}
这样获得vi到vj的最短路径
//弗洛伊德算法,同意理解,容易实现
public void floyd(){
int len = 0;
//中间顶点的遍历
for (int k = 0; k < dis.length; k++) {
for (int i = 0; i < dis.length; i++) {
//从i点出发
for (int j = 0; j < dis.length; j++) {
//到j终点
len = dis[i][k]+dis[k][j]; //i==>k|k==>j
if (len<dis[i][j]){
System.out.println(k+" "+pre[k][j]);
dis[i][j] = len;
pre[i][j] = pre[k][j];
//因为上边的dis[i][k]+dis[k][j]是已经变化的,所以不再是k,而是pre[k][j]
}
}
}
}
}
是图的深度优先算法应用。
这里的问题:对回溯的这个过程,或者说代码的运行步骤,有些不太理解。Debug了解一下。
/**
*
* @param chessboard 棋盘
* @param row 行
* @param column 列
* @param step 第几步 初始位置是第一步
*/
public static void travelChessBoard(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));
//对ps进行排序,排序的规则就是对ps的所有Point对象的下一步位置的数目进行非递减排序
sort(ps);
while (!ps.isEmpty()){
Point pNext = ps.remove(0);//取出一个可以走的位置
//判断是还不是访问过
if (!visited[pNext.y*X+ pNext.x]){
travelChessBoard(chessboard,pNext.y,pNext.x,step+1);
}
}
//判断step和该走的步数,吐过没有达到数量,就表示没有完成任务,将整个棋盘置为0
//是有两种情况的,1是步数确实没有达到,2是step达到了,但是处于一个回溯的过程中
if (step<X*Y&&!finished){
chessboard[row][column]=0;
visited[row*X+column]=false;
}else {
finished=true;
}
}
public static ArrayList<Point> next(Point curPoint) {
ArrayList<Point> ps = new ArrayList<>();
Point p1 = new Point();
if ((p1.x = curPoint.x - 2) >= 0 && (p1.y = curPoint.y - 1) >= 0) {
ps.add(new Point(p1));
}
if ((p1.x = curPoint.x - 1) >= 0 && (p1.y = curPoint.y - 2) >= 0) {
ps.add(new Point(p1));
}
if ((p1.x = curPoint.x + 1) < X && (p1.y = curPoint.y - 2) >= 0) {
ps.add(new Point(p1));
}
if ((p1.x = curPoint.x + 2) < X && (p1.y = curPoint.y - 1) >= 0) {
ps.add(new Point(p1));
}
if ((p1.x = curPoint.x + 2) < X && (p1.y = curPoint.y + 1) < Y) {
ps.add(new Point(p1));
}
if ((p1.x = curPoint.x + 1) < X && (p1.y = curPoint.y + 2) < Y) {
ps.add(new Point(p1));
}
if ((p1.x = curPoint.x - 2) >= 0 && (p1.y = curPoint.y + 1) < Y) {
ps.add(new Point(p1));
}
if ((p1.x = curPoint.x - 1) >= 0 && (p1.y = curPoint.y +2) < Y) {
ps.add(new Point(p1));
}
return ps;
}
//利用贪心算法,优化
//就是找到当前可以走的位置,看可以走的位置的下一步的位置数量,走下一步数量比较少的。减少回溯的可能
// 进行非递减排序【1,2,2,2,2,3,3,4】
public static void sort(ArrayList<Point> ps){
ps.sort(new Comparator<Point>() {
@Override
public int compare(Point o1, Point o2) {
//获取o1的下一步的所有位置个数
int count1 = next(o1).size();
int count2 = next(o2).size();
if (count1<count2){
return -1;
}else if (count1==count2){
return 0;
}else {
return 1;
}
}
});
}