带权图

看了一天的带权图,终于是理解了。不过还只是理解而已。
因为本人一直追随着倪升武的博客,但是可能由于本人算法基础较差,因此在“带权图”的算法的理解上,走了好多弯路(再次证明本人的无能啊啊啊,哈哈)。

带权图

在图定义的基础上,然后将每个边加上一定的权重,这就构成了带权图。其实在带权图这部分,重要的是其2个重要的问题:

  • 最小生成树问题
  • 最短路径问题

下面就这2个问题分别解释一下

最小生成树问题

注意:下面我们讨论的图都是连通图

定义:在中我们也有最小生成树这个概念。而在带权图中的最小生成树,指的是这棵树不仅要满足该图对应的非带权图的最小生成树的条件,还要求边的权值和最小。
算法的过程:
首先我们要假定2个容器,一个树容器,一个优先级队列的容器。我们的任务就是从放有边的队列容器中取出边放到树容器中。当树容器的顶点数目等于图的顶点的数目时,树容器内部放置的就是我们需要的最小生成树的边。而放置的过程就是我们产生最小生成树的过程。

  • 1.选定一个顶点为源,然后将该源相关的所有Edge(成员有src:起始顶点,des:末尾顶点,distance:边的权值)添加进优先级队列容器里。当然,添加时,我们会控制每个Edge在队列中的顺序,使其按照权值来进行有序排列
  • 2.从队列容器中找到权值最小的Edge,然后将其取出并放入树容器中。并且将我们所取的这个Edge对象的des顶点设置为当前节点。
  • 3.因为第二步中当前顶点改变了,因此我们再重复第1步和第2步的操作,直至树容器内部Edge的数目=图的顶点数目-1。

以上就是这个算法的基本过程。下面是取自倪升武的博客的代码。我会在其基础上再详细的添加一些注释帮助大家理解。

//边界路径类,主要记录了边的始末顶点,以及边的权值
class Edge {
    public int srcVert; //index of vertex starting edge
    public int destVert; //index of vertex ending edge
    public int distance; //distance from src to dest

    public Edge(int sv, int dv, int d) {
        srcVert = sv;
        destVert = dv;
        distance = d;
    }
}

//自定义优先队列,用来存储边
class PriorityQ {
    private final int SIZE = 20;
    private Edge[] queArray; //存储边界的数组
    private int size;

    public PriorityQ() {
        queArray = new Edge[SIZE];
        size = 0;
    }
    //从这个方法我们可以看出,queArray这个优先级队列是按照权值从大到小的顺序排列的。队列的最后是权值最小的Edge。
    public void insert(Edge item) { //有序的插入边界
        int j;
        for(j = 0; j < size; j++) { //找到插入的位置,从0到size-1,逐渐减小
            if(item.distance >= queArray[j].distance) 
                break;
        }
        //比item.distance小的往后挪一位,给腾出个空间
        for(int k = size-1; k >= j; k--) {
            queArray[k+1] = queArray[k];
        }
        queArray[j] = item; //插入item
        size++;
    }

    public Edge removeMin() { //删除最小的边界并返回
                                //这里的‘删除’,是将标志队列数目的size减1,这样最后一个元素的索引值就不在我们的使用范围之内了。也就达到了删除的效果。下面这行注释的代码是我自己添加的,可以有助于大家的理解。
        //queArray[size-1] = 0;
        return queArray[--size];
    }

    public void removeN(int n) { //删除n位置的边界
        for(int j = n; j < size-1; j++) {
            queArray[j] = queArray[j+1];
        }
        size--;
    }

    public Edge peekMin() { //返回最小边界,不删除
        return queArray[size-1];
    }

    public Edge peekN(int n) { //返回n位置的边界
        return queArray[n];
    }

    public int size() {
        return size;
    }

    public boolean isEmpty() {
        return (size == 0);
    }

    public int find(int findDex) { //寻找特定disVert的边界索引
        for(int j = 0; j < size; j++) {
            if(queArray[j].destVert == findDex)
                return j;
        }
        return -1;
    }
}

