算法-计算无向图中两个节点之间所有的路径

2024-03-11 10:08

本文主要是介绍算法-计算无向图中两个节点之间所有的路径,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

1、深度优先遍历

    1.1 深度优先遍历的定义

    深度优先搜索(Depth_First Search)遍历类似于树的先根遍历,是树的先根遍历的推广。

假设给定图G,图中所有顶点未曾被访问过,则深度优先搜索可以从图中某个顶点v出发,访问此顶点,然后依次从v的未被访问的邻接点出发深度优先遍历图,直至图中所有和v有路径相通的顶点都被访问到;若此时图中尚有顶点未被访问,则另选图中一个未曾被访问的顶点作起始点,重复上述过程,直至图中所有顶点都被访问到为止。这是一个递归的过程。

1.2 图的深度优先遍历的过程

假设给定图G,设x 是当前被访问顶点, 在对x 做过访问标记后, 选择一条从x出发的未检测过的边(x,y)。若发现顶点y 已访问过,则重新选择另一条从x 出发的未检测过的边,否则若顶点y未被访问过,沿边(x,y)到达未曾访问过的y,对y访问并将其标记为已访问过;然后从y开始搜索,直到搜索完从y 出发的所有路径,即访问完所有从y 出发可达的顶点之后,才回溯到顶点x,并且再选择一条从x 出发的未检测过的边。上述过程直至从x 出发的所有边都已检测过为止。此时,若x 不是源点,则回溯到在x 之前被访问过的顶点;否则图中所有和源点有路径相通的顶点(即从源点可达的所有顶点)都已被访问过,若图G 是连通图,则遍历过程结束,否则继续选择一个尚未被访问的顶点作为新源点,进行新的搜索过程。

2、求两点间所有路径的算法

                                                                                                              

                                                                                                                                                         图1 连通图G

                                                                                

图2 G的邻接表存储结构

假设简单连通图如图1 所示,那么它的邻接表存储结构如图2 所示。假设我们要找出结点3到结点6的所有路径,那么,我们就设结点3为起点,结点6为终点。我们需要的存储结构有:一个保存路径的栈、一个保存已标记结点的数组,那么找到结点3到结点6的所有路径步骤如下:

(1)我们建立一个存储结点的栈结构,将起点3 入栈,将结点3 标记为入栈状态;

(2)从结点3 出发, 找到结点3 的第一个非入栈状态的邻结点1,将结点1 标记为入栈状态;

(3)从结点1 出发, 找到结点1 的第一个非入栈状态的邻结点0,将结点0 标记为入栈状态;

(4)从结点0 出发, 找到结点0 的第一个非入栈状态的邻结点2,将结点2 标记为入栈状态;

(5)从结点2 出发, 找到结点2 的第一个非入栈状态的邻结点5,将结点5 标记为入栈状态;

(6)从结点5 出发, 找到结点5 的第一个非入栈状态的邻结点6,将结点6 标记为入栈状态;

(7)栈顶结点6 是终点, 那么, 我们就找到了一条起点到终点的路径,输出这条路径;

(8)从栈顶弹出结点6,将6 标记为非入栈状态;

(9)现在栈顶结点为5, 结点5 没有除终点外的非入栈状态的结点,所以从栈顶将结点5 弹出;

(10)现在栈顶结点为2,结点2 除了刚出栈的结点5 之外,还有非入栈状态的结点6,那么我们将结点6 入栈;

(11)现在栈顶为结点6,即找到了第二条路径,输出整个栈,即为第二条路径;

(12)重复步骤2-11,就可以找到从起点3 到终点6 的所有路径;

(13)栈为空,算法结束。


#include <stack>

#include <iostream>
using namespace std;
#define  NUM 10  
#define MAX_PATH 100  


struct Node  
{  
    int key;  
    int flag;  
    Node()  
    {  
        flag=0;  
    }  
};  


class Graph  
{  
    public:  
        //stack<int> searchStack;  
        int resultPath[MAX_PATH][NUM];  
        int result[NUM+1];//将result设为NUM+1,主要是为了避免发生B->D->B的事情  
      
        Node headNode;//起始节点  
        Node endNode;//终止节点  
        stack<Node> tempStack;  
        int pathNum;  
        int nPos;  
        bool Mark[NUM];  
      
