【数据结构与算法】图之最小生成树

时间:2022-12-31 11:40:28
最小生成树的基本概念
     一个有n个结点的连通图的生成树是原图的极小连通子图,它包含原图中的所有n个结点,并且有保持图连通的最少的边。

     如果无向连通图是一个带权图,那么它的所有生成树中必有一棵边的权值总和最小的生成树,我们称这棵生成树为最小代价生成树,简称最小生成树。

无向图和它的不同的生成树 

【数据结构与算法】图之最小生成树

     从最小生成树的定义可知,构造有n个结点的无向连通带权图的最小生成树,必须满足以下三条:
   (1)构造的最小生成树必须包括n个结点;
   (2)构造的最小生成树中有且只有n-1条边;
   (3)构造的最小生成树中不存在回路。
     构造最小生成树的方法有许多种,典型的构造方法有两种,一种称作普里姆(Prim)算法,另一种称作克鲁斯卡尔(Kruskal)算法。

8.5.2普里姆算法
1 普里姆算法思想
     假设G=(V,E)为一个带权图,其中V为带权图中结点的集合,E为带权图中边的权值集合。设置两个新的集合U和T,其中U用于存放带权图G的最小生成树的结点的集合,T用于存放带权图G的最小生成树的权值的集合。  
     普里姆算法思想是:令集合U的初值为U={u0}(即假设构造最小生成树时从结点u0开始),集合T的初值为T={}。从所有结点u∈U和结点v∈V-U的带权边中选出具有最小权值的边(u,v),将结点v加入集合U中,将边(u,v) 加入集合T中。如此不断重复,当U=V时则最小生成树构造完毕。此时集合U中存放着最小生成树结点的集合,集合T中存放着最小生成树边的权值集合。

普里姆算法构造最小生成树的过程

【数据结构与算法】图之最小生成树

2 普里姆函数设计
     这里我们令当弧头结点等于弧尾结点时权值等于0。
    函数的参数设计:
    普里姆函数应有两个参数:
     一个参数是图g,这里图g定义为邻接矩阵存储结构图类的对象;
     另一个参数是通过函数得到的最小生成树的结点数据和相应结点的边的权值数据closeVertex。


普里姆算法运行时数组lowCost的变化过程 :

【数据结构与算法】图之最小生成树

package cn.ls.prim;

import cn.ls.graph.AdjMWGraph;
/**
*
*普里姆算法类
*/
public class Prim{
static final int maxWeight = 9999;
/**
*
* @param g 邻接矩阵图类
* @param closeVertex 结点元素集合
* @throws Exception
*/
public static void prim(AdjMWGraph g, MinSpanTree[] closeVertex) throws Exception{
int n = g.getNumOfVertices();//结点个数
int minCost;
int[] lowCost = new int[n];
int k = 0;

for(int i = 1; i < n; i ++)
lowCost[i] = g.getWeight(0, i);//获取从头结点到各个结点的权值
MinSpanTree temp = new MinSpanTree();
temp.vertex = g.getValue(0);
closeVertex[0] = temp; //保存结点0
lowCost[0] = - 1; //标记结点0 设置首节点的权值为-1

for(int i = 1; i < n; i ++){
minCost = maxWeight;
//寻求当前最小权值边的弧头结点K.
for(int j = 1; j < n; j ++){
if(lowCost[j] < minCost && lowCost[j] > 0){
minCost = lowCost[j];
k = j;
}
}
MinSpanTree curr = new MinSpanTree();
curr.vertex = g.getValue(k); //保存结点k
curr.weight = minCost; //保存k的权值
closeVertex[i] = curr;
lowCost[k] = -1;
//根据加入集合U的结点K修改lowCost中的数值.
for(int j = 1; j < n; j ++){
if(g.getWeight(k, j) < lowCost[j])
lowCost[j] = g.getWeight(k, j);
}
}
}
}

package cn.ls.graph;
/**
*
*边信息行列权值类.
*/
public class RowColWeight{
public int row; //行下标
public int col; //列下标
public int weight; //权值

public RowColWeight(int r, int c, int w){
row = r;
col = c;
weight = w;
}
/**
* 创建矩阵图
* @param g 矩阵图类
* @param v 结点集合
* @param n 结点的个数
* @param rc 边信息集合
* @param e 边的个数
* @throws Exception
*/
public static void createGraph(AdjMWGraph g, Object[] v, int n, RowColWeight[] rc, int e) throws Exception{
for(int i = 0; i < n; i ++)
g.insertVertex(v[i]);
for(int k = 0; k < e; k ++)
g.insertEdge(rc[k].row, rc[k].col, rc[k].weight);
}
}