//带权图类
public class WeightedGraph {
    private final int MAX_VERTS = 20; //最大顶点数
    private final int INFINITY = 100000; //最远距离...表示无法达到
    private Vertex[] vertexArray; //存储顶点的数组
    private int adjMat[][]; //存储顶点之间的边界
    private int nVerts; //顶点数量
    private int currentVert; //当前顶点索引
    private PriorityQ thePQ; //存储边的优先级队列
    private int nTree; //最小生成树中的顶点数量

    public WeightedGraph() {
        vertexArray = new Vertex[MAX_VERTS];
        adjMat = new int[MAX_VERTS][MAX_VERTS];
        for(int i = 0; i < MAX_VERTS; i++) {
            for(int j = 0; j < MAX_VERTS; j++) {
                adjMat[i][j] = INFINITY; //初始化所有边界无穷远
            }
        }
        thePQ = new PriorityQ();
    }

    public void addVertex(char lab) { //添加顶点
        vertexArray[nVerts++] = new Vertex(lab);
    }

    public void addEdge(int start, int end, int weight) {//添加带权边
        adjMat[start][end] = weight;
        adjMat[end][start] = weight;
    }

    public void displayVertex(int v) {
        System.out.print(vertexArray[v].label);
    }

    /*
     * 带权图的最小生成树,要选择一条最优的路径
     */
    public void MinSpanningTree() {
        currentVert = 0; //从0开始
        while(nTree < nVerts-1) { //当不是所有节点都在最小生成树中时
            //isInTree是上一节Vertex类中新添加的成员变量 private boolean isInTree;
               //表示有没有加入到树中,初始化为false
vertexArray[currentVert].isInTree = true; //将当前顶点加到树中
            nTree++;

            //往PQ中插入与当前顶点相邻的一些边界
            for(int i = 0; i < nVerts; i++) {
                if(i == currentVert) //如果是本顶点,跳出
                    continue;
                if(vertexArray[i].isInTree) //如果顶点i已经在树中,跳出
                    continue;
                int distance = adjMat[currentVert][i]; //计算当前顶点到i顶点的距离
                if(distance == INFINITY) 
                    continue; //如果当前顶点与i顶点无穷远,跳出
                putInPQ(i, distance); //将i节点加入PQ中
            }

            if(thePQ.size() == 0) { //如果PQ为空,表示图不连接
                System.out.println("Graph not connected!");
                return;
            }

            Edge theEdge = thePQ.removeMin();
            int sourceVert = theEdge.srcVert;
            currentVert = theEdge.destVert;

            System.out.print(vertexArray[sourceVert].label);//这里就是一步步打印最小生成树的路径
            System.out.print(vertexArray[currentVert].label);
            System.out.print(" ");
        }
    }
    //这个方法是将一个Edge放入优先级队列,保证队列中每个Edge的des顶点是不同的。
    private void putInPQ(int newVert, int newDist) {
        int queueIndex = thePQ.find(newVert);//判断PQ中是否已经有到相同目的顶点的边界
        if(queueIndex != -1) { //如果有则与当前顶点到目的顶点的距离作比较,保留短的那个
            Edge tempEdge = thePQ.peekN(queueIndex);//get edge
            int oldDist = tempEdge.distance;
            if(oldDist > newDist) { //如果新的边界更短
                thePQ.removeN(queueIndex); //删除旧边界
                Edge theEdge = new Edge(currentVert, newVert, newDist);
                thePQ.insert(theEdge);
            }
        }
        else { //如果PQ中没有到相同目的顶点的边界
            Edge theEdge = new Edge(currentVert, newVert, newDist);
            thePQ.insert(theEdge);//直接添加到PQ
        }
    }
}

简要说一下上面的工作流程:就是选定一个源,根据优先级队列有序排列这个特点,选取队列末尾这个权值最小的Edge加入树容器,再将该Edge的des顶点选定为新的源,重复上述的过程,直至得到最小生成树。

最短路径问题

所谓的最短路径,即2点之间的路径要求权值最小。

实现最短路径的算法有很多,比如Dijkstra算法,(迪杰斯特拉),Floyd(弗洛伊德)算法等。这里我们仅介绍Dijkstra算法。
建议往下阅读之前,首先阅读绿岩的这篇博客,尤其是里面的框图,我认为值得一读。

下面是倪升武的代码,如果不理解Dijkstra算法过程,下面的代码我估计你是不容易读明白的(大神请无视)。