    public:   
        Graph()  
        {  
            //将矩阵中的元素置为空  
            for (int i=0;i<NUM;i++)  
            {  
                for (int j=0;j<MAX_PATH;j++)  
                {  
                    resultPath[i][j]=0;  
                }  
                result[i]=0;  
      
                Mark[i]=false;  
            }  
            result[NUM]=0;  
            pathNum=0;  
            nPos=0;  
        }  
      
    void test()  
    {  
        //对应无向图的矩阵  
        int Matrix[NUM][NUM]={  
            {0,1,0,1,0}, //A  
            {1,0,1,0,1}, //B  
            {0,1,0,1,1}, //C  
            {1,0,1,0,0}, //D  
            {0,1,1,0,0}  //E  
        };  
        //开始节点  
        headNode.key=1;
        headNode.flag=1;  
  
        //结束节点  
        endNode.key=5; 
  
        FindAllPath(Matrix,headNode,endNode);  
        cout<<"顶点"<<headNode.key<<"到顶点"<<endNode.key<<"路径数目为:"<<pathNum<<endl;  
        for (int i=0;i<pathNum;i++)  
        {  
            cout<<"第"<<i<<"条: ";  
            for(int j=0;j<NUM;j++)  
            {  
                if (resultPath[i][j]==0)  
                {  
                    break;  
                }  
                cout<<resultPath[i][j]<<"  ";  
            }  
            cout<<endl;  
        }  
        int i=0;  
    }  


    void FindAllPath(int Matrix[NUM][NUM],Node startNodeKey,Node endNodeKey)  
    {  
        result[nPos]=startNodeKey.key;  //将当前元素放入结果集中  
        Mark[startNodeKey.key-1]=true;  //将访问标记为已访问  
        nPos++;  //结果集索引加1  
        while(nPos!=0)    
        {    
            int tempVal=result[nPos-1];//获取到最前面的元素    
            if (tempVal==endNodeKey.key)  //若当前元素为目标节点  
            {    
                for (int j=0;j<nPos;j++)    
                {    
                    resultPath[pathNum][j]=result[j];  //将结果集复制于最后的路径矩阵中  
                }    
                nPos--;  //回溯至上一个节点  
                result[nPos]=0;  //结果集对应索引置为空  
                pathNum++;  //路径数目加1  
                Mark[endNodeKey.key-1]=false;    
                break;    
            }    
            while(startNodeKey.flag<NUM)//利用flag来指示每次的元素的索引    
            {    
                if (Matrix[tempVal-1][startNodeKey.flag]==1)    
                {    
                    if (Mark[startNodeKey.flag]==false)//利用Mark来判断是否已经访问过该节点    
                    {    
                        Node tempNode;    
                        tempNode.key=startNodeKey.flag+1;    
                        FindAllPath(Matrix,tempNode,endNodeKey);//深度优先遍历算法,    
                    }               
                }    
                startNodeKey.flag++;//索引值相应的加一    
            }    
  
            if (startNodeKey.flag==NUM)//如果已经是到最后的邻居,说明访问结束,    
            {                           //将对应的值置为空    
                nPos--;  //再次向上回溯  
                startNodeKey.flag=0;  //将节点的索引置为空  
                result[nPos]=0;  //将结果集中对应的索引置为空  
                Mark[startNodeKey.key-1]=false;  //访问之后标记为未访问。因为下面的元素已经访问结束,便于下次的访问  
                break;    
            }    
        }    
    }  
};  

int main()
{
    Graph G;
    G.test();
    return 0;
}
 


private Set<Integer> nodes;
private Map<Integer, LinkedList<Integer>> links;
private Map<Link, Weight> weights;
nodes用于存储所有节点,不重复;links为邻接表;weights为边上的权值。

1、两点间最短路径(dijkstra算法)

     实现代码为:

public Path shortestPath(int a_node, int b_node)
    {
        //从a_node到其他节点的代价,有序排列
        Map<Integer, Path> cost = new HashMap<Integer, Path>();
        //sNodes初始为空
        LinkedList<Integer> sNodes = new LinkedList<Integer>();
        //qNodes初始为整个节点集
        LinkedList<Integer> qNodes = new LinkedList<Integer>();
        for (int node : nodes)
        {
            qNodes.add(node);
            cost.put(node, new Path(new Weight(Float.MAX_VALUE)));
        }
        cost.put(a_node, new Path(new Link(a_node, a_node), new Weight(0.0f)));
        //每次迭代qNodes集中被选中的节点
        int uNode;
        while (!qNodes.isEmpty())
        {
            uNode = extractMin(qNodes, cost);
            sNodes.add(uNode);
            if (uNode == b_node)//已经找到到b_node的最短路径
                break;
            for (int vNode : adjNodes(uNode))//代表qNodes集合中的某一节点
            {
                relax(cost, uNode, vNode);
            }
        }
        
        return cost.get(b_node);
    }
