数据结构[4] -- 带权图

本文介绍了带权图的概念,并详细讲解了如何通过优先级队列实现最小生成树(MSTW)算法,包括顶点分类、边的剪除和具体算法实现。接着讨论了Dijkstra的最短路径算法,阐述了顶点分类、算法步骤和解析,并提供了代码片段。所有代码可在Github上查看。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

带权图 GraphW

最小生成树(MSTW)

有权图的最小生成树是用优先级队列来实现的,优先级队列 可以基于来实现,也可以基于数组实现。

方法

  1. 从一个顶点开始,放到树的集合中,循环执行下面步骤
  2. 找到从最新的顶点到其他顶点的的所有边,这些顶点不在树的集合中,将这些边放到优先级队列
  3. 找到权值最小的边,把它和它所到达的顶点都放到树的集合中

顶点分类

  • 已经被连接的顶点,它们是最小生成树的顶点
  • 还没有被链接的顶点,但是知道把他们连接到至少一个已知顶点的权重,
    这些叫做边缘顶点
  • 还不知道任何信息的顶点

边的剪除

在算法中,要确保优先级队列不能有连接在已在树中的的顶点的边,每次向树中增加顶点后,都要遍历优先级队列查找并删除这样的边,也就是优先级队列中应该只包含一条到达某个第二类顶点的边

putInPQ:保证优先级队列中应该只包含一条到达某个特定目标顶点的边

  1. 调用find()方法,寻找优先队列中是否存在可以到达指定节点的边,
    如果存在,返回其所在队列的位置,不存在则返回-1
  2. 存在:如果oldDist大于newDist,则在队列中删除removeN前者所在tempEdge,插入insert后者theEdge
    如果oldDist小于或等于newDist,则队列不作更改
  3. 不存在:直接insert插入Edge

算法具体实现

  1. 当前顶点currentVert放在树中
  2. 连接这个顶点的边放到优先级队列中putInPQ,但是只要一下任意一个条件满足,这条边就不可以放入队列中
    • 源点和终点相同
    • 终点在树中
    • 源点和终点之间没有边(邻接矩阵中对应的值为INFINITY

3.将权值最小的边从优先级队列中删除removeMin,把这条边和该边的终点加入树,并显示源点和终点。

代码片段

public void mstw(){
        currentVert=0;

        while (nTree<nVerts){
            vertexList[currentVert].isInTree=true;
            nTree++;

            for(int j=0;j<nVerts;j++){
                if(j==currentVert)
                    continue;
                if(vertexList[j].isInTree)
                    continue;
                int distance=adjMat[currentVert][j];
                if(distance==INFINITY)
                    continue;
                putInPQ(j,distance);
            }
            if(thePQ.size()==0){
                System.out.println("GRAPH NOT CONNECTED");
                return;
            }

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

            System.out.print(vertexList[sourceVert].label);
            System.out.print("->");
            System.out.print(vertexList[currentVert].label);
            System.out.print("  ");
        }

        for(int j=0;j<nVerts;j++)
            vertexList[j].isInTree=false;
    }

    public void putInPQ(int newVert,int newDist){
        int queueIndex=thePQ.find(newVert);
        if(queueIndex!=-1){
            Edge tempEdge=thePQ.peekN(queueIndex);
            int oldDist=tempEdge.distance;
            if(oldDist>newDist){
                thePQ.removeN(queueIndex);
                Edge theEdge=new Edge(currentVert,newVert,newDist);
                thePQ.insert(theEdge);
            }
        }else{
            Edge theEdge=new Edge(currentVert,newVert,newDist);
            thePQ.insert(theEdge);
        }
    }

最短路径(Dijkstra)

最短路径:寻找带权图中给定某两点间的最短路径(代码基于有向带权图)。

方法

  • 每次将一个新顶点放入树,用这个新顶点不断修正记录,在记录中只保留从源顶点到某个已知顶点的最低费用
  • 不断将新的顶点放入树,条件是从源点到那个城市的路线费用最低

顶点分类

  • 已经被连接的顶点,它们是已在树中
  • 还没有被链接的顶点,但是知道把他们连接到至少一个已知顶点的权重,这些叫做边缘顶点
  • 还不知道任何信息的顶点

算法步骤

  1. 将源顶点放入树中,并记录此顶点到其他可直达顶点的权值,不可直达为INFINITY
  2. 取权值最小的顶点,并分别记录源顶点到其他顶点(源顶点直达+源顶点经此顶点到达)的最小权值
  3. 确定第二个顶点,将其放入树中
  4. 循环2到3
  5. 到达目标顶点,结束

算法解析

  • sPath[]数组存储从源点开始到其他顶点(终点)的最短距离。

  • 不仅记录从源顶点到每个终点的最短路径,还应该记录走过的路径,即记录终点的父节点DisPar

    • C->E[140]C是起始顶点,E终止顶点,[150]表示从源节点到E的最短路径的权值和,C->E表示路径
  • path()选择源顶点放入树中,并记录sPath,开始循环

    1. 选择sPath[]数组的最小距离
    2. 把对应的顶点放入树中,这个顶点变成新顶点currentVert
    3. 根据currentVert变化,更新adjust_sPath()所有的sPath[]数组内容
  • adjust_sPath()当例程被调用的时候,currentVert被放入树中,startToCurrentsPath[]数组中的当前项。
    adjust_sPath()方法现在检测sPath[]数组的每一项,它使用循环计数器column依次指向每一个顶点,对于sPath[]数组的每一项,如果顶点不在树中,就做以下三件事:

    • 把当前顶点的距离startToCurrent加到从源顶点到当前顶点的距离上startToFringe
    • startToFringesPath[]数组当前项做做比较
    • 如果startToFringe更小,就替换当前项

代码片段

  public void path(){
          int startTree=0;
          vertexList[startTree].isInTree=true;
          nTree=1;
          for(int j=0;j<nVerts;j++){
              int tempDist=adjMat[startTree][j];
              sPath[j]=new DisPar(startTree,tempDist);
          }

          while (nTree<nVerts){
              int indexMin=getMin();
              int minDist=sPath[indexMin].distance;

              if(minDist==INFINITY){
                  System.out.println("There are unreachable vertices");
                  break;
              }else{
                  currentVert=indexMin;
                  startToCurreent=sPath[indexMin].distance;
              }

              vertexList[currentVert].isInTree=true;
              nTree++;
              adjust_sPath();
          }

          displayPaths();

          nTree=0;
          for(int j=0;j<nVerts;j++)
              vertexList[j].isInTree=false;
      }

      public void adjust_sPath(){
          int column=1;
          while (column<nVerts){
              if(vertexList[column].isInTree){
                  column++;
                  continue;
              }

              int currentToFringe=adjMat[currentVert][column];
              int startToFringe=startToCurreent+currentToFringe;
              int sPathDist=sPath[column].distance;

              if(startToFringe<sPathDist){
                  sPath[column].parentVert=currentVert;
                  sPath[column].distance=startToFringe;
              }
              column++;
          }
      }

Github

完整代码,我托管在Github上,如果对你有帮助,请给我点个star以示肯定和鼓励。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值