常用的十种算法

article/2025/9/13 11:27:47

十种算法

1、二分查找算法(非递归)

1、介绍:

1)二分查找只适用于有序的数列中进行查找(比如数字和字母等),将数列排序后再进行查找

2)二分查找算法的运行时间为对数时间O(log2 n)

public class BinarySearchNoRecur {public static void main(String[] args) {// 测试int[] arr={1,3,8,10,11,67,110};int index = binarySearch(arr,11);System.out.println("index="+index); // 0}// 二分查找的非递归实现/** arr 待查找数组* 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){left=mid+1;}else {right=mid-1;}}return -1;}
}


2、分治算法

1、介绍:

分治法就是把一个复杂的问题分成两个或更多的相同或相似的子问题,再把子问题分成更小的子问题…直到最后子问题可以简单的直接求解,原问题的解即子问题的解的合并。这个技巧是很多高效算法的基础,如排序算法(快速排序,归并排序),傅里叶变换

分治算法的基本步骤:

分解:将原问题分解为若干个规模较小,相互独立,与原问题形式相同的子问题。

解决:若子问题规模较小而容易被解决则直接解,否则递归的解各个子问题

合并:将各个子问题的解合并为原问题的解。

分治算法最佳实践-汉诺塔

思路分析:
1)如果有一个盘,a-c

如果有n>=2情况,总是可以看做是两个(盘1最下边的盘)、(盘2上面的盘)

①先把上面的盘a-b
② 把最下面的盘a-c
③ 再把b的塔 实现b-c

public class DivideAndConquer {public static void main(String[] args) {hanoiTower(3,'A','B','C');}// 汉诺塔移动方法// 分治算法public static void hanoiTower(int num,char a,char b,char c){if (num==1){System.out.println("第1个盘从"+a+"->"+c);}else {// 如果有n>=2情况,看作两个盘,// 一个是最下面的盘1,另一个是上面剩余的盘2//1、先把上面剩余的所有盘 A->B,移动过程会用到chanoiTower(num-1,a,c,b);//2、把最下边的盘 A->CSystem.out.println("第"+num+"个盘从"+ a +"->"+c);//3、把B塔所有的盘从B->C,移动过程使用到ahanoiTower(num-1,b,a,c);}}
}


3、动态规划算法

1、介绍:

动态规划算法通常用于求解具有某种最优性质的问题。在这类问题中,可能会有许多可行解。每一个解都对应于一个值,我们希望找到具有最优值的解。动态规划算法与分治法类似,其基本思想也是将待求解问题分解成若干个子问题,先求解子问题,然后从这些子问题的解得到原问题的解。与分治法不同的是,适合于用动态规划求解的问题,经分解得到子问题往往不是互相独立的。下一个子阶段的求解是建立在上一个子阶段的解的基础上,进行进一步的求解

动态规划可以通过填表的方式来逐步推进,得到最优解

应用场景举例- - -背包问题

背包问题:有一个背包,容量为4磅,现有如下物品

吉他 重量1磅 价格1500
音响 重量4磅 价格3000
电脑 重量3磅 价格2000

1)要求达到的目标为装入的背包的总价值最大,并且重量不超出容量
2)要求装入的物品不能重复

思路分析和图解
① 背包问题主要是指一个给定容量的背包、若干具有一定价值和重量的物品,如何选择物品放入背包使物品价值最大。其中又分01背包和完全背包(完全背包指的是:每种物品都有无限件可用)
② 这里的问题属于01背包,即每个物品最多放一个。而无限背包可以转化为01背包。

算法的主要思想

利用动态规划来解决。每次遍历到的第i个物品,根据w[i]和val[i]来确定是否需要将该物品放入到背包。即对于给定的n个物品,设val[i]、w[i]分别为第i个物品的价值和重量,C为背包的容量。再令v[i][j]表示在前i个物品中能够装入容量为j(j从0增到4)的背包中的最大价值,则有下面结果:

1)v[i][0]=v[0][j]=0; // 前i个物品对于背包容量为0的最大价值是0;前0个物品对于背包容量为j的最大价值是0

2)二维数组的添加是从第二行第二列开始遍历的故i=1、j=1

i从1开始,第一个商品(i-1)的重量就是w[i-1]、第一个商品(i-1)的价值就是val[i-1]

当w[i-1]>j时:v[i][j]=v[i-1][j]
当i商品的重量大于当前背包的容量j时,(i 这个物品就放不进去) - - - 把 v[i-1][j](前i-1件装入容量为j的背包的最大价值)赋给 v[i][j] 因为i放不进去

3)当j>=w[i-1]时,v[i][j]=Math.max(v[i-1][j],v[i-1][j-w[i-1]]+val[i-1])

当i商品的重量小于等于当前背包的容量j时,(i 这个物品可以放进背包) - - - v[i][j](前i件装入容量为j的背包的最大价值),在取v[i-1][j]和v[i-1][j-w[i-1]]+val[i-1]装入i后,剩余容量为j-w[i],在此剩余容量中装入前i-1件商品的最大价值+装入i的价值 v[i])取两者中最大价值的一个赋给 v[i][j]。

在这里插入图片描述