其中extractMin函数从qNodes集合中选取从源点出发代价最小的点。relax函数使用到了松弛(relaxation)技术。对于每个顶点v ∈ V,都设置一个属性d[v],用来描述从源点s到v的最短路径上权值的上界,称为最短路径估计(shortest-path estimate)。在松弛一条边(u,v)的过程中,要测试是否可以通过u,对迄今找到的到v的最短路径进行改进;如果可以,就更新。一次松弛操作可以减小最短路径估计的值。(摘自《算法导论》)
     如果只要找出源点到特定点的最短路径,可以提前跳出循环,如代码中所示。

2、两点间所有路径

    实现代码如下:

public LinkedList<Path> allPath(int a_node, int b_node, Weight max)
    {
        LinkedList<Path> apath = new LinkedList<Path>();
        int top_node; //即栈顶节点
        int cur_node;//当前的临接节点
        int next_node;//下一个入栈节点
        //遍历过程中使用的栈
        LinkedList<Integer> stack = new LinkedList<Integer>();
        //标记节点是否在栈内,避免回路
        Map<Integer, Integer> states = new HashMap<Integer, Integer>();
        //记录栈中路径长度
        Weight weight;
        for (int node : nodes)
        {
            states.put(node, 0);//0表示不在栈内
        }
        //初始化变量
        stack.add(a_node);
        weight = new Weight();//初始化为0
        states.put(a_node, 1);
        cur_node = -1; //定义为第一个临接节点的前一个
        while (!stack.isEmpty())
        {
            top_node = stack.getLast();
            weight = calWeight(stack);
            //路径是否超出最大权值
            if (weight.compareTo(max) > 0)
            {
                cur_node = stack.removeLast();
                states.put(cur_node, 0);
                continue;
            }
            //找到一条路径
            if (top_node == b_node)
            {
                apath.add(genPath(stack, weight));
                cur_node = stack.removeLast();
                states.put(cur_node, 0);
            }
            else
            {
                next_node = nextNode(top_node, cur_node, states);
                if (next_node < Integer.MAX_VALUE)
                {
                    stack.add(next_node);
                    states.put(next_node, 1);
                    cur_node = -1; //新节点入栈,要从头遍历它的所有临接节点
                }
                else
                {
                    cur_node = stack.removeLast();
                    states.put(cur_node, 0);
                }
            }
            
        }
        return apath;
        
    }

找出所有路径采用的是遍历的方法,以“深度优先”算法为基础。从源点出发,先到源点的第一个邻接点N00,再到N00的第一个邻接点N10,再到N10的第一个邻接点N20...当遍历到目标点时表明找到一条路径。上述代码以核心数据结构为一个栈,主要步骤:
①源点先入栈,当栈顶为终点时,即找到一条路径;

②栈顶元素N1出栈,新的栈顶元素为N2,从N2的所有邻接点中,以N1为起点,选取下一个N3入栈;

③为避免回路,已入栈元素要记录,选取新入栈顶点时应跳过已入栈的顶点

④当栈为空时,遍历完成。

这里我用了两个特殊的标记:一个值为-1,另一个值为Integer.MAX_VALUE。前者代表遍历某个顶点所有邻接点的开始位置,后者为结束位置。

详细的算法描述可以参考中国海洋大学熊建设、梁磊的论文《两点间所有路径的遍历算法》。

这篇关于算法-计算无向图中两个节点之间所有的路径的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/797498

相关文章

锐捷和腾达哪个好? 两个品牌路由器对比分析

《锐捷和腾达哪个好?两个品牌路由器对比分析》在选择路由器时,Tenda和锐捷都是备受关注的品牌,各自有独特的产品特点和市场定位,选择哪个品牌的路由器更合适,实际上取决于你的具体需求和使用场景,我们从... 在选购路由器时,锐捷和腾达都是市场上备受关注的品牌,但它们的定位和特点却有所不同。锐捷更偏向企业级和专

