堆优化prim算法C语言,007 基于最小索引堆优化的 Prim 算法求最小生成树

两种 Prim 算法的区别

基于最小索引堆优化的 Prim 算法在堆中最多存储 v-1 条边,即每个节点最多存一条邻边到堆中;而 LazyPrim 算法会将一个节点的多条邻边存进堆中;

由于堆中存储边的数量从 e 变为 v,从而关于堆的操作的时间复杂度从 O(logE) 降为 O(logV);

v 是比 e 小的;

存储在文件中的图

8 16

4 5 .35

4 7 .37

5 7 .28

0 7 .16

1 5 .32

0 4 .38

2 3 .17

1 7 .19

0 2 .26

1 2 .36

1 3 .29

2 7 .34

6 2 .40

3 6 .52

6 0 .58

6 4 .93

优化的 Prim 算法 - O(ElogV)

void visit(int v)

访问节点 v,并把 v 所有相邻节点(除了已经纳入 MST 的)的最小权边维护进数组 edgeTo 中;

随着访问的节点越来越多,edgeTo 中有值的节点越来越多,在 edgeTo 中已经有值的节点的值在更新,更新成权更小的边;

核心算法逻辑描述

在探索图的过程中,把探索到的边的权值更新在以节点为横坐标的一维数组中,

从 0 开始探索;

每次探索完了,取出权最小的边纳入 MST;

然后以新纳入的边的另一端开始新的探索;

要点

最小索引堆 ipq 在带权无向图中的几何意义是:除了已经纳入 MST 的边,已经 visit 到的边;

每次从最小索引堆 ipq 删除的边就是要纳入 MST 的;

最小索引堆 ipq 中存的是遍历到某个程度时,节点当前探索到的权最小的邻边的权值,存储在最小索引堆的 data 数组中,data 数组的每一格表示图的一个节点;

data 数组中的值根据大小,以最小堆的结构维护在数组 indexes 中;

indexes 数组中元素的个数,对应的是最小堆中元素的个数,对应到图中是涉及到(不是被 visit 到)的点的数目;

package _08._05;

import java.util.Vector;

// 使用优化的Prim算法求图的最小生成树