public class dynamic {public static void main(String[] args) {// 背包问题int[] w ={1,4,3}; // 物品的重量int[] val ={1500,3000,2000}; // 物品的价值 val[i]就是v[i]int m=4; // 背包的容量int n=val.length; //物品的个数// 为了记录放入商品的情况,定义一个二维数组int[][] path=new int[n+1][m+1];//创建二维数组// v[i][j]表示在前i个物品中能够装入容量为j的背包中的最大价值int[][] v= new int[n+1][m+1];// 初始化第一行、第一列,不去设置也可以默认为0for (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[i].length;j++){  //不处理第一列,j从1开始//公式if (w[i-1]>j){  // 因为程序i是从1开始的,因此原公式w[i]改为// w[i-1]v[i][j]=v[i-1][j];}else {  // i从1开始,按顺序商品价格和重量在数组中val[i-1]、w[i-1]//v[i][j]=Math.max(v[i-1][j],val[i-1]+v[i-1][j-w[i-1]]);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;   // 此记录的是最优解,i表示第几个商品}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();}// 输出最后放入的哪些商品int i= path.length-1; // 行最大下标int j= path[0].length-1; // 列最大下标while (i>0 && j>0){if(path[i][j]==1){ //说明第i个商品放入了容量为j的背包中System.out.printf("第%d个商品放入到背包\n",i);j-=w[i-1]; // 减去当前i商品的重量w[i-1]}i--;}}


4、KMP算法(串的匹配算法)

1、介绍:

定位操作:若主串str1中存在与串str2值相同的子串,则返回它在主串str1中第一次出现的位置;否则函数返回0.

2、暴力匹配算法主串和模式串都会回溯
在这里插入图片描述

public class ViolenceMatch {public static void main(String[] args) {// 测试暴力匹配算法String str1 = "硅硅谷 尚硅谷你尚硅 尚硅谷你尚硅谷你尚硅你好";String str2 = "尚硅谷你尚硅你";int index=violenceMatch(str1,str2);System.out.println("index="+index);}public static int violenceMatch(String str1,String str2){char[] s1=str1.toCharArray();char[] s2=str2.toCharArray();int i=0,j=0;  //i索引指向s1,j索引指向s2while (i<s1.length && j< s2.length){if (s1[i]==s2[j]){  // 匹配成功i++;        // 继续比较后续字符j++;}else {// 检查下一个字串i=i-(j-1);  // i和j同增,匹配失败,把i退回上次位置的下一下位置j=0;}}if(j==s2.length){return i-j;   //如果全部匹配(最后匹配成功j++)索引会等于s2.length,匹配成功}else {         // 匹配失败return 0;}}
}

3、KMP算法主串指针不回溯,模式串回溯

思路分析:

① 先得到字串的部分匹配表

② 使用部分匹配表完成KMP匹配


《部分匹配表》

先介绍前缀和后缀

例如:字符串 “bread”

前缀(字符串中包含第一个元素但不包含最后一个元素的连续子字符串):b 、br、bre、brea

后缀(字符串中包含最后一个元素但不包含第一个元素的连续子字符串): read、ead、ad、d


部分匹配值”就“前缀”和“后缀”的**最长**的共有元素的长度

例如:

“A”的前缀和后缀都只能是空集,最长共有元素的长度为0

“ABC”的前缀[A,AB],后缀[C,BC],最长共有元素的长度为0

"ABCDAB"的前缀[A,AB,ABC,ABCD],后缀[B,AB,DAB,CDAB,BCDAB],最长共有元素为“AB”,长度是2

在这里插入图片描述
主串和模式串的下标索引都是从0开始

next数组定义:当主串与模式串的某一位字符不匹配时,模式串要回退的位置。

j表示模式串的第j个字符匹配失败时
next[j]:其值 = 第j位字符前面j-1位字符组成的子串的前后缀重合字符数 表示模式串跳到next[j]位置再继续匹配

子串前后缀最大重合长度为j,如果子串增加一个元素,如果此元素Str[i]与Str[j](Str[1])相等,则增加元素后的子串的最大重合长度j也+1

不相等则,令j退回到next[j](j=next[j]),再判断此元素与Str[i]与新的Str[j]是否相等…(循环判断)

图解如下相同颜色框的表示是元素相对应的子串
在这里插入图片描述

 // 甩手掌柜凡三岁 求next数组   运用原理与上面两者不同public static int[] KmpNext(String str){    // 求模式串的Next数组int[] next = new int[str.length()];   // 创建一个模式串长度的next数组int j=-1;         //初始模式串子串前后缀最大重合长度为-1int i=0;            // 从模式串第一个元素(i=0)开始遍历next[0]=-1;         // 定义模式串i=0位置元素之前的子串的前后缀最大重合长度为-1while (i<str.length()-1){ // 相当于上面两种的移位,故最后一个的不需要if (j==-1 || str.charAt(i)==str.charAt(j)){  // i从0开始,这里j的值第一次出现是i前一位对应的前后缀最大重合长度next[++i] = ++j;}else {             //如果不相等 把j移回到next[j]位置,// 再进循环判断,j=next[j];}}return next;}

在这里插入图片描述
优化代码如下:

public static int[] KmpNext(String str){    // 求模式串的Next数组int[] next = new int[str.length()];   // 创建一个模式串长度的next数组int j=-1;         //初始模式串子串前后缀最大重合长度为-1int i=0;            // 从模式串第一个元素(i=0)开始遍历next[0]=-1;         // 定义模式串i=0位置元素之前的子串的前后缀最大重合长度为-1while (i<str.length()-1){ // 相当于上面两种的移位,故最后一个的不需要if (j==-1 || str.charAt(i)==str.charAt(j)){  // i从0开始,这里j的值第一次出现是i前一位对应的前后缀最大重合长度i++;j++;if(str.charAt(i)==str.charAt(j)){next[i]=next[j];}else{next[i]=j;}}else {             //如果不相等 把j移回到next[j]位置,// 再进循环判断,j=next[j];}}return next;}

KMP算法查找

加粗样式

// KMP算法public static int IndexKmp(String str1,String str2){char[] s1=str1.toCharArray();char[] s2=str2.toCharArray();int i=0,j=0;  //i索引指向s1,j索引指向s2,也是next数组next[j]对应查找的最大重合长度(j返回的位置)int [] next=KmpNext2(str2); //获取模式串str2的next数组while (i< s1.length && j< s2.length){if (j==-1 || s1[i]==s2[j]){i++;j++;}else {j=next[j];    // 匹配失败j移回到其对应的next数组应该的位置}}if (j == s2.length){   // 匹配到最后一个(s2.length-1)也成功j++后// j==s2.length  全部匹配成功return i-j;   // 返回第一次匹配成功的位置}else {return 0;    // 返回0则匹配失败}}


5、贪心算法

1、介绍

1)贪心算法是指再对问题进行求解时,在每一步选择中都采取最好或最优(最有利)的选择,从而希望能够导致结果是最好或者最优的算法。

2)贪心算法所得到的结果不一定是最优的结果(有时候会是最优解),但是都是相对近似(接近)最优解的结果。


贪心算法最佳应用- - -集合覆盖

例题:假设存在如下表的需要付费的广播台,以及广播台信号可以覆盖的地区,如何选择最少的广播台,让所有的地区都可以接收到信号。