package cn.ls.graph;
/**
*
*邻接矩阵图类
*/
public class AdjMWGraph {
static final int maxWeight = 10000;

private SeqList vertices; // 存储结点的顺序表
private int[][] edge; // 存储边的二维数组
private int numOfEdges; // 边的个数

public AdjMWGraph(int maxV) { // 构造函数,maxV为结点个数
vertices = new SeqList(maxV);
edge = new int[maxV][maxV];
for (int i = 0; i < maxV; i++) {
for (int j = 0; j < maxV; j++) {
if (i == j)
edge[i][j] = 0;
else
edge[i][j] = maxWeight;
}
}
numOfEdges = 0;
}

public int getNumOfVertices() { // 返回结点个数
return vertices.size;
}

public int getNumOfEdges() { // 返回边的个数
return numOfEdges;
}

public Object getValue(int v) throws Exception {
// 返回结点v的数据元素
return vertices.getData(v);
}
/**
* 返回边<v1,v2>的权值
* @param v1 边的行下标
* @param v2 边的列下标
* @return
* @throws Exception
*/
public int getWeight(int v1, int v2) throws Exception {
if (v1 < 0 || v1 >= vertices.size || v2 < 0 || v2 >= vertices.size)
throw new Exception("参数v1或v2越界出错!");
return edge[v1][v2];
}

public void insertVertex(Object vertex) throws Exception {
// 插入结点
vertices.insert(vertices.size, vertex);
}
/**
* 插入边<v1,v2>,权值为weight
* @param v1 边的行下标
* @param v2 边的列下标
* @param weight 边的权值
* @throws Exception
*/
public void insertEdge(int v1, int v2, int weight) throws Exception {
if (v1 < 0 || v1 >= vertices.size || v2 < 0 || v2 >= vertices.size)
throw new Exception("参数v1或v2越界出错!");
edge[v1][v2] = weight; //置边的权值
numOfEdges++; //边的个数加1
}

public void deleteEdge(int v1, int v2) throws Exception {
//删除边<v1,v2>
if (v1 < 0 || v1 > vertices.size || v2 < 0 || v2 > vertices.size)
throw new Exception("参数v1或v2越界出错!");
if (edge[v1][v2] == maxWeight || v1 == v2)
throw new Exception("该边不存在!");

edge[v1][v2] = maxWeight; // 置边的权值为无穷大
numOfEdges--; // 边的个数减1
}

public int getFirstNeighbor(int v) throws Exception {
// 取结点v的第一个邻接结点。若存在返回该结点的下标序号,否则返回-1
if (v < 0 || v >= vertices.size)
throw new Exception("参数v越界出错!");
for (int col = 0; col < vertices.size; col++)
if (edge[v][col] > 0 && edge[v][col] < maxWeight)
return col;
return -1;
}

public int getNextNeighbor(int v1, int v2) throws Exception {
// 取结点v1的邻接结点v2后的邻接结点
// 若存在返回该结点的下标序号,否则返回-1
if (v1 < 0 || v1 >= vertices.size || v2 < 0 || v2 >= vertices.size)
throw new Exception("参数v1或v2越界出错!");
for (int col = v2 + 1; col < vertices.size; col++)
if (edge[v1][col] > 0 && edge[v1][col] < maxWeight)
return col;
return -1;
}
/**
* 深度优先遍历
* @param v
* @param visited
* @param vs
* @throws Exception
*/
private void depthFirstSearch(int v, boolean[] visited, Visit vs) throws Exception {
// 连通图以v为初始结点序号、访问操作为vs的深度优先遍历
// 数组visited标记了相应结点是否已访问过,0表示未访问,1表示已访问
vs.print(getValue(v)); // 访问该结点
visited[v] = true; // 置已访问标记

int w = getFirstNeighbor(v); // 取第一个邻接结点
while (w != -1) { // 当邻接结点存在时循环
if (!visited[w]) // 如果没有访问过
depthFirstSearch(w, visited, vs); // 以w为初始结点递归遍历
w = getNextNeighbor(v, w); // 取下一个邻接结点
}
}
/**
* 广度优先遍历
* @param v
* @param visited
* @param vs
* @throws Exception
*/
private void broadFirstSearch(int v, boolean[] visited, Visit vs) throws Exception {
// 连通图以v为初始结点序号、访问操作为vs的广度优先遍历
// 数组visited标记了相应结点是否已访问过,0表示未访问,1表示已访问
int u, w;
SeqQueue queue = new SeqQueue(); // 创建顺序队列queue

vs.print(getValue(v)); // 访问结点v
visited[v] = true; // 置已访问标记

queue.append(new Integer(v)); // 结点v入队列
while (!queue.isEmpty()) { // 队列非空时循环
u = ((Integer) queue.delete()).intValue(); // 出队列
w = getFirstNeighbor(u); // 取结点u的第一个邻接结点
while (w != -1) { // 当邻接结点存在时循环
if (!visited[w]) { // 若该结点没有访问过
vs.print(getValue(w)); // 访问结点w
visited[w] = true; // 置已访问标记
queue.append(new Integer(w)); // 结点w入队列
}

// 取结点u的邻接结点w的下一个邻接结点
w = getNextNeighbor(u, w);
}
}
}
/**
* 非连通图的深度优先遍历
* @param vs
* @throws Exception
*/
public void depthFirstSearch(Visit vs) throws Exception {
boolean[] visited = new boolean[getNumOfVertices()];
for (int i = 0; i < getNumOfVertices(); i++)
visited[i] = false; // 置所有结点均未访问过
for (int i = 0; i < getNumOfVertices(); i++)
// 对每个结点循环
if (!visited[i]) // 如果该结点未访问
depthFirstSearch(i, visited, vs);// 以结点i为初始结点深度优先遍历
}
/**
* 非连通图的广度优先遍历
* @param vs
* @throws Exception
*/
public void broadFirstSearch(Visit vs) throws Exception {
boolean[] visited = new boolean[getNumOfVertices()];
for (int i = 0; i < getNumOfVertices(); i++)
visited[i] = false; // 置所有结点均未访问过
for (int i = 0; i < getNumOfVertices(); i++)
// 对每个结点循环
if (!visited[i]) // 如果该结点未访问过
broadFirstSearch(i, visited, vs);// 以结点i为初始结点广度优先遍历
}
}

