经典算法总结

二分查找算法

可以用循环或递归

分治算法

把一个复杂的问题分成两个或更多的相同或相似的子问题,再把子问题分成更小的子问题……直到最后子问题可以简单的直接求解,原问题的解即子问题的解的合并。

分治算法可以求解的一些经典问题
二分搜索
大整数乘法
棋盘覆盖
合并排序
快速排序
线性时间选择
最接近点对问题
循环赛日程表
汉诺塔

分治法在每一层递归上都有三个步骤:
分解:将原问题分解为若干个规模较小,相互独立,与原问题形式相同的子问题
解决:若子问题规模较小而容易被解决则直接解,否则递归地解各个子问题
合并:将各个子问题的解合并为原问题的解。

例:面试题 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.使用限界函数,剪去不能得到最优解的路径。
问题的关键在于如何定义问题的解空间,转化成树(即解空间树)。解空间树分为两种:子集树和排列树。两种在算法结构和思路上大体相同。

  1. 子集树
    所给的问题是从n个元素的集合S中找出满足某种性质的子集时,相应的解空间成为子集树。
    如0-1背包问题,从所给重量、价值不同的物品中挑选几个物品放入背包,使得在满足背包不超重的情况下,背包内物品价值最大。它的解空间就是一个典型的子集树。
  2. 排列树
    所给的问题是确定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 + "]";
	}
}

深度优先搜索

二叉树的最近公共祖先
二叉树展开为链表

路径总和 II

二叉树剪枝

652. 寻找重复的子树

广度优先搜索

1609. 奇偶树

1325. 删除给定值的叶子节点

贪心算法

找2~3个题目
在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
人脸识别的经典算法有多种,下面我为你介绍几种常用的Python人脸识别算法。 1. Haar特征分类器算法:这是一种基于Haar特征的分类器算法,利用Haar-like特征对人脸进行检测和识别。OpenCV库提供了Haar特征分类器的训练模型,可以直接使用。这种算法简单高效,但对光照、姿态等因素的变化较为敏感。 2. LBPH算法:LBPH(Local Binary Patterns Histograms)是一种基于局部二值模式直方图的算法。它将人脸图像分割为若干小块,在每个小块中提取局部二值模式特征,并生成对应的直方图。最后通过比较直方图的相似度来进行人脸识别。LBPH算法在光照、姿态等因素变化较大的情况下仍具有较好的性能。 3. Eigenfaces算法:Eigenfaces(特征脸)是一种基于主成分分析的算法,通过构建人脸图像的特征空间来进行人脸识别。该算法先将人脸图像转换为灰度图像,并将其展开成向量,然后对这些向量进行降维处理,并计算特征向量。最后通过计算新图像与特征向量之间的欧氏距离来进行识别。 这些算法都是经典的Python人脸识别算法,可根据具体需求选择适合的算法进行应用。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* [总结几个简单好用的Python人脸识别算法](https://blog.csdn.net/m0_72091242/article/details/125740394)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *3* [python实现人脸识别经典算法--特征脸法](https://blog.csdn.net/Donny19880915/article/details/101372875)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值