	广播台           		覆盖地区K1                “北京”,“上海”,“天津”K2                “广州”,“北京”,“深圳”K3                “成都”,“上海”,“杭州”K4                “上海”,“天津”K5                “杭州”,“大连“

思路分析:

1)遍历所有广播电台,找到一个覆盖了最多未覆盖的地区的电台(此电台可能包含一些已经覆盖的地区,但没有关系)

2)将这个电台加入到一个集合中(比如ArrayList),想办法把该电台覆盖的地区在下一次比较时去掉。

3)重复第一步直到覆盖了全部的地区。

public class GreedyAlgorithm {public static void main(String[] args) {// 创建广播电台,放入到一个Map中  key不能重复,value没有限制HashMap<String, HashSet<String>> broadcasts = new HashMap<>();// 将各个电台放入到broadcastsHashSet<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("大连");//加入到mapbroadcasts.put("K1",hashSet1);broadcasts.put("K2",hashSet2);broadcasts.put("K3",hashSet3);broadcasts.put("K4",hashSet4);broadcasts.put("K5",hashSet5);// 存放所有地区HashSet<String> allAreas = new HashSet<>();allAreas.addAll(hashSet1);  // addAll添加全部,自动去重allAreas.addAll(hashSet2);allAreas.addAll(hashSet3);allAreas.addAll(hashSet4);allAreas.addAll(hashSet5);/*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<>();// 定义一个maxKey保存在一次遍历中,能够覆盖最多未覆盖地区对应的电台的key// 如果maxKey(存放key 即电台)不为null,则会加入到selects// len存放遍历过后 电台与未覆盖地区交集元素的最大个数String maxKey=null;int len=0;while (allAreas.size()!=0){  // 如果allAreas部位0,则表示还有未覆盖的地区// 没进行一次while,把上一次存放电台的maxKey置空,同时len也置零maxKey=null;len=0;// 遍历broadcasts,取出对应的keyfor (String key:broadcasts.keySet()){// 每进行一次for,就要把tempSet清空,//              以备存下一个电台的元素进行交集运算tempSet.clear();//当前key能够覆盖的地区HashSet<String> areas=broadcasts.get(key);tempSet.addAll(areas);  //放入临时集合中// 求出stmpSet 和allAreas集合的交集,交集重新赋给tempSettempSet.retainAll(allAreas);// templen存放本轮中交集个数int templen=tempSet.size();// 如果当前电台与未覆盖的地区集合有交集//  且 ( maxKey没有被赋值//  或  当前交集元素个数大于上一轮电台与未覆盖地区集合的交集元素的个数//                   templen>len   体现出贪心算法的特点if (templen >0 && (maxKey==null || templen>len)){//if (tempSet.size() >0 && (maxKey==null || tempSet.size()>broadcasts.get(maxKey).size())){maxKey = key;    // 把电台更换为当前电台len = templen;   //就把当前交集个数赋给len}}//maxKey不为null,就应该将maxKey加入到selectsif (maxKey != null){selects.add(maxKey);//将maxKey指向的广播电台覆盖的地区从allAreas中去掉allAreas.removeAll(broadcasts.get(maxKey));}}System.out.println("得到的选择结果是"+selects);}}


6、Prim(普里姆)算法 - - -即解决(带权连通无向图的最小生成树


最小生成树(MST)的介绍:

 1)给定一个带权的无向连通图,如何选取一颗生成树,使树上所有边上权的总和为最小,这叫最小生成树。2)最小生成树可能有多个,但边的权值之和总是唯一且最小的3)最小生成树的边数 = 顶点数 -1. 砍掉一条则不连通,增加一条边则会出现回路。(极小连通子图)4)如果一个连通图本身就是一颗树,则其最小生成树就是它本身5) 只有连通图才有生成树,非连通图只有生成森林

求最小生成树的算法主要是普里姆算法和克鲁斯卡尔算法


应用场景-修路问题带权无向连通图

在这里插入图片描述
1)一个乡镇,有7个村庄(A、B、C、D、E、F、G),现在需要修路把7个村庄连通(上图的线是修路的长度 权值)

2)各个村庄的距离用边线表示(权),比如A-B距离5公里

3)问:如何修路保证各个村庄都能连通,并且总的修建公路总里程最短


思路分析

从某个顶点开始构建生成树;每次将代价最小的新顶点纳入生成树,直到所有顶点都纳入为止。

1)设G=(V,E)是连通图,T=(U,D)是最小生成树,V,U是顶点集合,E,D是边的集合

2)若从顶点u开始构造最小生成树,则从集合V中取出顶点u放入集合U中,标记顶点v的visited[u]=1