package cn.ls.prim;

import cn.ls.graph.AdjMWGraph;
import cn.ls.graph.RowColWeight;
/**
*
*测试类
*/
public class Exam8_3{
static final int maxVertices = 100;

public static void createGraph(AdjMWGraph g, Object[] v, int n, RowColWeight[] rc, int e) throws Exception{
for(int i = 0; i < n; i ++)
g.insertVertex(v[i]);
for(int k = 0; k < e; k ++)
g.insertEdge(rc[k].row, rc[k].col, rc[k].weight);
}

public static void main(String[] args){
AdjMWGraph g = new AdjMWGraph(maxVertices);
Character[] a = {new Character('A'),new Character('B'),new Character('C'),
new Character('D'),new Character('E'),new Character('F'),new Character('G')};
RowColWeight[] rcw = {new RowColWeight(0,1,50),new RowColWeight(1,0,50),
new RowColWeight(0,2,60),new RowColWeight(2,0,60),new RowColWeight(1,3,65),
new RowColWeight(3,1,65),new RowColWeight(1,4,40),new RowColWeight(4,1,40),
new RowColWeight(2,3,52),new RowColWeight(3,2,52),new RowColWeight(2,6,45),
new RowColWeight(6,2,45),new RowColWeight(3,4,50),new RowColWeight(4,3,50),
new RowColWeight(3,5,30),new RowColWeight(5,3,30),new RowColWeight(3,6,42),
new RowColWeight(6,3,42),new RowColWeight(4,5,70),new RowColWeight(5,4,70)};
int n = 7, e = 20;

try{
createGraph(g,a,n,rcw,e);
MinSpanTree[] closeVertex = new MinSpanTree[7];
Prim.prim(g, closeVertex);

System.out.println("初始顶点 = " + closeVertex[0].vertex);
for(int i = 1; i < n; i ++)
System.out.println("顶点 = " + closeVertex[i].vertex + " 边的权值 = " + closeVertex[i].weight);
}
catch (Exception ex){
ex.printStackTrace();
}
}
}

测试结果:

初始顶点 = A
顶点 = B  边的权值 = 50
顶点 = E  边的权值 = 40
顶点 = D  边的权值 = 50
顶点 = F  边的权值 = 30
顶点 = G  边的权值 = 42
顶点 = C  边的权值 = 45