数据结构(三十三)最小生成树(Prim、Kruskal)
一、最小生成树的定义
一个连通图的生成树是一个极小的连通子图,它含有图中全部的顶点,但只有足以构成一棵树的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)的更多相关文章
- 最小生成树 Prim Kruskal
layout: post title: 最小生成树 Prim Kruskal date: 2017-04-29 tag: 数据结构和算法 --- 目录 TOC {:toc} 最小生成树Minimum ...
- 数据结构与算法--最小生成树之Kruskal算法
数据结构与算法--最小生成树之Kruskal算法 上一节介绍了Prim算法,接着来看Kruskal算法. 我们知道Prim算法是从某个顶点开始,从现有树周围的所有邻边中选出权值最小的那条加入到MST中 ...
- 邻接矩阵c源码(构造邻接矩阵,深度优先遍历,广度优先遍历,最小生成树prim,kruskal算法)
matrix.c #include <stdio.h> #include <stdlib.h> #include <stdbool.h> #include < ...
- 数据结构学习笔记05图(最小生成树 Prim Kruskal)
最小生成树Minimum Spanning Tree 一个有 n 个结点的连通图的生成树是原图的极小连通子图,且包含原图中的所有 n 个结点,并且有保持图连通的最少的边. 树: 无回路 |V|个顶 ...
- 布线问题 最小生成树 prim + kruskal
1 : 第一种 prime 首先确定一个点 作为已经确定的集合 , 然后以这个点为中心 , 向没有被收录的点 , 找最短距离( 到已经确定的点 ) , 找一个已知长度的最小长度的 边 加到 s ...
- POJ 1258 Agri-Net(最小生成树 Prim+Kruskal)
题目链接: 传送门 Agri-Net Time Limit: 1000MS Memory Limit: 10000K Description Farmer John has been elec ...
- 最小生成树-Prim&Kruskal
Prim算法 算法步骤 S:当前已经在联通块中的所有点的集合 1. dist[i] = inf 2. for n 次 t<-S外离S最近的点 利用t更新S外点到S的距离 st[t] = true ...
- 邻接表c源码(构造邻接矩阵,深度优先遍历,广度优先遍历,最小生成树prim,kruskal算法)
graph.c #include <stdio.h> #include <stdlib.h> #include <limits.h> #include " ...
- poj1861 最小生成树 prim & kruskal
// poj1861 最小生成树 prim & kruskal // // 一个水题,为的仅仅是回味一下模板.日后好有个照顾不是 #include <cstdio> #includ ...
随机推荐
- 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 ...
- 三大特征提取器(RNN/CNN/Transformer)
目录 三大特征提取器 - RNN.CNN和Transformer 简介 循环神经网络RNN 传统RNN 长短期记忆网络(LSTM) 卷积神经网络CNN NLP界CNN模型的进化史 Transforme ...
- springboot2.0+ 使用拦截器导致静态资源被拦截
在spring1.0+的版本中,配置拦截器后是不会拦截静态资源的.其配置如下: @Configuration public class WebMvcConfig extends WebMvcConfi ...
- Angular template ng-template/container/content
1. ng-template 形式:<ng-template>...</ng-template> 默认ng-template中的内容会隐藏; 可通过[ngIf]来控制内容显示隐 ...
- 夯实Java基础系列13:深入理解Java中的泛型
目录 泛型概述 一个栗子 特性 泛型的使用方式 泛型类 泛型接口 泛型通配符 泛型方法 泛型方法的基本用法 类中的泛型方法 泛型方法与可变参数 静态方法与泛型 泛型方法总结 泛型上下边界 泛型常见面试 ...
- 十大排序算法JavaScript实现总结
花费了几周的时间断断续续的练习和模仿与使用JavaScript代码实现了十大排序算法. 里面有每种算法的动图和静态图片演示,看到图片可以自己先按照图片的思路实现一下. github中正文链接,点击查看 ...
- 【Elasticsearch 搜索之路】(一)什么是 Elasticsearch?
本篇文章对 Elasticsearch 做了基本介绍,在后续将通过专栏的方式持续更新,本系列以 Elasticsearch7 作为主要的讲解版本,欢迎各位大佬指正,共同学习进步! 一般涉及大型数据库的 ...
- Docker5-docker私库的搭建及常用方法-harbor-registry方式
一.简介 1.官方已经提供registry镜像为什么还需要用harbor 1)registry缺少镜像清理机制,可以push但是不能删除,耗费空间 2)registry缺乏相应的扩展机制 3)harb ...
- 游戏服务器和Web服务器的区别
用Go语言写游戏服务器也有一个多月了,也能够明显的感受到两者的区别.这篇文章就是想具体的聊聊其中的区别.当然,在了解区别之间,我们先简单的了解一下Go语言本身. 1. Go语言的特点 Go语言跟其他的 ...
- mobaxterm使用手册
Mobaxterm V14使用手册 文章出处 https://blog.51cto.com/937761/2372598 简介 MobaXterm 一款Windows系统下全功能终端软件.以下将 ...