3)若集合U中顶点ui与集合V-U中的顶点vj之间存在边

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[][]{   //999表示不连通,其他的表示连通的权{999,5,7,999,999,999,2},{5,999,999,9,999,999,3},{7,999,999,999,8,999,999},{999,9,999,9,999,4,999},{999,999,8,999,999,5,4},{999,999,999,4,5,999,6},{2,3,999,999,4,6,999},};// 创建MGraph对象MGraph graph = new MGraph(verxs);// 创建一个MinTree对象MinTree minTree = new MinTree();minTree.createGraph(graph,verxs,data,weight);// 显示minTree.showGraph(graph);//测试primminTree.prim(graph,0);}
}// 先创建村庄的图->再求出最小生成树
class MinTree {// 创建图的邻接矩阵/**  graph  图对象*  verxs  图对应的顶点个数*  data   图的各个顶点的值*  weight 图的邻接矩阵* */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));}}// 编写prim算法,得到最小生成树/**  graph 村庄图*   v表示从图的第几个顶点开始生成'A'->0 'B'->1。。。* */public void prim(MGraph graph, int v) {// visited[]标记结点(顶点)是否被访问过int[] visited = new int[graph.verxs];//visited[]初始默认都为0,表示没有被访问/*for (int i=0;i< graph.verxs;i++){visited[i]=0;}*/// 把当前这个顶点标记为已访问visited[v]=1;// 最后h1和h2记录的是当前权值最小边连接的两个顶点的下标int h1=-1;int h2=-1;int minWeight=999;  // 将minWeight初始成一个大数,后面遍历过程会被替换for (int k=1;k<graph.verxs;k++){ //找到graph.verxs-1条边// 普里姆算法结束时,边的个数比顶点少1,故从1开始// 确定每一次生成的子图,距离最近for (int i=0;i<graph.verxs;i++){    // 遍历已经访问过的顶点if(visited[i]==1){for (int j=0;j<graph.verxs;j++){  //遍历没有访问的顶点if( visited[j]==0 && graph.weight[i][j]<minWeight){// 替换minWeight// 寻找已访问过的结点和为访问过的结点间的权值最小的边minWeight=graph.weight[i][j];h1=i;h2=j;}}}}// 找到一条边最小System.out.println("边<"+graph.data[h1]+","+graph.data[h2]+"> 权值:"+minWeight);// 将当前的这个结点标记为已经访问visited[h2]=1;// 重置minWeight=999;minWeight=999;}}}// 图类
class MGraph{int verxs; // 表示图的结点个数char[] data; //存放结点数据int[][] weight;// 存放带权边,就是邻接矩阵public MGraph(int verxs) {this.verxs = verxs;data = new char[verxs];weight = new int[verxs][verxs];}
}


7、Kruskal(克鲁斯卡尔)算法 - - -即解决(带权连通无向图的最小生成树) 适用于边稀疏的网的最小生成树


应用场景-公交站问题

在这里插入图片描述
1)某城市新增7个站点(A、B、C、D、E、F、G),现在需要修路把7个站点连通

2)各个站点的距离用边线表示(权),比如A-B距离12公里

3)问:如何修路保证各个站点都能连通,并且总的修建公路总里程最短


思路分析

每次选择一条权值最小的边,使这条边的两头连通(原本已经连通的就不选),直到所有点但都连通

① 按照权值从小到大的顺序选择n-1条边,保证这n-1条边不构成回路(处理方式:记录顶点再最小生成树中的终点,顶点的终点是在最小生成树中与它连通的最大顶点(对编号来说)。然后每次需要将一条边添加到最小生成树时,判断该边的两个顶点的终点是否重合,重合的话则会构成回路)。
加入的边的两个顶点不能都指向同一个终点,否则会构成回路

② 具体做法:首先构造一个只含n个顶点的森林,然后依权值从小到大从连通网中选择边加入到森林中,并使森林不产生回路,直至森林变成一棵树为止。

public class KruskalAlgorithm {private int edgeNum;//边的个数private char[] vertexs;//顶点数组private int[][] matrix;//邻接矩阵// 使用INF表示两个顶点不能连通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 对象实例KruskalAlgorithm kruskal = new KruskalAlgorithm(vertexs, matrix);//输出构建的kruskal.print();//kruskal.kruskal();}//构造器public KruskalAlgorithm(char[] vertexs,int[][] matrix){//初始化顶点数和边的个数int vlen=vertexs.length;//初始化顶点  复制拷贝的方式赋值,值的变化不改变引用类型的vertexs本身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 print() {System.out.println("邻接矩阵为: \n");for(int i = 0; i < vertexs.length; i++) {for(int j=0; j < vertexs.length; j++) {System.out.printf("%12d", matrix[i][j]);}System.out.println();//换行}}/*** 功能:对边进行排序处理, 冒泡排序* @param edges 边的集合*/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;}}}}/**** @param ch 顶点的值,比如'A','B'* @return 返回ch顶点对应的下标,如果找不到,返回-1*/private int getPosition(char ch) {for(int i = 0; i < vertexs.length; i++) {if(vertexs[i] == ch) {//找到return i;}}//找不到,返回-1return -1;}/*** 功能: 获取图中边,放到EData[] 数组中,后面我们需要遍历该数组* 是通过matrix 邻接矩阵来获取* EData[] 形式 [['A','B', 12], ['B','F',7], .....]* @return*/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;}/*** 功能: 获取下标为i的顶点的终点(), 用于后面判断两个顶点的终点是否相同* @param ends : 数组就是记录了各个顶点对应的终点是哪个,ends 数组是在遍历过程中,逐步形成* @param i : 表示传入的顶点对应的下标* @return 返回的就是 下标为i的这个顶点对应的终点的下标,*/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;}public void kruskal() {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 数组,将边添加到最小生成树中时,判断是准备加入的边否形成了回路,如果没有,就加入 rets, 否则不能加入for(int i=0; i < edgeNum; i++) {//获取到第i条边的第一个顶点(起点)int p1 = getPosition(edges[i].start); //p1=4//获取到第i条边的第2个顶点int p2 = getPosition(edges[i].end); //p2 = 5//获取p1这个顶点在已有最小生成树中的终点int m = getEnd(ends, p1); //m = 4//获取p2这个顶点在已有最小生成树中的终点int n = getEnd(ends, p2); // n = 5//是否构成回路if(m != n) { //没有构成回路ends[m] = n; // 设置m 在"已有最小生成树"中的终点 <E,F> [0,0,0,0,5,0,0,0,0,0,0,0]rets[index++] = edges[i]; //有一条边加入到rets数组}}//<E,F> <C,D> <D,E> <B,F> <E,G> <A,B>。//统计并打印 "最小生成树", 输出  retsSystem.out.println("最小生成树为");for(int i = 0; i < index; i++) {System.out.println(rets[i]);}}}// 创建EData类,表示一条边
class EData{char start;//边的一点char end;//边的另一点int weight;//边的权值//构造器public EData(char start, char end, int weight) {this.start = start;this.end = end;this.weight = weight;}@Overridepublic String toString() {return "EData{" +"start=" + start +", end=" + end +", weight=" + weight +'}';}}


8、Dijkstra(迪杰斯特拉)算法 - - -即解决(一个顶点到其他顶点的最短路径) - - - 不适用于带负权图

最短路径问题:

1、单源最短路径(一个顶点到其他顶点最短路径)

① BFS算法(无权值)
② Dijkstra算法(带权图、无权图)

2、各顶点间的最短路径

Floyd算法(带权图、无权图)

应用场景-最短路径问题

在这里插入图片描述
1)战争时期,胜利乡有7个村庄(A、B、C、D、E、F、G),现在有六个邮差,从G点出发,需要分别把邮件分别送到A、 B、C、D、E、F六个村庄

2)各个村庄的距离用边线表示(权)

