一、最小生成树的定义

  一个连通图的生成树是一个极小的连通子图,它含有图中全部的顶点,但只有足以构成一棵树的n-1条边。

  在一个网的所有生成树中,权值总和最小的生成树称为最小代价生成树(Minimum Cost Spanning Tree),简称为最小生成树。

  构造最小生成树的准则有以下3条:

  • 只能使用该图中的边构造最小生成树
  • 当且仅当使用n-1条边来连接图中的n个顶点
  • 不能使用产生回路的边

  对比两个算法,Kruskal算法主要是针对边来展开,边数少时效率会非常高,所以对于稀疏图有很大的优势;而Prim算法对于稠密图,即边数非常多的情况会更好一些。

  二、普里姆(Prim)算法

  1.Prim算法描述

  假设N={V,{E}}是连通网,TE是N上最小生成树中边的集合。算法从U={u0,u0属于V},TE={}开始。重复执行下面的操作:在所有u属于U,v属于V-U的边(u,v)中找一条代价最小的边(u0,v0)并加入集合TE,同时v0加入U,直到U=V为止。此时TE中必有n-1条边,则T=(V,{TE})为N的最小生成树。

  2.Prim算法的C语言代码实现

/* Prim算法生成最小生成树  */
void MiniSpanTree_Prim(MGraph G)
{
int min, i, j, k;
int adjvex[MAXVEX]; /* 保存相关顶点下标 */
int lowcost[MAXVEX]; /* 保存相关顶点间边的权值 */
lowcost[] = ;/* 初始化第一个权值为0,即v0加入生成树 */
/* lowcost的值为0,在这里就是此下标的顶点已经加入生成树 */
adjvex[] = ; /* 初始化第一个顶点下标为0 */
for(i = ; i < G.numVertexes; i++) /* 循环除下标为0外的全部顶点 */
{
lowcost[i] = G.arc[][i]; /* 将v0顶点与之有边的权值存入数组 */
adjvex[i] = ; /* 初始化都为v0的下标 */
}
for(i = ; i < G.numVertexes; i++)
{
min = INFINITY; /* 初始化最小权值为∞, */
/* 通常设置为不可能的大数字如32767、65535等 */
j = ;k = ;
while(j < G.numVertexes) /* 循环全部顶点 */
{
if(lowcost[j]!= && lowcost[j] < min)/* 如果权值不为0且权值小于min */
{
min = lowcost[j]; /* 则让当前权值成为最小值 */
k = j; /* 将当前最小值的下标存入k */
}
j++;
}
printf("(%d, %d)\n", adjvex[k], k);/* 打印当前顶点边中权值最小的边 */
lowcost[k] = ;/* 将当前顶点的权值设置为0,表示此顶点已经完成任务 */
for(j = ; j < G.numVertexes; j++) /* 循环所有顶点 */
{
if(lowcost[j]!= && G.arc[k][j] < lowcost[j])
{/* 如果下标为k顶点各边权值小于此前这些顶点未被加入生成树权值 */
lowcost[j] = G.arc[k][j];/* 将较小的权值存入lowcost相应位置 */
adjvex[j] = k; /* 将下标为k的顶点存入adjvex */
}
}
}
}

Prim算法

  3.Prim算法的Java语言代码实现

