加权无向图是一种为每条边关联一个权重值或是成本的图模型。
加权无向图中的边我们就不能简单的使用v-w两个顶点表示了,而必须要给边关联一个权重值,因此我们可以使用对象来描述一条边。
类名 | Digraph |
---|---|
构造方法 | Digraph(int V):创建一个包含V个顶点但不包含边的有向图 |
成员方法 | 1.public int V():获取图中顶点的数量 2.public int E():获取图中边的数量 3.public void addEdge(int v,int w):向有向图中添加一条边 v->w 4.public Queue adj(int v):获取由v指出的边所连接的所有顶点 5.private Digraph reverse():该图的反向图 |
成员变量 | 1.private final int V: 记录顶点数量 2.private int E: 记录边数量 3.private Queue[] adj: 邻接表 |
/**
* 加权无向图的边
* @date 2021/7/13 16:49
*/
public class Edge implements Comparable<Edge> {
// 顶点一
private final int v;
// 顶点二
private final int w;
// 当前边的权重
private final double weight;
//通过顶点v和w,以及权重weight值构造一个边对象
public Edge(int v, int w, double weight) {
this.v = v;
this.w = w;
this.weight = weight;
}
// 获取边的权重值
public double weight() {
return weight;
}
// 获取边上的一个点
public int either() {
return v;
}
// 获取边上除了顶点vertex外的另一个顶点
public int other(int vertex) {
if (vertex == v) {
//如果传入的顶点vertext是v,则返回另外一个顶点w
return w;
} else {
//如果传入的顶点vertext不是v,则返回v即可
return v;
}
}
@Override
public int compareTo(Edge that) {
int cmp;
if (this.weight() > that.weight()) {
//如果当前边的权重大于参数边that的权重,返回1
cmp = 1;
} else if (this.weight() < that.weight()) {
//如果当前边的权重小于参数边that的权重,返回-1
cmp = -1;
} else {
//如果当前边的权重等于参数边that的权重,返回0
cmp = 0;
}
return cmp;
}
}
类名 | EdgeWeightedGraph |
---|---|
构造方法 | EdgeWeightedGraph(int V):创建一个含有V个顶点的空加权无向图 |
成员方法 | 1.public int V():获取图中顶点的数量 2.public int E():获取图中边的数量 3.public void addEdge(Edge e):向加权无向图中添加一条边e 4.public Queue adj(int v):获取和顶点v关联的所有边 5.public Queue edges():获取加权无向图的所有边 |
成员变量 | 1.private final int V: 记录顶点数量 2.private int E: 记录边数量 3.private Queue[] adj: 邻接表 |
/**
* 加权无向图
*
* @date 2021/7/13 18:48
*/
public class EdgeWeightedGraph {
// 顶点总数
private final int V;
// 边的总数
private int E;
// 邻接表
private Queue<Edge>[] adj;
// 创建一个含有V个顶点的空加权无向图
public EdgeWeightedGraph(int V) {
this.V = V;
this.E = 0;
this.adj = new Queue[V];
for (int i = 0; i < adj.length; i++) {
adj[i] = new Queue<Edge>();
}
}
// 获取图中顶点的数量
public int V() {
return V;
}
// 获取图中边的数量
public int E() {
return E;
}
// 向加权无向图中添加一条边e
public void addEdge(Edge e) {
// 获取边中的一个顶点
int v = e.either();
// 获取边中的另一个顶点w
int w = e.other(v);
// 因为是无向图,所以边e需要同时出现在两个顶点的邻接表中
adj[v].enqueue(e);
adj[w].enqueue(e);
// 边的数量+1
E++;
}
// 获取和顶点v关联的所有边
public Queue<Edge> adj(int v) {
return adj[v];
}
// 获取加权无向图的所有边
public Queue<Edge> edges() {
Queue<Edge> allEdge = new Queue<>();
for (int v = 0; v < this.V; v++) {
for (Edge e : adj(v)) {
if (e.other(v) < v) {
allEdge.enqueue(e);
}
}
}
return allEdge;
}
}
/**
* @date 2021/7/13 21:01
*/
public class EdgeWeightedGraphTest {
public static void main(String[] args) {
EdgeWeightedGraph edgeWeightedGraph = new EdgeWeightedGraph(10);
Edge edge01 = new Edge(1, 2, 1);
Edge edge02 = new Edge(2, 3, 1);
Edge edge03 = new Edge(3, 4, 1);
edgeWeightedGraph.addEdge(edge01);
edgeWeightedGraph.addEdge(edge02);
edgeWeightedGraph.addEdge(edge03);
Queue<Edge> adj = edgeWeightedGraph.adj(2);
System.out.print("顶点2关联的所有边:");
for (Edge edge : adj) {
System.out.print(edge.toString()+" ");
}
System.out.println();
Queue<Edge> edges = edgeWeightedGraph.edges();
System.out.print("所有边:");
for (Edge edge : edges) {
System.out.print(edge.toString()+" ");
}
System.out.println();
System.out.println("顶点的个数:" + edgeWeightedGraph.V());
System.out.println("边的个数:" + edgeWeightedGraph.E());
}
}
(1)之前学习的加权图,我们发现它的边关联了一个权重,那么我们就可以根据这个权重解决最小成本问题,但如何才能找到最小成本对应的顶点和边呢?最小生成树相关算法可以解决。
(2)图的生成树是它的一棵含有其所有顶点的无环连通子图,一副加权无向图的最小生成树它的一棵权值(树中所有边的权重之和)最小的生成树
要从一副连通图中找出该图的最小生成树,需要通过切分定理完成。
切分: 将图的所有顶点按照某些规则分为两个非空且没有交集的集合。
横切边: 连接两个属于不同集合的顶点的边称之为横切边。
切分定理: 在一副加权图中,给定任意的切分,它的横切边中的权重最小者必然属于图中的最小生成树。
注意: :一次切分产生的多个横切边中,权重最小的边不一定是所有横切边中唯一属于图的最小生成树的边。
贪心算法或贪心思想采用贪心的策略,保证每次操作都是局部最优解的,从而使最后得到的结果是全局最优解。贪心算法没有固定的算法框架,算法设计的关键是贪心策略的选择。必须注意的是,贪心算法不是对所有问题都能得到整体最优解,选择的贪心策略必须具备无后效性(即某个状态以后的过程不会影响以前的状态,只与当前状态有关。)所以,对所采用的贪心策略一定要仔细分析其是否满足无后效性。
1)将问题分为若干子问题
2)找到合适的贪心策略
3)求解每一个子集的最优解
4)将局部最优解迭代成全局最优解
普里姆算法(Prim算法),图论中的一种算法,可在加权连通图里搜索最小生成树。意即由此算法搜索到的边子集所构成的树中,不但包括了连通图里的所有顶点,且其所有边的权值之和亦为最小。
切分定理
类名 | PrimMST |
---|---|
构造方法 | PrimMST(EdgeWeightedGraph G):根据一副加权无向图,创建最小生成树计算对象; |
成员方法 | 1.private void visit(EdgeWeightedGraph G, int v):将顶点v添加到最小生成树中,并且更新数据 2.public Queue edges():获取最小生成树的所有边 |
成员变量 | 1.private Edge[] edgeTo: 索引代表顶点,值表示当前顶点和最小生成树之间的最短边 2.private double[] distTo: 索引代表顶点,值表示当前顶点和最小生成树之间的最短边的权重 3.private boolean[] marked:索引代表顶点,如果当前顶点已经在树中,则值为true,否则为false 4.private IndexMinPriorityQueue pq:存放树中顶点与非树中顶点之间的有效横切边 |
public class PrimMST {
// 索引代表顶点,值代表当前顶点和最小生成树之间的最短边
private Edge[] edgeTo;
// 索引代表顶点,值表示当前顶点和最小生成树之间的最短边的权重
private double[] distTo;
// 索引代表顶点,如果当前顶点已经在树中,则值为true,否则为false
private boolean[] marked;
// 存放树中顶点与非树中顶点之间的有效横切边
private IndexMinPriorityQueue<Double> pq;
// 根据一副加权无向图,创建最小生成树计算对象
public PrimMST(EdgeWeightedGraph G) {
// 创建一个和图的顶点数一样大小的Edge数组,表示边
this.edgeTo = new Edge[G.V()];
// 创建一个和图的顶点数一样大小的double数组,表示权重,并且初始化数组中的内容为无穷大,无穷大表示不存在这样的边
this.distTo = new double[G.V()];
for (int i = 0; i < distTo.length; i++) {
distTo[i] = Double.POSITIVE_INFINITY;
}
// 创建一个和图的顶点数一样大小的boolean数组,表示当前顶点是否已经在树中
this.marked = new boolean[G.V()];
// 创建一个和图的顶点数一样大小的索引优先队列,存储有效横切边
this.pq = new IndexMinPriorityQueue<>(G.V());
// 默认让顶点0进入树中,但0顶点目前没有与树中其他的顶点相连接,因此初始化distTo[0]=0.0
distTo[0] = 0.0;
// 使用顶点0和权重0初始化pq
pq.insert(0, 0.0);
// 遍历有效边队列
while (!pq.isEmpty()) {
// 找到权重最小的横切边对应的顶点,加入到最小生成树中
visit(G, pq.delMin());
}
}
// 将顶点v添加到最小生成树中,并且更新数据
private void visit(EdgeWeightedGraph G, int v) {
// 把顶点v添加到树中
marked[v] = true;
// 遍历顶点v的邻接表,得到每一条边Edge e,
for (Edge e : G.adj(v)) {
// 边e的一个顶点是v,找到另外一个顶点w;
int w = e.other(v);
//检测是否已经在树中,如果在,则继续下一次循环,如果不在,则需要修正当前顶点w距离最小生成树的最小边edgeTo[w]以及它的权重distTo[w],还有有效横切边也需要修正
if (marked[w]) {
continue;
}
// 如果v-w边e的权重比目前distTo[w]权重小,则需要修正数据
if (e.weight() < distTo[w]) {
// 把顶点w距离最小生成树的边修改为e
edgeTo[w] = e;
// 把顶点w距离最小生成树的边的权重修改为e.weight()
distTo[w] = e.weight();
// 如果pq中存储的有效横切边已经包含了w顶点,则需要修正最小索引优先队列w索引关联的权 重值
if (pq.contains(w)) {
pq.changeItem(w, e.weight());
} else {
//如果pq中存储的有效横切边不包含w顶点,则需要向最小索引优先队列中添加v-w和其 权重值
pq.insert(w, e.weight());
}
}
}
}
// 获取最小生成树的所有边
public Queue<Edge> edges() {
// 创建队列
Queue<Edge> edges = new Queue<>();
// 遍历edgeTo数组,找到每一条边,添加到队列中
for (int i = 0; i < marked.length; i++) {
if (edgeTo[i] != null) {
edges.enqueue(edgeTo[i]);
}
}
return edges;
}
}
public class PrimMSTTest {
public static void main(String[] args) {
// 创建加权无向图
EdgeWeightedGraph edgeWeightedGraph = new EdgeWeightedGraph(10);
Edge edge00 = new Edge(0, 2, 5);
Edge edge01 = new Edge(1, 2, 1);
Edge edge02 = new Edge(2, 3, 2);
Edge edge03 = new Edge(3, 4, 4);
Edge edge04 = new Edge(1, 4, 6);
edgeWeightedGraph.addEdge(edge00);
edgeWeightedGraph.addEdge(edge04);
edgeWeightedGraph.addEdge(edge01);
edgeWeightedGraph.addEdge(edge02);
edgeWeightedGraph.addEdge(edge03);
// 构建PrimMST对象
PrimMST primMST = new PrimMST(edgeWeightedGraph);
// 获取最小生成树的边
Queue<Edge> edges = primMST.edges();
// 打印输出
for (Edge edge : edges) {
if(edge!=null){
System.out.println(edge.either() + "-" + edge.other(edge.either()) + "::" + edge.weight());
}
}
}
}
切分定理
类名 | KruskalMST |
---|---|
构造方法 | KruskalMST(EdgeWeightedGraph G):根据一副加权无向图,创建最小生成树计算对象; |
成员方法 | 1.public Queue edges():获取最小生成树的所有边 |
成员变量 | 1.private Queue mst:保存最小生成树的所有边 2.private UF_Tree_Weighted uf: 索引代表顶点,使用uf.connect(v,w)可以判断顶点v和顶点w是否在同一颗树中,使用uf.union(v,w)可以把顶点v所在的树和顶点w所在的树合并 3.private MinPriorityQueue pq: 存储图中所有的边,使用最小优先队列,对边按照权重进行排序 |
public class KruskalMST {
// 保存最小生成树的所有边
private Queue<Edge> mst;
// 索引代表顶点,使用uf.connect(v.w)可以判断顶点v和顶点w是否在同一颗树中,使用uf.union(v,w)可以把顶点v所在的树和顶点w所在的树合并
private UF_Tree_Weighted uf;
// 存储图中所有的边,使用最小优先队列,对边按照权重进行排序
private MinPriorityQueue<Edge> pq;
// 根据一副甲醛无向图,创建最小生成树计算对象
public KruskalMST(EdgeWeightedGraph G) {
// 初始化mst
this.mst = new Queue<Edge>();
// 初始化uf
this.uf = new UF_Tree_Weighted(G.V());
// 初始化pq
this.pq = new MinPriorityQueue<>(G.E()+1);
// 把图中所有的边存储到pq中
for (Edge edge : G.edges()) {
pq.insert(edge);
}
// 遍历pq队列,拿到最小权重的边,进行处理
while (!pq.isEmpty() && mst.size()<G.V()-1){
// 找到权重最小的边
Edge e = pq.delMin();
// 找到该边的两个顶点
int v = e.either();
int w = e.other(v);
// 判断这两个顶点是否已经在同一颗树中,如果在同一颗树中,则不对该边做处理,如果不在同一颗树中,则让这两个顶点属于的两颗树合并成一棵树
if (uf.connected(v,w)){
continue;
}
uf.union(v,w);
// 让边e进入到mst队列中
mst.enqueue(e);
}
}
// 获取最小生成树的所有边
public Queue<Edge> edges(){
return mst;
}
}
/**
* @date 2021/7/15 14:42
*/
public class KruskalMSTTest {
public static void main(String[] args) {
// 创建加权无向图
EdgeWeightedGraph edgeWeightedGraph = new EdgeWeightedGraph(10);
Edge edge00 = new Edge(0, 2, 5);
Edge edge01 = new Edge(1, 2, 1);
Edge edge02 = new Edge(2, 3, 2);
Edge edge03 = new Edge(3, 4, 4);
Edge edge04 = new Edge(1, 4, 6);
edgeWeightedGraph.addEdge(edge00);
edgeWeightedGraph.addEdge(edge04);
edgeWeightedGraph.addEdge(edge01);
edgeWeightedGraph.addEdge(edge02);
edgeWeightedGraph.addEdge(edge03);
// 构建PrimMST对象
KruskalMST kruskalMST = new KruskalMST(edgeWeightedGraph);
// 获取最小生成树的边
Queue<Edge> edges = kruskalMST.edges();
// 打印输出
for (Edge edge : edges) {
if(edge!=null){
System.out.println(edge.either() + "-" + edge.other(edge.either()) + "::" + edge.weight());
}
}
}
}