3)问:如何计算出G村庄到其他各个村庄的最短距离

4)如果从其他点出发到各个点的最短距离又是多少

算法过程:

设置出发顶点为v,顶点集合V{v1,v2,vi…},v到V中各顶点的距离构成距离集合Dis,Dis{d1,d2,di…},Dis集合记录着v到图中各顶点的距离(到自身可以看作0,v到vi距离对应位di)

1)从Dis中选择值最小的di并移除Dis集合,同时移除V集合中对应的顶点vi,此时的v到vi即为最短路径2)更新Dis集合,更新规则:比较v到V集合中顶点距离值,与v通过vi到V集合中顶点的距离值,保留值较小的一个(同时也应该更新顶点的前驱结点为vi,表明是通过vi到达的)3)重复执行两步操作,直到最短路径顶点为目标顶点即可结束。
public class DijkstraAlgorithm {public static void main(String[] args) {char[] vertex = {'A','B','C','D','E','F','G'};//邻接矩阵int[][] matrix =new int[vertex.length][vertex.length];final int N =666; //表示不可连接matrix[0]=new int[]{N,5,7,N,N,N,2};matrix[1]=new int[]{5,N,N,9,N,N,3};matrix[2]=new int[]{7,N,N,N,8,N,N};matrix[3]=new int[]{N,9,N,N,N,4,N};matrix[4]=new int[]{N,N,8,N,N,5,4};matrix[5]=new int[]{N,N,N,4,5,N,6};matrix[6]=new int[]{2,3,N,N,4,6,N};// 创建Graph对象Graph graph = new Graph(vertex,matrix);//显示图graph.showGraph();graph.dsj(6);graph.showDijkstra();}
}class Graph{private char[] vertex;     //顶点数组private int[][] matrix;     //邻接矩阵private VisitedVertex visitedVertex; // 已经访问过顶点的集合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));}}//显示最后结果public void showDijkstra(){visitedVertex.show();}//迪杰斯特拉算法public void dsj(int index){visitedVertex = new VisitedVertex(vertex.length, index);update(index);  // 更新index顶点到周围顶点的距离和前驱顶点for (int j=1;j<vertex.length;j++){index=visitedVertex.updateArr(); //选择并返回新的访问顶点update(index);          // 更新index顶点到周围顶点的距离和前驱顶点}}// 更新index下标顶点到周围顶点的距离和周围顶点的前驱顶点private void update(int index){int len=0;//根据遍历邻接矩阵对应当前顶点的matrix[index]行for (int j=0;j<matrix[index].length;j++){// len:出发顶点到index顶点的距离+从index顶点到j顶点的距离的和//      (即:出发顶点到j距离)len=visitedVertex.getDis(index) + matrix[index][j];// 如果j顶点没有被访问过,并且len小于出发顶点到j顶点的距离,就需要更新if(!visitedVertex.in(j) && len<visitedVertex.getDis(j)){visitedVertex.updatePre(j,index); // 当前访问的index顶点就是j的前驱visitedVertex.updateDis(j,len);  // 把顶点到j的距离就更新为len}}}}// 已访问顶点集合
class VisitedVertex{//记录各个顶点是否访问过,1表示访问过,0表示未访问,会动态更新public int[] already_arr;//每个下标对应的值为前一个顶点下标(前驱),会动态更新public int[] pre_visited;//记录出发顶点到其他所有顶点的距离,比如G为出发顶点,就会记录G到其他顶点的距离,// 会动态更新,求的最短距离就会存放到dispublic int[] dis;//构造器/** length:表示顶点个数* index:出发顶点对应的下标,比如G顶点,下标就是6* */public VisitedVertex(int length,int index){this.already_arr=new int[length];this.pre_visited=new int[length];this.dis=new int[length];//初始化disArrays.fill(dis,666);this.already_arr[index]=1;//设置出发顶点被访问this.dis[index]=0; //设置出发顶点到自身的访问距离为0}//判断index顶点是否被访问过//如果访问过,就返回true,否则返回falsepublic boolean in(int index){return already_arr[index] == 1;}// 更新出发顶点到index顶点的距离public void updateDis(int index ,int len){dis[index]=len;}// 更新顶点pre的前驱顶点为index结点public void updatePre(int pre,int index){pre_visited[pre]=index;}// 返回出发顶点到index顶点的距离public int getDis(int index){return dis[index];}// 继续选择并返回新的访问顶点,// 比如这里的G被访问完后,就是A点作为新的访问顶点(注意不是出发顶点)public int updateArr(){int min =666,index = 0;for (int i=0;i<already_arr.length;i++){    // 广度优先遍历+贪心if (already_arr[i]==0 && dis[i]<min){// i顶点未被访问,且i与当前顶点距离小于666(即无穷)min=dis[i]; // 循环中找到一个最小距离的  (贪心)index=i;}}// 更新index顶点被访问,返回此顶点already_arr[index]=1;return index;}//显示最后的结果 三个数组的输出public void show(){System.out.println("======================");// 输出already_arrfor (int i:already_arr){System.out.print(i+" ");}System.out.println();// 输出pre_visitedfor (int i:pre_visited){System.out.print(i+" ");}System.out.println();// 输出disfor (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!=666){System.out.print(vertex[count]+"("+i+")");}else {System.out.println("N ");}count++;}System.out.println();}
}


8、Floyd(弗洛伊德)算法 - - -即解决(各个顶点间的最短路径)

算法过程

1)设置顶点vi到vk的最短路径已知为Lik,顶点vk到vj的最短路径已知为Lkj,顶点vi到vj的路径为Lij,则vi到vj的最短路径为:min((Lik+Lkj),Lij),vk的取值为图中所有顶点,则可获得vi到vj的最短路径

2)至于vi到vk的最短路径Lik或者vk到vj的最短路径Lkj,是以同样的方式获得