如何用Java结合经纬度位置计算目标点的日出日落时间详解

《如何用Java结合经纬度位置计算目标点的日出日落时间详解》这篇文章主详细讲解了如何基于目标点的经纬度计算日出日落时间,提供了在线API和Java库两种计算方法,并通过实际案例展示了其应用,需要的朋友... 目录前言一、应用示例1、天安门升旗时间2、湖南省日出日落信息二、Java日出日落计算1、在线API2

python获取当前文件和目录路径的方法详解

《python获取当前文件和目录路径的方法详解》:本文主要介绍Python中获取当前文件路径和目录的方法,包括使用__file__关键字、os.path.abspath、os.path.realp... 目录1、获取当前文件路径2、获取当前文件所在目录3、os.path.abspath和os.path.re

不懂推荐算法也能设计推荐系统

本文以商业化应用推荐为例,告诉我们不懂推荐算法的产品,也能从产品侧出发, 设计出一款不错的推荐系统。 相信很多新手产品,看到算法二字,多是懵圈的。 什么排序算法、最短路径等都是相对传统的算法(注:传统是指科班出身的产品都会接触过)。但对于推荐算法,多数产品对着网上搜到的资源,都会无从下手。特别当某些推荐算法 和 “AI”扯上关系后,更是加大了理解的难度。 但,不了解推荐算法,就无法做推荐系

【前端学习】AntV G6-08 深入图形与图形分组、自定义节点、节点动画(下)

【课程链接】 AntV G6:深入图形与图形分组、自定义节点、节点动画(下)_哔哩哔哩_bilibili 本章十吾老师讲解了一个复杂的自定义节点中,应该怎样去计算和绘制图形,如何给一个图形制作不间断的动画,以及在鼠标事件之后产生动画。(有点难,需要好好理解) <!DOCTYPE html><html><head><meta charset="UTF-8"><title>06

康拓展开(hash算法中会用到)

康拓展开是一个全排列到一个自然数的双射(也就是某个全排列与某个自然数一一对应) 公式: X=a[n]*(n-1)!+a[n-1]*(n-2)!+...+a[i]*(i-1)!+...+a[1]*0! 其中,a[i]为整数,并且0<=a[i]<i,1<=i<=n。(a[i]在不同应用中的含义不同); 典型应用: 计算当前排列在所有由小到大全排列中的顺序,也就是说求当前排列是第

csu 1446 Problem J Modified LCS (扩展欧几里得算法的简单应用)

这是一道扩展欧几里得算法的简单应用题,这题是在湖南多校训练赛中队友ac的一道题,在比赛之后请教了队友,然后自己把它a掉 这也是自己独自做扩展欧几里得算法的题目 题意:把题意转变下就变成了:求d1*x - d2*y = f2 - f1的解,很明显用exgcd来解 下面介绍一下exgcd的一些知识点:求ax + by = c的解 一、首先求ax + by = gcd(a,b)的解 这个

综合安防管理平台LntonAIServer视频监控汇聚抖动检测算法优势

LntonAIServer视频质量诊断功能中的抖动检测是一个专门针对视频稳定性进行分析的功能。抖动通常是指视频帧之间的不必要运动,这种运动可能是由于摄像机的移动、传输中的错误或编解码问题导致的。抖动检测对于确保视频内容的平滑性和观看体验至关重要。 优势 1. 提高图像质量 - 清晰度提升:减少抖动,提高图像的清晰度和细节表现力,使得监控画面更加真实可信。 - 细节增强:在低光条件下,抖

hdu2544(单源最短路径)

模板题: //题意:求1到n的最短路径,模板题#include<iostream>#include<algorithm>#include<cstring>#include<stack>#include<queue>#include<set>#include<map>#include<stdio.h>#include<stdlib.h>#include<ctype.h>#i

【数据结构】——原来排序算法搞懂这些就行,轻松拿捏

前言:快速排序的实现最重要的是找基准值,下面让我们来了解如何实现找基准值 基准值的注释:在快排的过程中,每一次我们要取一个元素作为枢纽值,以这个数字来将序列划分为两部分。 在此我们采用三数取中法,也就是取左端、中间、右端三个数,然后进行排序,将中间数作为枢纽值。 快速排序实现主框架: //快速排序 void QuickSort(int* arr, int left, int rig