public class PrimMST {

private WeightedGraph G; // 图的引用

private IndexMinHeap ipq; // 最小索引堆, 算法辅助数据结构

private Edge[] edgeTo; // 访问的点所对应的边, 算法辅助数据结构

private boolean[] marked; // 标记数组, 在算法运行过程中标记节点i是否被访问

private Vector> mst; // 最小生成树所包含的所有边

private Number mstWeight; // 最小生成树的权值

// 构造函数, 使用Prim算法求图的最小生成树

public PrimMST(WeightedGraph graph){

G = graph;

assert( graph.E() >= 1 );

ipq = new IndexMinHeap(graph.V());

// 算法初始化

marked = new boolean[G.V()];

edgeTo = new Edge[G.V()];

for( int i = 0 ; i < G.V() ; i ++ ){

marked[i] = false;

edgeTo[i] = null;

}

mst = new Vector>();

// 核心算法 - 优化过的 Prim 算法

visit(0);

while( !ipq.isEmpty() ){

/**

* v 是已经遍历过的边中权最小的边那头的端点(不属于 MST 的那头),这个端点 v 是将要纳入 MST 的点, 这个端点 v 当前正在考虑的边

* edgeTo[v] 是要纳入 MST 的边;

*/

int v = ipq.extractMinIndex();

// edgeTo[v] 是端点 v 当前正在考虑的边;

assert( edgeTo[v] != null );

mst.add( edgeTo[v] );

visit( v );

}

// 计算最小生成树的权值

mstWeight = mst.elementAt(0).wt();

for( int i = 1 ; i < mst.size() ; i ++ )

mstWeight = mstWeight.doubleValue() + mst.elementAt(i).wt().doubleValue();

}

/**

* v 是已经纳入 MST 中的节点;

* 对 v 的每条边,取到其另一个端点 w;

* 对还没有纳入 MST 的 w:

* 如果 w 的任何一条临边是否可以纳入 MST 都没有被考虑过:

* 先把从 v 指向 w 的边 e 作为 w 当前考虑的边维护进数组 edgeTo 中;

* 再在最小索引堆中维护 w 节点当前考虑的边的权;

* 如果已经有临接于 w 的边被考虑过,并且从 v 指向 w 的边 e 的权比当前 w 正在考虑的边的权小:

* 更新 w 正在考虑的边为 e;

* 在最小索引堆中更新 w 当前考虑的临边的权;

*

* 对于第一个切分中的属于 MST 的唯一的点,比如点 v, 经过 visit(int v),v 的所有临边那头的点都把和 v 之间的边 e 的权视为最小的,随着

* 图中的点不断的被 visit(int v), 已经维护了最小权边的点会更新其维护的最小权边;

* @param v

*/

void visit(int v){

assert !marked[v];

marked[v] = true;

// 将和节点v相连接的未访问的另一端点, 和与之相连接的边, 放入最小堆中

for( Object item : G.adj(v) ){

Edge e = (Edge)item;

int w = e.other(v);

// 如果边的另一端点未被访问

if( !marked[w] ){

// 如果从没有考虑过这个端点, 直接将这个端点和与之相连接的边加入索引堆

if( edgeTo[w] == null ){

edgeTo[w] = e;

ipq.insert(w, e.wt());

}

// 如果曾经考虑这个端点, 但现在的边比之前考虑的边更短, 则进行替换

else if( e.wt().compareTo(edgeTo[w].wt()) < 0 ){

edgeTo[w] = e;

ipq.change(w, e.wt());

}

}

}

}

// 返回最小生成树的所有边

Vector> mstEdges(){

return mst;

}

// 返回最小生成树的权值

Number result(){

return mstWeight;

}

// 测试 Prim

public static void main(String[] args) {

String filename = "testG1.txt";

int V = 8;

SparseWeightedGraph g = new SparseWeightedGraph(V, false);

ReadWeightedGraph readGraph = new ReadWeightedGraph(g, filename);

// Test Prim MST

System.out.println("Test Prim MST:");

PrimMST primMST = new PrimMST(g);

Vector> mst = primMST.mstEdges();

for( int i = 0 ; i < mst.size() ; i ++ )

System.out.println(mst.elementAt(i));

System.out.println("The MST weight is: " + primMST.result());

System.out.println();

}

}

测试

package _08._05;

public class Main {

// 测试我们实现的两种Prim算法的性能差距

// 可以看出这一节使用索引堆实现的Prim算法优于上一小节的Lazy Prim算法

public static void main(String[] args) {

String filename1 = "src/_08/_05/testG1.txt";

int V1 = 8;

// 文件读取

SparseWeightedGraph g1 = new SparseWeightedGraph(V1, false);

ReadWeightedGraph readGraph1 = new ReadWeightedGraph(g1, filename1);

System.out.println( filename1 + " load successfully.");

long startTime, endTime;

// Test Prim MST

System.out.println("Test Prim MST:");

startTime = System.currentTimeMillis();

PrimMST primMST1 = new PrimMST(g1);

endTime = System.currentTimeMillis();

System.out.println("Test for G1: " + (endTime-startTime) + "ms.");

System.out.println("Test for G1's mstWeight: " + primMST1.result());

}

}

输出:

src/_08/_05/testG1.txt load successfully.

Test Prim MST:

Test for G1: 3ms.

Test for G1's mstWeight: 1.81

辅助类

最小索引堆 - IndexMinHeap

package _08._05;

import java.lang.reflect.Array;

import java.util.*;

import java.lang.*;

// 最小索引堆