在这里插入图片描述
1)胜利乡有7个村庄(A、B、C、D、E、F、G)

2)各个村庄的距离用边线表示(权)

3)问:如何计算出各村庄到其他各村庄的最短距离?

在这里插入图片描述

算法核心在于各顶点之间距离表、顶点前驱关系表

// ...准备工作,根据图的信息初始化矩阵A和path
for(int k=0;k<n;k++){    		// 考虑以Vk作为中转点for(int i=0;i<n;i++){		// 遍历整个矩阵,i为行号,j为列号for(int j=0;j<n;j++){if(A[i][j]>A[i][k]+A[k][j]){  // 以VK为中转点的路径更短A[i][j]=A[i][k]+A[k][j];	// 更新最短路径长度path[i][j]=k; 				// 前驱表对应的更新为中转点}}}
}

代码如下:

public class FloydAlgorithm {public static void main(String[] args) {//测试图的创建char[] vertex={'A','B','C','D','E','F','G'};//创建邻接矩阵int[][] matrix = new int[vertex.length][vertex.length];final int N =999;matrix[0]=new int[]{0,5,7,N,N,N,2};matrix[1]=new int[]{5,0,N,9,N,N,3};matrix[2]=new int[]{7,N,0,N,8,N,N};matrix[3]=new int[]{N,9,N,0,N,4,N};matrix[4]=new int[]{N,N,8,N,0,5,4};matrix[5]=new int[]{N,N,N,4,5,0,6};matrix[6]=new int[]{2,3,N,N,4,6,0};//创建FloydGraph对象FloydGraph floydGraph = new FloydGraph(vertex.length, matrix, vertex);// 调用floydfloydGraph.floyd();floydGraph.show();}
}class FloydGraph{private char[] vertex; //存放顶点的数组private int[][] dis;  // 保存从各个顶点出发到其他顶点的距离,最后结果保留在该数组private int[][] pre;  // 保存到达目标顶点的前驱顶点// length 顶点个数  matrix 邻接矩阵  vertex 顶点数组public FloydGraph(int length,int[][] matrix, char[] vertex) {this.vertex = vertex;this.dis = matrix;this.pre = new int[length][length];//对pre数组初始化,注意存放的是前去顶点的下标for (int i=0;i<length;i++){Arrays.fill(pre[i],i);}}// 显示pre数组和dis数组public void show() {for (int k = 0; k < dis.length; k++) {// 输出pre每行for (int i = 0; i < dis.length; i++) {System.out.print(vertex[pre[k][i]] + "   ");}System.out.println();}System.out.println("====================");for (int k = 0; k < dis.length; k++) {// 输出dis每行for (int i = 0; i < dis.length; i++) {System.out.printf(" %2d ",dis[k][i] );}System.out.println();}}// 弗洛伊德算法public void floyd(){//从中转顶点遍历,k就是中转顶点的坐标for(int k=0;k<dis.length;k++){    		// 考虑以Vk作为中转点for(int i=0;i<dis.length;i++){		// 遍历整个矩阵,i为行号,j为列号for(int j=0;j<dis.length;j++){if(dis[i][j]>dis[i][k]+dis[k][j]){  // 以VK为中转点的路径更短dis[i][j]=dis[i][k]+dis[k][j];	// 更新最短路径长度pre[i][j]=pre[k][j]; 				// 前驱表对应的更新为中转点}}}}}}


10、马踏棋盘算法(骑士周游问题) - - -图的深度优先遍历BFS

介绍

1)将马随即放在国际象棋的8*8棋盘Board[0-7][0-7]的某个方格中,马按走棋规则(马走日字)进行移动。要求每个方格只进入一次,走遍棋盘上64个方格。

在这里插入图片描述

代码实现 — 含贪心优化

public class HorseChessBoardAlgorithm {private static int X; //表示棋盘的列数(X的下标负责每行的左右)private static int Y; //表示棋盘的行数(Y的下标负责每列的左右)//创建一个数组,标记棋盘的各个位置是否被访问过,一维表示二维private static boolean visited[];// 使用一个属性,标记是否棋盘的所有位置都被访问private static boolean finished; // 初始为flase,如果为true,表示成功public static void main(String[] args) {//测试System.out.println("骑士周游:");X = 8;Y = 8;int row = 1; //骑士初始行,从1开始编号int col = 1; //骑士初始列,从1开始编号//创建棋盘int[][] chessboard = new int[X][Y];//初始值都为falsevisited = new boolean[X * Y];long start = System.currentTimeMillis();traversalChessboard(chessboard, row -1, col - 1, 0);long end = System.currentTimeMillis();System.out.println("共耗时 " + (end - start) + " 毫秒");//输出棋盘情况for (int[] rows : chessboard) {for (int step : rows) {System.out.print(step + "\t");}System.out.println();}}/*** 骑士周游算法* @param chessboard 棋盘(棋盘的每个格子记录的是骑士第几部访问的该位置)* @param row 骑士当前位置的行* @param col 骑士当前位置的列* @param step 当前骑士走的是第几步,初始时为1*/public static void traversalChessboard(int[][] chessboard, int row, int col, int step){//记录骑士第step步访问该位置chessboard[row][col] = step;//将该位置记录为已访问  一维表示二维visited[row * X + col] = true;//获取当前位置可以走的下一个位置的集合ArrayList<Point> next = next(new Point(col, row));//对next进行排序  排序的规则就是对next的所有的Point对象的下一步的位置的数目,进行非递减排序sort(next);  // 贪心优化while (!next.isEmpty()){//取出骑士下一个走的位置Point p = next.remove(0);//判断下一个位置是否已经访问过,没有访问则访问if(!visited[p.y * X + p.x]){traversalChessboard(chessboard, p.y, p.x, step + 1);}}//判断骑士是否完成周游,如果骑士走的步数小于 X * Y 则表示没有完成任务,此时,将当前位置,置0//step < X * Y有两种情况:1.棋盘还没有走完  2.棋盘此时正在回溯if(step < X * Y - 1  && !finished){chessboard[row][col] = 0;visited[row * X + col] = false;}else {finished  = true;}}//根据当前这一步的所有的下一步的选择位置,进行非递减排序,减少回溯的次数private static void sort(ArrayList<Point> next) {next.sort(new Comparator<Point>() {@Overridepublic int compare(Point o1, Point o2) {//获取o1下一步的所有位置个数int count1 = next(o1).size();//获取o2下一步的所有位置个数int count2 = next(o2).size();if(count1 < count2){return -1;}else if(count1 == count2){return 0;}else {return 1;}}});}/*** 根据当前的位置(Point为java的内置对象),计算骑士下一步能走的位置,最多有8个位置* @param curPoint* @return 包含当前位置能访问的下一个位置的坐标!!!*      (坐标横着的是X轴!!!),X的下标自左向右增大,Y的下标自上向下增大*/public static ArrayList<Point> next(Point curPoint){ArrayList<Point> nextList = new ArrayList<>();Point p = new Point();//表示骑士可以走5这个位置if((p.x = curPoint.x - 2) >= 0 && (p.y = curPoint.y - 1) >= 0){nextList.add(new Point(p));}//表示骑士可以走6这个位置if((p.x = curPoint.x - 1) >= 0 && (p.y = curPoint.y - 2) >= 0){nextList.add(new Point(p));}//表示骑士可以走7这个位置if((p.x = curPoint.x + 1) < X && (p.y = curPoint.y - 2) >= 0){nextList.add(new Point(p));}//表示骑士可以走0这个位置if((p.x = curPoint.x + 2) < X && (p.y = curPoint.y - 1) >= 0){nextList.add(new Point(p));}//表示骑士可以走1这个位置if((p.x = curPoint.x + 2) < X && (p.y = curPoint.y + 1) < Y){nextList.add(new Point(p));}//表示骑士可以走2这个位置if((p.x = curPoint.x + 1) < X && (p.y = curPoint.y + 2) < Y){nextList.add(new Point(p));}//表示骑士可以走3这个位置if((p.x = curPoint.x - 1) >= 0 && (p.y = curPoint.y + 2) < Y){nextList.add(new Point(p));}//表示骑士可以走4这个位置if((p.x = curPoint.x - 2) >= 0 && (p.y = curPoint.y + 1) < Y){nextList.add(new Point(p));}return  nextList;}}

http://chatgpt.dhexx.cn/article/xMO0heRU.shtml

相关文章

基础夯实:基础数据结构与算法(二)

基础夯实&#xff1a;基础数据结构与算法&#xff08;二&#xff09; 常见的10种算法1、递归算法例题1&#xff1a;计算n&#xff01;例题2&#xff1a;斐波那契数列例题3&#xff1a;递归将整形数字转换为字符串例题4&#xff1a;汉诺塔例题5&#xff1a;猴子吃桃例题6&#x…

蓝桥杯知识点汇总:基础知识和常用算法

文章目录 JAVA基础语法&#xff1a;算法竞赛常用的JAVA API&#xff1a;算法和数据结构简单算法简单数据结构图论数学贪心动态规划 补充省赛题解待更&#xff1a; 此系列包含蓝桥杯&#xff08;软件类&#xff09;所考察的绝大部分知识点。一共分为 基础语法&#xff0c; 常用…

HBA的WWN号以及存储区域网络

古驰古驰巴拉巴拉&#xff0c;今天讲一下存储区域网络和wwn号以及查看wwn号的方法 存储区域网络&#xff08;Storage Area Network&#xff0c;简称SAN&#xff09;采用网状通道&#xff08;Fibre Channel &#xff0c;简称FC&#xff0c;区别与Fiber Channel光纤通道&#xf…

nsw hnsw

参考了很多该博客 https://blog.csdn.net/u011233351/article/details/85116719&#xff0c;感谢博主。 参考论文《Approximate nearest neighbor algorithm based on navigable small world graphs》 《Efficient and robust approximate nearest neighbor search using Hie…

思科光交MDS9710绑定WWN并激活新的wwn

第一步、查看所有的wwn号 #命令 #show flogi database 内容示例&#xff1a; 第二步、查看是否有发现新的wwn号 图中为新发现的wwn号 第三步、将该wwn号加入到对应的zone下 #先筋肉config模式 #再进入对应的zone zone name Zone_P11_****——** vsan 1 #新增新存在的wwn号…

www.wwwwwwwwww

复习题 一、问答题 1.Anaconda的优点有哪些&#xff1f; &#xff08;1&#xff09;开源。 &#xff08;2&#xff09;安装过程简单。 &#xff08;3&#xff09;⾼性能使⽤Python和R语⾔。 &#xff08;4&#xff09;免费的社区⽀持。 &#xff08;5&#xff09; Conda包…

NWD(2022)

A Normalized Gaussian Wasserstein Distance for Tiny Object Detection Abstract 检测微小物体是一个非常具有挑战性的问题&#xff0c;因为微小物体仅包含几个像素大小。我们证明&#xff0c;由于缺乏外观信息&#xff0c;最先进的检测器无法在微小物体上产生令人满意的结…

SAN环境中WWN,WWNN,WWPN的区别

存储区域网络&#xff08;Storage Area Network&#xff0c;简称SAN&#xff09;采用网状通道&#xff08;Fibre Channel &#xff0c;简称FC&#xff0c;区别与Fiber Channel光纤通道&#xff09;技术&#xff0c;通过FC交换机连接存储阵列和服务器主机&#xff0c;建立专用于…

WWN,WWNN,WWPN介绍

WWN是HBA卡用的编号吧&#xff0c;每一个光纤通道设备都有一个唯一的标识&#xff0c;称为WWN&#xff08;world wide name&#xff09;&#xff0c;由IEEE负责分配。在有多台主机使用磁盘阵列时&#xff0c;通过WWN号来确定哪台主机正在使用指定的LUN&#xff08;或者说是逻辑…

WWN,WWNN,WWPN区别

WWN: world wide number 是硬件的全球唯一标示 WWPN: world wide port number 是指端口号 WWNN: world wide node number 是指节点号 如果是光纤交换机的话wwn和wwnn是一样的,而wwpn是指每个光纤端口. 如果是HBA卡的话,若是只有一个端口则三者可能一样,若是有多个端口则和交换…

如何查看WWN号

如何查看WWN号 WWN即World Wide Name,用来标识网络上的一个连接或连接集合,主要用于FC和SAS。就像网卡的MAC地址一样,WWN是用在光纤网络的。 如何查看WWN号AIX: 1,获得AIX主机连接的光纤设备: # lsdev -Cc adapter -S a | grep fcs fcs0 Ava…

linux查看WWN号及常见问题解决

linux查看WWN号及常见问题解决 查看WWN号查看WWID号查询常见问题 查看WWN号 要查看CentOS 6.7版本的WWN号&#xff0c;可以执行以下步骤&#xff1a; 1.确保已经连接了存储设备。 lspci | grep -i fibre2.在终端中输入命令&#xff1a;lsscsi&#xff0c;然后按 Enter 键。该命…

WWN,WWNN,WWPN三者的区别

WWN: world wide number 是硬件的全球唯一标示 WWPN: world wide port number 是指端口号 WWNN: world wide node number 是指节点号 如果是光纤交换机的话wwn和wwnn是一样的,而wwpn是指每个光纤端口. 如果是HBA卡的话,若是只有一个端口则三者可能一样,若是有多个端口则和交换…

excel制作可模糊匹配的下拉框

1.整体效果&#xff1a; 2.设置数据有效性 在来源中输入公式&#xff1a;OFFSET(国籍地区!$A$1,MATCH(船舶基本资料!$F2&"*",国籍地区!$A$2:$A$246,0),,COUNTIF(国籍地区!$A$2:$A$246,船舶基本资料!$F2&"*"),) 其中“国籍地区”为一个sheet,ru如下…

关于Excel表操作-通过gensim实现模糊匹配

gensim是一个Python的自然语言处理库&#xff0c;能够将文档根据TF-IDF&#xff0c;LDA&#xff0c;LSI等模型转换成向量模式&#xff0c;此外&#xff0c;gensim还实现了word2vec&#xff0c;能够将单词转换为词向量。 gensim的一些常见概念&#xff1a; 语料Corpus: 一组原始…

Excel效率提升|解决不完全匹配数据整理

以各地级市&#xff08;1&#xff0d;5线城市&#xff09;人均GDP数据为例 从国家统计局或wind导出来的数据&#xff1a; 而我们整理后的目标sheet的匹配字段如图&#xff1a; 如何进行有效匹配&#xff1f; 观察可知&#xff1a;我们需要以城市名作为匹配的依据 如何将城市名批…

ExcelWPS通配符的使用方法,一招解决模糊查询!

大家好&#xff0c;本期和大家分享Excel通配符的使用方法&#xff01; Excel 通配符一共有3个。 它们的含义如下图所示&#xff1a; 符号含义举例&#xff1f;表示任意单个字符比如要查找所有姓王的名字为2个字的人&#xff0c;则可以使用 【 王? 】 代替&#xff1b;查找所…

[Excel]vlookup的内在逻辑以及模糊检索

作为一个excel的用户&#xff0c;vlookup可能是使用频度最高的一个函数 但是有关这个函数当中的数学意义不知道大家具体了解多少 今天就在这里讲讲我个人的vlookup的一些用法 比一般的使用方法稍微高阶一点&#xff08;求保命&#xff09; 大部分人刚开始使用vlookup的时候都…

python 模糊匹配字符串 excel,python pandas模糊匹配 读取Excel后 获取指定指标的操作...

1.首先读取Excel文件 数据代表了各个城市店铺的装修和配置费用,要统计出装修和配置项的总费用并进行加和计算; 2.pandas实现过程 import pandas as pd #1.读取数据 df = pd.read_excel(r./data/pfee.xlsx) print(df) cols = list(df.columns) print(cols) #2.获取含有装修 和…

模糊匹配省市区地址

用户输入地址不可能一定规范&#xff0c;如按习惯省略掉&#xff1a;“省”、“市”、“区”等关键字&#xff0c;此时安装正则匹配很容易查找不到正确的地址。 以下代码按照用户输入的先后顺序&#xff0c;相同的词组进行匹配&#xff0c;可靠性与适配性大大提高&#xff0c;记…