package bigjun.iplab.adjacencyMatrix;
/**
* 最小生成树之Prim算法
*/
public class MiniSpanTree_Prim { private static class CloseEdge{
Object adjVex; // 顶点符号
int lowCost; // 顶点对应的权值
public CloseEdge(Object adjVex, int lowCost) {
this.adjVex = adjVex;
this.lowCost = lowCost;
}
} private static int getMinMum(CloseEdge[] closeEdges) {
int min = Integer.MAX_VALUE; // 初始化最小权值为正无穷
int v = -1; // 顶点数组下标
for (int i = 0; i < closeEdges.length; i++) { // 遍历权值数组,找到最小的权值以及对应的顶点数组的下标
if (closeEdges[i].lowCost != 0 && closeEdges[i].lowCost < min) {
min = closeEdges[i].lowCost;
v = i;
}
}
return v;
} // Prim算法构造图G的以u为起始点的最小生成树
public static void Prim(AdjacencyMatrixGraphINF G, Object u) throws Exception{
// 初始化一个二维最小生成树数组minSpanTree,由于最小生成树的边是n-1,所以数组第一个参数是G.getVexNum() - 1,第二个参数表示边的起点和终点符号,所以是2
Object[][] minSpanTree = new Object[G.getVexNum() - 1][2];
int count = 0; // 最小生成树得到的边的序号
// 初始化保存相关顶点和相关顶点间边的权值的数组对象
CloseEdge[] closeEdges = new CloseEdge[G.getVexNum()];
int k = G.locateVex(u);
for (int j = 0; j < G.getVexNum(); j++) {
if (j!=k) {
closeEdges[j] = new CloseEdge(u, G.getArcs()[k][j]);// 将顶点u到其他各个顶点权值写入数组中
}
}
closeEdges[k] = new CloseEdge(u, 0); // 加入u到自身的权值0
for (int i = 1; i < G.getVexNum(); i++) { // 注意,这里从1开始,
k = getMinMum(closeEdges); // 获取u到数组下标为k的顶点的权值最短
minSpanTree[count][0] = closeEdges[k].adjVex; // 最小生成树第一个值为u
minSpanTree[count][1] = G.getVexs()[k]; // 最小生成树第二个值为k对应的顶点
count++;
closeEdges[k].lowCost = 0; // 下标为k的顶点不参与最小权值的查找了
for (int j = 0; j < G.getVexNum(); j++) {
if (G.getArcs()[k][j] < closeEdges[j].lowCost) {
closeEdges[j] = new CloseEdge(G.getVex(k), G.getArcs()[k][j]);
}
}
}
System.out.print("通过Prim算法得到的最小生成树序列为: {");
for (Object[] Tree : minSpanTree) {
System.out.print("(" + Tree[0].toString() + "-" + Tree[1].toString() + ")");
}
System.out.println("}");
} }

  4.举例说明Prim算法实现过程

  以下图为例:

  

  测试类:

    // 手动创建一个用于测试最小生成树算法的无向网
public static AdjacencyMatrixGraphINF createUDNByYourHand_ForMiniSpanTree() {
Object vexs_UDN[] = {"V0", "V1", "V2", "V3", "V4", "V5", "V6", "V7", "V8"};
int arcsNum_UDN = 15;
int[][] arcs_UDN = new int[vexs_UDN.length][vexs_UDN.length];
for (int i = 0; i < vexs_UDN.length; i++) // 构造无向图邻接矩阵
for (int j = 0; j < vexs_UDN.length; j++)
if (i==j) {
arcs_UDN[i][j]=0;
} else {
arcs_UDN[i][j] = arcs_UDN[i][j] = INFINITY;
} arcs_UDN[0][1] = 10;
arcs_UDN[0][5] = 11;
arcs_UDN[1][2] = 18;
arcs_UDN[1][6] = 16;
arcs_UDN[1][8] = 12;
arcs_UDN[2][3] = 22;
arcs_UDN[2][8] = 8;
arcs_UDN[3][4] = 20;
arcs_UDN[3][6] = 24;
arcs_UDN[3][7] = 16;
arcs_UDN[3][8] = 21;
arcs_UDN[4][5] = 26;
arcs_UDN[4][7] = 7;
arcs_UDN[5][6] = 17;
arcs_UDN[6][7] = 19; for (int i = 0; i < vexs_UDN.length; i++) // 构造无向图邻接矩阵
for (int j = i; j < vexs_UDN.length; j++)
arcs_UDN[j][i] = arcs_UDN[i][j];
return new AdjMatGraph(GraphKind.UDN, vexs_UDN.length, arcsNum_UDN, vexs_UDN, arcs_UDN);
} public static void main(String[] args) throws Exception { AdjMatGraph UDN_Graph = (AdjMatGraph) createUDNByYourHand_ForMiniSpanTree();
MiniSpanTree_Prim.Prim(UDN_Graph, "V0");
}

  输出为:

通过Prim算法得到的最小生成树序列为: {(V0-V1)(V0-V5)(V1-V8)(V8-V2)(V1-V6)(V6-V7)(V7-V4)(V7-V3)}

  分析算法执行过程:

  

从V0开始:
-count为0,k为0,closeEdges数组的
-lowCost为{0 10 INF INF INF 11 INF INF INF},adjVex数组为{V0,V0,V0,V0,V0,V0,V0,V0,V0}
-比较lowCost,于是k为1,adjVex[1]为V0,minSpanTree[0]为(V0,V1),lowCost为{0 0 INF INF INF 11 INF INF INF}
-k为1,与V1的权值行比较,得到新的
-lowCost为:{0 0 18 INF INF 11 16 INF 12},adjVex数组为{V0,V0,V1,V0,V0,V0,V1,V0,V1}
-比较lowCost,于是k为5,adjVex[5]为V0,minSpanTree[1]为(V0,V5),lowCost为{0 0 18 INF INF 0 16 INF 12}
-k为5,与V5的权值行比较,得到新的
-lowCost为{0 0 18 INF 26 0 16 INF 12},adjVex数组为{V0,V0,V1,V0,V5,V0,V1,V0,V1}
-比较lowCost,于是k为8,adjVex[8]为V1,minSpanTree[2]为(V1,V8),lowCost为{0 0 18 INF INF 0 16 INF 0}
...

  三、克鲁斯卡尔(Kruskal)算法

  1.Kruskal算法描述

  Kruskal算法是根据边的权值递增的方式,依次找出权值最小的边建立的最小生成树,并且规定每次新增的边,不能造成生成树有回路,直到找到n-1条边为止。

  Kruskal算法的基本思想是:假设图G=(V,{E})是一个具有n个顶点的连通无向网,T=(V,{TE}是图G的最小生成树,其中,V是T的顶点集,TE是T的边集,则构造最小生成树的步骤是:

  • T的初始状态为T=(V,{空}),即开始时,最小生成树T是图G的生成零图。
  • 将图G中的边按照权值从小到大的顺序依次选取,若选取的边未使生成树T形成回路,则加入TE中,否则舍弃,直至TE中包含了n-1条边为止。

  2.Kruskal算法的C语言代码实现

#include "stdio.h"
#include "stdlib.h"
#include "io.h"
#include "math.h"
#include "time.h" #define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0 typedef int Status; /* Status是函数的类型,其值是函数结果状态代码,如OK等 */ #define MAXEDGE 20
#define MAXVEX 20
#define INFINITY 65535 typedef struct
{
int arc[MAXVEX][MAXVEX];
int numVertexes, numEdges;
}MGraph; typedef struct
{
int begin;
int end;
int weight;
}Edge; /* 对边集数组Edge结构的定义 */ /* 构件图 */
void CreateMGraph(MGraph *G)
{
int i, j; /* printf("请输入边数和顶点数:"); */
G->numEdges=;
G->numVertexes=; for (i = ; i < G->numVertexes; i++)/* 初始化图 */
{
for ( j = ; j < G->numVertexes; j++)
{
if (i==j)
G->arc[i][j]=;
else
G->arc[i][j] = G->arc[j][i] = INFINITY;
}
} G->arc[][]=;
G->arc[][]=;
G->arc[][]=;
G->arc[][]=;
G->arc[][]=;
G->arc[][]=;
G->arc[][]=;
G->arc[][]=;
G->arc[][]=;
G->arc[][]=;
G->arc[][]=;
G->arc[][]=;
G->arc[][]=;
G->arc[][]=;
G->arc[][]=; for(i = ; i < G->numVertexes; i++)
{
for(j = i; j < G->numVertexes; j++)
{
G->arc[j][i] =G->arc[i][j];
}
} } /* 交换权值 以及头和尾 */
void Swapn(Edge *edges,int i, int j)
{
int temp;
temp = edges[i].begin;
edges[i].begin = edges[j].begin;
edges[j].begin = temp;
temp = edges[i].end;
edges[i].end = edges[j].end;
edges[j].end = temp;
temp = edges[i].weight;
edges[i].weight = edges[j].weight;
edges[j].weight = temp;
} /* 对权值进行排序 */
void sort(Edge edges[],MGraph *G)
{
int i, j;
for ( i = ; i < G->numEdges; i++)
{
for ( j = i + ; j < G->numEdges; j++)
{
if (edges[i].weight > edges[j].weight)
{
Swapn(edges, i, j);
}
}
}
printf("权排序之后的为:\n");
for (i = ; i < G->numEdges; i++)
{
printf("(%d, %d) %d\n", edges[i].begin, edges[i].end, edges[i].weight);
} } /* 查找连线顶点的尾部下标 */
int Find(int *parent, int f)
{
while ( parent[f] > )
{
f = parent[f];
}
return f;
} /* 生成最小生成树 */
void MiniSpanTree_Kruskal(MGraph G)
{
int i, j, n, m;
int k = ;
int parent[MAXVEX];/* 定义一数组用来判断边与边是否形成环路 */ Edge edges[MAXEDGE];/* 定义边集数组,edge的结构为begin,end,weight,均为整型 */ /* 用来构建边集数组并排序********************* */
for ( i = ; i < G.numVertexes-; i++)
{
for (j = i + ; j < G.numVertexes; j++)
{
if (G.arc[i][j]<INFINITY)
{
edges[k].begin = i;
edges[k].end = j;
edges[k].weight = G.arc[i][j];
k++;
}
}
}
sort(edges, &G);
/* ******************************************* */ for (i = ; i < G.numVertexes; i++)
parent[i] = ; /* 初始化数组值为0 */ printf("打印最小生成树:\n");
for (i = ; i < G.numEdges; i++) /* 循环每一条边 */
{
n = Find(parent,edges[i].begin);
m = Find(parent,edges[i].end);
if (n != m) /* 假如n与m不等,说明此边没有与现有的生成树形成环路 */
{
parent[n] = m; /* 将此边的结尾顶点放入下标为起点的parent中。 */
/* 表示此顶点已经在生成树集合中 */
printf("(%d, %d) %d\n", edges[i].begin, edges[i].end, edges[i].weight);
}
}
} int main(void)
{
MGraph G;
CreateMGraph(&G);
MiniSpanTree_Kruskal(G);
return ;
}

Kruskal算法

  3.Kruskal算法的Java语言代码实现

package bigjun.iplab.adjacencyMatrix;
/**
* 最小生成树之Kruskal算法
*/
public class MiniSpanTree_Kruskal { private final static int INFINITY = Integer.MAX_VALUE; // 表示正无穷 private static class Edge{
int begin; // 边的起点
int weight; // 边的权值
int end; // 边的终点 public Edge(int begin, int weight, int end) {
this.begin = begin;
this.weight = weight;
this.end = end;
}
} // 交换两条边的各个属性,包括起点,终点和权值
private static void Swap_edges(Edge[] edges, int i, int j) {
int temp;
temp = edges[i].begin;
edges[i].begin = edges[j].begin;
edges[j].begin = temp;
temp = edges[i].weight;
edges[i].weight = edges[j].weight;
edges[j].weight = temp;
temp = edges[i].end;
edges[i].end = edges[j].end;
edges[j].end = temp;
} // 对边集数组按照权值进行排序
private static void Sorted_Edge(Edge[] edges) {
for (int i = 0; i < edges.length; i++) {
for (int j = i + 1; j < edges.length; j++) {
if (edges[i].weight > edges[j].weight) {
Swap_edges(edges, i, j);
}
}
} } // 查找顶点的尾部下标
private static int Find_indexOfParent(int[] parent, int f) {
while (parent[f] > 0) {
f = parent[f];
}
return f;
} public static void Kruskal(AdjacencyMatrixGraphINF G) throws Exception {
Edge[] edges = new Edge[G.getArcNum()]; // 定义边集数组
int[] parent = new int[G.getVexNum()]; // 定义一组数组用来判断边与边是否形成回路
int k = 0;
for (int i = 0; i < G.getVexNum() - 1; i++) { // 将邻接矩阵G转化为边集数组edges
for (int j = i + 1; j < G.getVexNum(); j++) {
if (G.getArcs()[i][j] < INFINITY) {
edges[k] = new Edge(i, G.getArcs()[i][j], j);
k++;
}
}
}
Sorted_Edge(edges); // 对边集数组按照权值从小到大排序
for (int i = 0; i < G.getVexNum(); i++) { // 初始化判断边与边是否形成回路数组
parent[i] = 0;
}
System.out.print("通过Kruskal算法得到的最小生成树序列为: {");
for (int i = 0; i < edges.length; i++) { // 遍历每一条边
int n = Find_indexOfParent(parent, edges[i].begin);
int m = Find_indexOfParent(parent, edges[i].end);
if (n!=m) { // 如果不构成回路的话
parent[n] = m; // 将此边的结尾项放在下标为起点的parent中,表示此顶点已经在生成树集合中
System.out.print("(" + G.getVex(edges[i].begin) + "-" + G.getVex(edges[i].end) + ")");
}
}
System.out.println("}");
}
}

  4.Kruskal算法的距离说明实现过程

  以下图为例:

  

  测试代码:

    // 手动创建一个用于测试最小生成树算法的无向网