public class IndexMinHeap {

protected Item[] data; // 最小索引堆中的数据

protected int[] indexes; // 最小索引堆中的索引, indexes[x] = i 表示索引i在x的位置

protected int[] reverse; // 最小索引堆中的反向索引, reverse[i] = x 表示索引i在x的位置

protected int count;

protected int capacity;

// 构造函数, 构造一个空堆, 可容纳capacity个元素

public IndexMinHeap(int capacity){

data = (Item[])new Comparable[capacity+1];

indexes = new int[capacity+1];

reverse = new int[capacity+1];

for( int i = 0 ; i <= capacity ; i ++ )

reverse[i] = 0;

count = 0;

this.capacity = capacity;

}

// 返回索引堆中的元素个数

public int size(){

return count;

}

// 返回一个布尔值, 表示索引堆中是否为空

public boolean isEmpty(){

return count == 0;

}

// 向最小索引堆中插入一个新的元素, 新元素的索引为i, 元素为item

// 传入的i对用户而言,是从0索引的

public void insert(int i, Item item){

assert count + 1 <= capacity;

assert i + 1 >= 1 && i + 1 <= capacity;

// 再插入一个新元素前,还需要保证索引i所在的位置是没有元素的。

assert !contain(i);

i += 1;

data[i] = item;

indexes[count+1] = i;

reverse[i] = count + 1;

count ++;

shiftUp(count);

}

// 从最小索引堆中取出堆顶元素, 即索引堆中所存储的最小数据

public Item extractMin(){

assert count > 0;

Item ret = data[indexes[1]];

swapIndexes( 1 , count );

reverse[indexes[count]] = 0;

count --;

shiftDown(1);

return ret;

}

// 从最小索引堆中取出堆顶元素的索引

public int extractMinIndex(){

assert count > 0;

int ret = indexes[1] - 1;

swapIndexes( 1 , count );

reverse[indexes[count]] = 0;

count --;

shiftDown(1);

return ret;

}

// 获取最小索引堆中的堆顶元素

public Item getMin(){

assert count > 0;

return data[indexes[1]];

}

// 获取最小索引堆中的堆顶元素的索引

public int getMinIndex(){

assert count > 0;

return indexes[1]-1;

}

// 看索引i所在的位置是否存在元素

boolean contain( int i ){

assert i + 1 >= 1 && i + 1 <= capacity;

return reverse[i+1] != 0;

}

// 获取最小索引堆中索引为i的元素

public Item getItem( int i ){

assert contain(i);

return data[i+1];

}

// 将最小索引堆中索引为i的元素修改为newItem

public void change( int i , Item newItem ){

assert contain(i);

i += 1;

data[i] = newItem;

// 有了 reverse 之后,

// 我们可以非常简单的通过reverse直接定位索引i在indexes中的位置

shiftUp( reverse[i] );

shiftDown( reverse[i] );

}

// 交换索引堆中的索引i和j

// 由于有了反向索引reverse数组,

// indexes数组发生改变以后, 相应的就需要维护reverse数组

private void swapIndexes(int i, int j){

int t = indexes[i];

indexes[i] = indexes[j];

indexes[j] = t;

reverse[indexes[i]] = i;

reverse[indexes[j]] = j;

}

//********************

//* 最小索引堆核心辅助函数

//********************

// 索引堆中, 数据之间的比较根据data的大小进行比较, 但实际操作的是索引

private void shiftUp(int k){

while( k > 1 && data[indexes[k/2]].compareTo(data[indexes[k]]) > 0 ){

swapIndexes(k, k/2);

k /= 2;

}

}

// 索引堆中, 数据之间的比较根据data的大小进行比较, 但实际操作的是索引

private void shiftDown(int k){

while( 2*k <= count ){

int j = 2*k;

if( j+1 <= count && data[indexes[j+1]].compareTo(data[indexes[j]]) < 0 )

j ++;

if( data[indexes[k]].compareTo(data[indexes[j]]) <= 0 )

break;

swapIndexes(k, j);

k = j;

}

}

// 测试 IndexMinHeap

public static void main(String[] args) {

int N = 1000000;

IndexMinHeap indexMinHeap = new IndexMinHeap(N);

for( int i = 0 ; i < N ; i ++ )

indexMinHeap.insert( i , (int)(Math.random()*N) );

}

}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值