另外,为了分析方便,下面的代码有些量未完全引入,完整代码请移步倪升武的博客

/************************** 最短路径问题 ****************************/
/**
 * path()方法执行真正的最短路径算法。
 */
public void path() { //寻找所有最短路径
    /*
     * 源点总在vertexArray[]数组下标为0的位置,path()方法的第一个任务就是把这个顶点放入树中。
     * 算法执行过程中,将会把其他顶点也逐一放入树中。把顶点放入树中的操作是设置一下标志位即可。
     * 并把nTree变量增1,这个变量记录了树中有多少个顶点。
     */
    int startTree = 0; //从vertex 0开始
    vertexArray[startTree].isInTree = true;
    nTree = 1;

    /*
     * path()方法把邻接矩阵的对应行表达的距离复制到sPath[]中,实际总是先从第0行复制
     * 为了简单,假定源点的下标总为0。最开始,所有sPath[]数组中的父节点字段为A,即源点。
     */
    for(int i = 0; i < nVerts; i++) {
        int tempDist = adjMat[startTree][i];
        //sPath中保存的都是到初始顶点的距离,所以父顶点默认都是初始顶点,后面程序中会将其修改
        sPath[i] = new DistPar(startTree, tempDist);
    }

    /*
     * 现在进入主循环,等到所有的顶点都放入树中,这个循环就结束,这个循环有三个基本动作:
     * 1. 选择sPath[]数组中的最小距离
     * 2. 把对应的顶点(这个最小距离所在列的题头)放入树中,这个顶点变成“当前顶点”currentVert
     * 3. 根据currentVert的变化,更新所有的sPath[]数组内容
     */
    while(nTree < nVerts) {
        //1. 选择sPath[]数组中的最小距离
        int indexMin = getMin(); //获得sPath中的最小路径值索引
        int minDist = sPath[indexMin].distance; //获得最小路径

        if(minDist == INFINITY) {
            System.out.println("There are unreachable vertices");
            break;
        }
        //2. 把对应的顶点(这个最小距离所在列的题头)放入树中,这个顶点变成“当前顶点”currentVert
        else { //reset currentVert
            currentVert = indexMin;
            startToCurrent = sPath[indexMin].distance;
        }
        vertexArray[currentVert].isInTree = true;
        nTree++;
        //3. 根据currentVert的变化,更新所有的sPath[]数组内容
        adjust_sPath();
    }
    displayPaths();

    nTree = 0;
    for(int i = 0; i < nVerts; i++) {
        vertexArray[i].isInTree = false;
    }
}

//获取sPath中最小路径的索引
private int getMin() {
    int minDist = INFINITY;
    int indexMin = 0;
    for(int i = 0; i < nVerts; i++) {
        if(!vertexArray[i].isInTree && sPath[i].distance < minDist) {
            minDist = sPath[i].distance;
            indexMin = i;
        }
    }
    return indexMin;
}

/*调整sPath中存储的对象的值,即顶点到初始顶点的距离,和顶点的父顶点
 * 这是Dijkstra算法的核心
 */
private void adjust_sPath() {
    int column = 1;
    while(column < nVerts) {
        if(vertexArray[column].isInTree) {
            column++;
            continue;
        }
        int currentToFringe = adjMat[currentVert][column]; //获得当前顶点到其他顶点的距离,其他顶点不满足isInTree
        int startToFringe = startToCurrent + currentToFringe; //计算其他顶点到初始顶点的距离=当前顶点到初始顶点距离+当前顶点到其他顶点的距离
        int sPathDist = sPath[column].distance; //获得column处顶点到起始顶点的距离,如果不与初始顶点相邻,默认值都是无穷大

        if(startToFringe < sPathDist) {
            sPath[column].parentVert = currentVert; //修改其父顶点
            sPath[column].distance = startToFringe; //以及到初始顶点的距离
        }
        column++;
    }
}
//显示路径
private void displayPaths() {
    for(int i = 0; i < nVerts; i++) {
        System.out.print(vertexArray[i].label + "=");
        if(sPath[i].distance == INFINITY)
            System.out.print("infinity");
        else
            System.out.print(sPath[i].distance);
        char parent = vertexArray[sPath[i].parentVert].label;
        System.out.print("(" + parent + ") ");
    }
    System.out.println("");
}
  • 4
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值