graph

图的基本算法主要有四个:BFS、DFS、最短路径、最小生成树算法、拓扑排序算法。算法比较多,并且图的表达形式也非常多,所以我们可以维护一种常用的图结构来作为构成图的模板,将不熟悉的图结构转化为我们常用的图结构。

图结构模板

``` c++ "图的总体结构" struct Graph{ unordered_map nodes; unordered_set edges; };

图主要有点集`nodes`和边集`edges`构成,这里选用unordered结构的原因是因为hashmap比普通的map查询更快。`Node`是基本点的wrapper,这么做是为了方便算法编写。

``` c++ "node的基本结构"
struct Node{
    int value;//基本点的序号,即点1、点2...
    int in;//该点的入度
    int out;//该店的出度
    unordered_set<Node*,HashNodeptr,EqualNodeptr> nexts;//该set表示由该点出发可以到达的第一个邻居节点
    unordered_set<Edge*,HashEdgePtr,EqualEdgePtr> edges;//该set表示属于该点的边,即从该点出发的边
    Node (int v):value(v){
        in=0;
        out=0;
    }
}

``` c++ "Edge的基本结构" struct Edge{ int weight;//该边的权重 Node from;//该边的出发节点 Node to;//改边的目的节点 Edge(int w,Node f,Node t):weight(w),from(f),to(t){} }

因为结构体相互嵌套的原因,所以必须使用指针。

使用上面的图结构,只需要写一个转换接口就可以使用。假设采用这样一种图接口:一个二维数组:每一行表示一个节点:`[Node from,Node to,int weight]`,下面是一个栗子。

``` c++ "转化函数"
Graph* Transform(vector<vecotr<int>>& nums){
    Graph* graph=new Graph;
    for(auto elem:nums){
        int from=elem[0];
        int to=elem[1];
        int weight=elem[2];
        //没有构造过该节点,将该节点加入nodes
        //比如[1,2,0]和[2,3,0],在处理第一个边时节点2已经构造好了,只需要更新节点相关信息即可
        if(graph->nodes.find(from)==graph->nodes.end()){
            graph->nodes.insert(pair<int,Node>(from,*new Node(from)));
        }
        if(graph->nodes.find(to)==graph->nodes.end()){
            graph->nodes.insert(pair<int,Node>(to,*new Node(to)));
        }
        //这里不能使用at,因为返回值copy,除非对应的value是指针,无所谓copy与否,使用find返回迭代器直接修改
        auto from_iter=graph->nodes.find(from);
        auto to_iter=graph->nodes.find(to);
        //更新入度出度
        from_iter->second.out++;
        to_iter->second.in++;
        //更新邻居节点
        from_iter->second.nexts.insert(&to_iter.second);
        Edge* edge=new Edge(weight,&from_iter.second,&to_iter.second);
        from_iter->second.edges.insert(edge);
        graph->edges.insert(*edge);
        //一个节点处理完毕
    }
    return graph;
}

使用时只需要稍微修改上述模板即可使用该结构。

图结构java模板

``` java "图结构" public class Graph { HashMap nodes; HashSet edges; Graph(){ this.nodes=new HashMap<>(); this.edges=new HashSet<>(); } }

}

图遍历

图遍历两种常用的方法是DFSBFS,含义就不细说了,主要讲讲实现代码。

BFS

顾名思义,按照广度遍历,这里需要使用一个队列结构,以及一个hashset结构(防止重复将邻居节点放入队列中)

拓扑排序

这个有啥用?有一个典型应用场景:编译项目时,子项目的依赖问题,需要决定先后顺序,这时候就能用到拓扑排序了。解决该问题时需要用到节点的入度与出度。

写代码时应考虑是否应该更改参数的相关数据,所以不希望更改时应加const,那么如何表示常量指针呢? 答案:const写在*前面就是常量指针,写在后面就是指针常量,比如const Graph* graph,graph是一个常量指针。

java版本:

``` java "拓扑排序java版" static List TopoSort(GraphTemp graph,int numCourse) { HashMap inMap=new HashMap<>(); Queue zeroInQueue=new LinkedList<>();

}

p算法

p算法就是首先随便选取一个点,然后选取该点权值最小的边,查看该边的to节点是否已经处理过,如果没有处理,将该点的所有边加入小根堆,继续选取最小的边

注意:map中的每个元素是一个pair,而不是一个pair迭代器,这一点不要弄混了

  • 简单来说p算法就是按点选,先随便选一个点,选取属于该点并且权值最小的边

  • 然后再看该边的to节点是否已经处理过,如果没有处理过,就将属于to节点的边加入候选边组,并且将该变加入结果集,不是是个最小边就要加入哦(只有边的to节点没处理才会加入,这是为了防止形成环,)

  • 然后再从候选边组选最小的边

最短路径

这里使用迪杰斯特拉算法,求一个节点到所有节点的路径

这个注册过的点表示源点至该点的最小距离已经确定,无需更改。

hash table相关的自定义hash function

Last updated

Was this helpful?