public static AdjacencyMatrixGraphINF createUDNByYourHand_ForMiniSpanTree() {
Object vexs_UDN[] = {"V0", "V1", "V2", "V3", "V4", "V5", "V6", "V7", "V8"};
int arcsNum_UDN = 15;
int[][] arcs_UDN = new int[vexs_UDN.length][vexs_UDN.length];
for (int i = 0; i < vexs_UDN.length; i++) // 构造无向图邻接矩阵
for (int j = 0; j < vexs_UDN.length; j++)
if (i==j) {
arcs_UDN[i][j]=0;
} else {
arcs_UDN[i][j] = arcs_UDN[i][j] = INFINITY;
} arcs_UDN[0][1] = 10;
arcs_UDN[0][5] = 11;
arcs_UDN[1][2] = 18;
arcs_UDN[1][6] = 16;
arcs_UDN[1][8] = 12;
arcs_UDN[2][3] = 22;
arcs_UDN[2][8] = 8;
arcs_UDN[3][4] = 20;
arcs_UDN[3][6] = 24;
arcs_UDN[3][7] = 16;
arcs_UDN[3][8] = 21;
arcs_UDN[4][5] = 26;
arcs_UDN[4][7] = 7;
arcs_UDN[5][6] = 17;
arcs_UDN[6][7] = 19; for (int i = 0; i < vexs_UDN.length; i++) // 构造无向图邻接矩阵
for (int j = i; j < vexs_UDN.length; j++)
arcs_UDN[j][i] = arcs_UDN[i][j];
return new AdjMatGraph(GraphKind.UDN, vexs_UDN.length, arcsNum_UDN, vexs_UDN, arcs_UDN);
} public static void main(String[] args) throws Exception { AdjMatGraph UDN_Graph = (AdjMatGraph) createUDNByYourHand_ForMiniSpanTree();
MiniSpanTree_Prim.Prim(UDN_Graph, "V0");
MiniSpanTree_Kruskal.Kruskal(UDN_Graph);
}

  输出为:

通过Kruskal算法得到的最小生成树序列为: {(V4-V7)(V2-V8)(V0-V1)(V0-V5)(V1-V8)(V3-V7)(V1-V6)(V6-V7)}

  分析算法执行过程(重点分析如何利用parent数组来判断新加入的边是否构成回路):

  

i=0,parent[]={0,0,0,0,0,0,0,0,0},edge[0].begin=4,edge[0].end=7,n=4,m=7,parent[4]=7,打印(V4,V7)
i=1,parent[]={0,0,0,0,7,0,0,0,0},edge[1].begin=2,edge[1].end=8,n=2,m=8,parent[2]=8,打印(V2,V8)
i=2,parent[]={0,0,8,0,7,0,0,0,0},edge[2].begin=0,edge[2].end=1,n=0,m=1,parent[0]=1,打印(V0,V1)
i=3,parent[]={1,0,8,0,7,0,0,0,0},edge[3].begin=0,edge[3].end=5,n=1,m=5,parent[1]=5,打印(V0,V5)
i=4,parent[]={1,5,8,0,7,0,0,0,0},edge[4].begin=1,edge[4].end=8,n=5,m=8,parent[5]=8,打印(V1,V8)
i=5,parent[]={1,5,8,0,7,8,0,0,0},edge[5].begin=3,edge[5].end=7,n=3,m=7,parent[3]=7,打印(V3,V7)
i=6,parent[]={1,5,8,7,7,8,0,0,0},edge[6].begin=1,edge[6].end=6,n=8,m=6,parent[8]=6,打印(V1,V6)
i=6时,parent[]={1,5,8,7,7,8,0,0,6},如上右图,有两个连通的边集合A与B共同被纳入到最小生成树中,其中,对于集合A来说,
parent[0]=1表示V0和V1已经在生成树的边集合A中,
parent[1]=5表示V1和V5也在边集合A中,
同理parent[5]=8表示V5和V8在边集合A中,parent[8]=6表示V8和V6在边集合A中
此时,parent[6]=0表示集合A暂时到头,所以要重点关注的就是parent[6]的值
同理,边集合B中,parent[3]=7,parent[4]=7,parent[7]=0,表示V3、V4、V7在另一个边集合B中并且暂时到头,所以要重点关注的就是parent[7]的值 i=7时,parent[]={1,5,8,7,7,8,0,0,6},edge[7].begin=5,edge[7].end=6, parent[5]=8,parent[8]=6,parent[6]=0,即n=6,而parent[6]=0即m=7,
此时n=m=6,所以不能打印,实际上也就是如果parent[6]=6就表示V6到V6,这就是环路了!另一方面,直观来看(V5,V6)这条边加上去之后会构成环路,所以就不行。跳出循环。 i=8,同理
i=9,parent[]={1,5,8,7,7,8,0,0,6,edge[9].begin=6,edge[9].end=7,n=6,m=7,parent[6]=7,打印(V6,V7)
i=10~14,同理。

数据结构(三十三)最小生成树(Prim、Kruskal)的更多相关文章

  1. 最小生成树 Prim Kruskal

    layout: post title: 最小生成树 Prim Kruskal date: 2017-04-29 tag: 数据结构和算法 --- 目录 TOC {:toc} 最小生成树Minimum ...

  2. 数据结构与算法--最小生成树之Kruskal算法

    数据结构与算法--最小生成树之Kruskal算法 上一节介绍了Prim算法,接着来看Kruskal算法. 我们知道Prim算法是从某个顶点开始,从现有树周围的所有邻边中选出权值最小的那条加入到MST中 ...

  3. 邻接矩阵c源码(构造邻接矩阵,深度优先遍历,广度优先遍历,最小生成树prim,kruskal算法)

    matrix.c #include <stdio.h> #include <stdlib.h> #include <stdbool.h> #include < ...

  4. 数据结构学习笔记05图(最小生成树 Prim Kruskal)

    最小生成树Minimum Spanning Tree 一个有 n 个结点的连通图的生成树是原图的极小连通子图,且包含原图中的所有 n 个结点,并且有保持图连通的最少的边. 树: 无回路   |V|个顶 ...

  5. 布线问题 最小生成树 prim + kruskal

    1 : 第一种 prime     首先确定一个点 作为已经确定的集合 , 然后以这个点为中心 , 向没有被收录的点 , 找最短距离( 到已经确定的点 ) , 找一个已知长度的最小长度的 边 加到 s ...

  6. POJ 1258 Agri-Net(最小生成树 Prim+Kruskal)

    题目链接: 传送门 Agri-Net Time Limit: 1000MS     Memory Limit: 10000K Description Farmer John has been elec ...

  7. 最小生成树-Prim&Kruskal

    Prim算法 算法步骤 S:当前已经在联通块中的所有点的集合 1. dist[i] = inf 2. for n 次 t<-S外离S最近的点 利用t更新S外点到S的距离 st[t] = true ...

  8. 邻接表c源码(构造邻接矩阵,深度优先遍历,广度优先遍历,最小生成树prim,kruskal算法)

    graph.c #include <stdio.h> #include <stdlib.h> #include <limits.h> #include " ...

  9. poj1861 最小生成树 prim &amp; kruskal

    // poj1861 最小生成树 prim & kruskal // // 一个水题,为的仅仅是回味一下模板.日后好有个照顾不是 #include <cstdio> #includ ...

随机推荐

  1. JMeter 压测Server Agent无法监控资源问题,PerfMon Metrics Collector报Waiting for sample,Error loading results file - see file log, Can't accept UDP connections java.net.BindException: Address already in use 各种疑难杂症

    如何安装插件此博主已经说得很详细了. https://www.cnblogs.com/saryli/p/6596647.html 但是需注意几点: 1.修改默认端口,这样可以避免掉一个问题.Serve ...

  2. 三大特征提取器(RNN/CNN/Transformer)

    目录 三大特征提取器 - RNN.CNN和Transformer 简介 循环神经网络RNN 传统RNN 长短期记忆网络(LSTM) 卷积神经网络CNN NLP界CNN模型的进化史 Transforme ...

  3. springboot2.0+ 使用拦截器导致静态资源被拦截

    在spring1.0+的版本中,配置拦截器后是不会拦截静态资源的.其配置如下: @Configuration public class WebMvcConfig extends WebMvcConfi ...

  4. Angular template ng-template/container/content

    1. ng-template 形式:<ng-template>...</ng-template> 默认ng-template中的内容会隐藏; 可通过[ngIf]来控制内容显示隐 ...

  5. 夯实Java基础系列13:深入理解Java中的泛型

    目录 泛型概述 一个栗子 特性 泛型的使用方式 泛型类 泛型接口 泛型通配符 泛型方法 泛型方法的基本用法 类中的泛型方法 泛型方法与可变参数 静态方法与泛型 泛型方法总结 泛型上下边界 泛型常见面试 ...

  6. 十大排序算法JavaScript实现总结

    花费了几周的时间断断续续的练习和模仿与使用JavaScript代码实现了十大排序算法. 里面有每种算法的动图和静态图片演示,看到图片可以自己先按照图片的思路实现一下. github中正文链接,点击查看 ...

  7. 【Elasticsearch 搜索之路】(一)什么是 Elasticsearch?

    本篇文章对 Elasticsearch 做了基本介绍,在后续将通过专栏的方式持续更新,本系列以 Elasticsearch7 作为主要的讲解版本,欢迎各位大佬指正,共同学习进步! 一般涉及大型数据库的 ...

  8. Docker5-docker私库的搭建及常用方法-harbor-registry方式

    一.简介 1.官方已经提供registry镜像为什么还需要用harbor 1)registry缺少镜像清理机制,可以push但是不能删除,耗费空间 2)registry缺乏相应的扩展机制 3)harb ...

  9. 游戏服务器和Web服务器的区别

    用Go语言写游戏服务器也有一个多月了,也能够明显的感受到两者的区别.这篇文章就是想具体的聊聊其中的区别.当然,在了解区别之间,我们先简单的了解一下Go语言本身. 1. Go语言的特点 Go语言跟其他的 ...

  10. mobaxterm使用手册

    Mobaxterm V14使用手册 文章出处    https://blog.51cto.com/937761/2372598 简介 MobaXterm 一款Windows系统下全功能终端软件.以下将 ...