《C++实现数据结构》:图

图的内容非常多,边学习边写和图有关的算法也花费了不少时间与精力。写着写着最后都有近1000行代码了。orz……
接下来详细说说和图有关的都有啥。

一、相关概念

图分为无向图和有向图。
在无向图中,若图中任意一对顶点都是连通的,则称此图是连通图。
在有向图中,若任意一对顶点u和v间存在一条从u到v的路径和从v到u的路径,则称此图是强连通图。
无向图的一个极大连通子图称为该图的一个连通分量。
有向图的一个极大强连通子图称为该图的一个强连通分量。
在图的每条边上加上一个数字作权,也称代价,带权的图称为网。

二、存储结构

本次我使用的是邻接表表示。在邻接表中,为图的每个顶点u建立一个单链表,链表中每个结点代表一条边

//边结点类
template <typename T>
struct ENode{
    int vertex;         //边的一端顶点
    int adjVex;         //边的另一端顶点
    T weight;            //边的权值
    ENode<T> *next;

    ENode() { next = NULL; }
    ENode(int vertex,int adjvertex,T w,ENode<T> *nextArc) {
        this->vertex = vertex;
        adjVex = adjvertex;
        weight = w;
        next = nextArc;
    }
    operator T() const{ return weight; }

    bool operator <(const ENode<T> &rhs) const {
        return this->weight > rhs.weight;   //最小值优先
    }
};

其中边的一端顶点vertex成员变量是可以不用的,但是如果加上了之后,在后文有些算法中会方便很多。
另外,两个重载运算符都是为了方便后文的算法。

三、初识图类

先把整个图的类定义贴出来。

#include <iostream>
#include <set>
#include <queue>
#include <stack>
#include <string.h>

using namespace std;

const int INT_MAX = 0x7fff;

//图类
template <typename T>
class Graph{
private:
    ENode<T> **enodes;
    int n;                  //顶点个数
    int edges;              //边的个数
    int connectedCount;     //强连通分量个数
    int *id;                //由顶点索引的数组,存放顶点所属的连通分量标识符
    vector<int> *tarjanConnection;    //通过tarjan算法得到的强连通分量
    int connectedCountForTarjan;      //在tarjan算法中使用的强连通分量个数
    bool hasCycle;          //是否有环
    stack<int> cycle;       //有向环中的所有顶点(如果存在)
    stack<int> reversePost; //通过DFS得到的所有顶点的逆后序排列
    UnionFind *uf;           //用于Kruskal算法,用来判断最小生成树森林中是否会构成回路

    void DFS(int v,bool *visited);              //私有DFS,供递归调用
    void BFS(int v,bool *visited);  //私有BFS

    void DFSForCycle(int v,bool *visited,bool *onStack,int *edgeTo);    //用DFS思想来判断环
    void DFSForReversePost(int v,bool *visited);   //用DFS思想来求逆后序序列,用于求拓扑序列或者强连通分量
    void DFSForConnection(int v,bool *visited); //用DFS思想来求强连通分量
    void TarjanForConnection(int u,bool *visited,int *DFN,int *low,stack<int> *tarjanStack,bool *inStack,int &index); //用tarjan算法求强连通分量,其实也是运用了DFS思想

    void ClearCycle();  //清空栈cycle中的记录
    void ClearReversePost();   //清空栈reversePost中的记录
    void CalInDegree(int *inDegree);    //计算所有顶点的入度

    void Prim(int v0,int *nearest, T *lowcost);  //普里姆算法求无向图最小代价生成树,私有,内部调用
    void Kruskal(priority_queue<ENode<T>> &pq); //克鲁斯卡尔算法求无向图最小代价生成树,私有,内部调用
    void Dijkstra(int v0,int  *path, T *curShortLen);    //迪杰斯特拉算法解决单源最短路径问题,私有,内部调用
    int FinMinLen(T *curShortLen,bool *mark);              //Dijkstra算法的辅助函数,用于找出下一条最短路径的终点
public:
    Graph(int mSize);
    ~Graph();
    bool Exist(int u,int v) const;  //边u->v是否存在
    bool Insert(int u,int v,T w);   //插入边u->v
    bool Remove(int u,int v);       //删去边u->v
    Graph<T> Reverse();             //得到反向图
    void DFS();                     //公有接口,深度优先搜索
    void BFS();                     //公有接口,宽度优先搜索

    bool HasCycle();                //判断是否有环
    stack<int> GetCycle();          //返回环
    void CalReversePost();          //通过递归调用DFSForReversePost来求得
    void TopoSort();                //拓扑排序
    void TopoSortByDFS();           //用DFS来求拓扑序列
    stack<int> GetReversePost();    //返回DFS中顶点的逆后序序列
    void CalculateConnection();     //求图的强连通分量
    int GetConnectedCount();        //得到强连通分量数
    int ConnectionID(int v);        //v所在的强连通分量的标识符(1~connectedCount)
    void ShowConnection();          //打印强连通分量
    void TarjanForConnection();     //用tarjan算法求强连通分量


    void Prim(int v0);              //普里姆算法求无向图最小代价生成树,外部接口
    void Kruskal();                 //克鲁斯卡尔算法求无向图最小代价生成树,外部接口
    void Dijkstra(int v0);          //迪杰斯特拉算法解决单源最短路径问题
    void Floyd();                   //弗洛伊德算法求所有顶点之间的最短路径
    T GetWeight(int u,int v);       //获得边u-v的权值
};

其中的三个操作搜索、插入和删除因为比较简单,就不作说明了,最后看代码就知道了。另外,我还写了个求反向图的成员函数,主要也是为了后文在解决强连通问题时方便调用。
下面来具体看看图的重要算法吧!

四、图论算法

(一)深度优先搜索DFS
假定初始时,图G的所有顶点都未被访问过,那么从图中某个顶点v出发的深度优先搜索图的递归过程DFS可以描述为:
(1)访问顶点v,并对v打上已访问标记。
(2)依次从v的未访问的邻接点出发,深度优先搜索图G。

//公有接口,深度优先搜索
template <typename T>
void Graph<T>::DFS() {
    bool *visited = new bool[n];
    for (int i = 0; i < n; ++i) {
        visited[i] = false;
    }
    for (int j = 0; j < n; ++j) {
        if (!visited[j]) {
            DFS(j, visited);
        }
    }
    delete[] visited;
}

对于for循环j,每次从此入口进入递归调用,在无向图的情况下,其实是遍历了一个连通分量。对于有向图,则遍历了所有从顶点j出发,有路径可到达的顶点,即j的可达集。
私有递归函数如下:

//私有DFS,供递归调用
template <typename T>
void Graph<T>::DFS(int v, bool *visited) {
    visited[v] = true;
    cout<<v<<" ";
    for (ENode<T> *w = enodes[v]; w; w = w->next) {
        if (!visited[w->adjVex]) {
            DFS(w->adjVex, visited);
        }
    }
}

可见,DFS类似于树中的先序遍历。
(二)宽度优先遍历BFS
假定初始时,图G的所有顶点都未被访问过,那么从图中某个顶点v出发的宽度优先搜索图的过程BFS可以描述为:
(1)访问顶点v,并对v打上已访问标记。
(2)然后依次访问v的各个未访问过的邻接点。
(3)接着再依次访问分别与这些邻接点相邻接且未访问过的顶点。

//公有接口,宽度优先搜索
template <typename T>
void Graph<T>::BFS() {
    bool *visited = new bool[n];
    for (int i = 0; i < n; ++i) {
        visited[i] = false;
    }

    for (int j = 0; j < n; ++j) {
        if (!visited[j]) {
            BFS(j, visited);
        }
    }
    delete[] visited;
}

宽度优先搜索是按层次往外扩展的搜索方法。它需要一个队列来记录那些自身已经被访问过,但其邻接点尚未被访问过的顶点。
私有递归函数如下:

//私有BFS
template <typename T>
void Graph<T>::BFS(int v, bool *visited) {
    visited[v] = true;
    cout<<v<<" ";
    queue<int> myqueue;
    myqueue.push(v);

    int s;
    while (!myqueue.empty()) {
        s = myqueue.front();
        myqueue.pop();
        for (ENode<T> *w = enodes[s]; w; w = w->next) {
            if (!visited[w->adjVex]) {
                visited[w->adjVex] = true;
                cout<<w->adjVex<<" ";
                myqueue.push(w->adjVex);
            }
        }
    }
}

可见,BFS类似于树中的层次遍历。

说完了DFS和BFS,接下来我们来看看DFS的各种妙用!
(三)用DFS思想判断是否有环

//用DFS思想来判断环
template <typename T>
void Graph<T>::DFSForCycle(int v, bool *visited, bool *onStack, int *edgeTo) {
    onStack[v] = true;
    visited[v] = true;

    for (ENode<T> *w = enodes[v]; w; w = w->next) {
        if (hasCycle) {
            return;
        }else if (!visited[w->adjVex]) {
            edgeTo[w->adjVex] = v;
            DFSForCycle(w->adjVex, visited, onStack, edgeTo);
        } else if (onStack[w->adjVex]) {    //此顶点已经在递归调用的栈上,再次访问说明出现环了
            //用栈cycle将环上的点都保存起来
            for (int i = v; i != w->adjVex; i = edgeTo[i]) {
                cycle.push(i);
            }
            cycle.push(w->adjVex);
            cycle.push(v);
            hasCycle = true;
        }
    }
    onStack[v] = false;     //消除在此递归调用栈上的记录,因为已经递归结束了
}

//判断是否有环
template <typename T>
bool Graph<T>::HasCycle() {
    bool *visited = new bool[n];
    bool *onStack = new bool[n];    //由顶点索引的数组,以标记递归调用栈上的所有顶点
    int *edgeTo = new int[n];       //edgeTo[i]存放指向i的边的点
    ClearCycle();

    for (int i = 0; i < n; ++i) {
        visited[i] = false;
        onStack[i] = false;
        edgeTo[i] = -1;
    }

    for (int j = 0; j < n; ++j) {
        if (!visited[j]) {
            DFSForCycle(j, visited, onStack, edgeTo);
        }
    }
    delete[] visited;
    delete[] onStack;
    delete[] edgeTo;
    if (hasCycle) {
        return true;
    } else {
        return false;
    }

}

//返回环
template <typename T>
stack<int> Graph<T>::GetCycle() {
    stack<int> tmp(cycle);
    return tmp;
}

//清空栈cycle中的记录
template <typename T>
void Graph<T>::ClearCycle() {
    while (!cycle.empty()) {
        cycle.pop();
    }
}

对于每次从公有函数HasCycle中的外部for循环进入DFS递归调用,都用onStack来保存本次递归调用开始到结束所访问的结点,如果某个结点在本次递归中被访问两次,则说明出现了环。
(四)用DFS思想来求逆后序序列
逆后序序列?这有什么用呢?
其实这可大有用途!用于求拓扑序列或者强连通分量!后文会介绍到。
怎么求逆后序序列呢?其实很简单,只在普通的DFS私有递归中加入了一个语句,就可以实现。

//用DFS思想来求逆后序序列,用于求拓扑序列或者强连通分量
template <typename T>
void Graph<T>::DFSForReversePost(int v, bool *visited) {
    visited[v] = true;
    for (ENode<T> *w = enodes[v]; w; w = w->next) {
        if (!visited[w->adjVex]) {
            DFSForReversePost(w->adjVex, visited);
        }
    }
    reversePost.push(v);    //这是和普通的DFS唯一不同的地方!
}

//通过调用DFSForReversePost来求得
template <typename T>
void Graph<T>::CalReversePost() {
    ClearReversePost();
    bool *visited = new bool[n];
    for (int i = 0; i < n; ++i) {
        visited[i] = false;
    }
    for (int j = 0; j < n; ++j) {
        if (!visited[j]) {
            DFSForReversePost(j, visited);
        }
    }


    delete[] visited;
}

//返回DFS中顶点的逆后序序列
template <typename T>
stack<int> Graph<T>::GetReversePost() {
    //因为栈的特殊性,这里直接返回一个拷贝,以保证源栈不会因为外界操作而改变
    stack<int> tmp(reversePost);

    return tmp;
}

//清空栈reversePost中的记录
template <typename T>
void Graph<T>::ClearReversePost() {
    while (!reversePost.empty()) {
        reversePost.pop();
    }
}

(五)拓扑排序
拓扑序列是描述这样的情景:一个工程可以分成若干个活动,活动的执行常常伴随着某些先决条件,一些活动必须先于另一些活动被完成。
所以拓扑序列就是这些活动先后完成的一个次序。而拓扑排序就是求拓扑序列的一个过程。
求拓扑序列我用了两种方法。
1.用入度来求拓扑序列
拓扑排序的步骤可描述如下:
(1)在图中选择一个入度为零的顶点,并输出之。
(2)从图中删除该顶点及其所有出边(以该顶点为尾的有向边)。
(3)重新计算剩余顶点的入度。
(4)重复(1)到(3),直到所有顶点都已列出,或者直到剩下的图中再也没有入度为零的顶点为止,后者表示图中包含有向回路。

//计算所有顶点的入度
template <typename T>
void Graph<T>::CalInDegree(int *inDegree) {
    for (int i = 0; i < n; ++i) {
        for (ENode<T> *w = enodes[i]; w; w = w->next) {
            inDegree[w->adjVex]++;
        }
    }
}

//拓扑排序
template <typename T>
void Graph<T>::TopoSort() {
    if (HasCycle()) {       //存在环,直接返回
        cout<<"Cycle exists."<<endl;
        return;
    }

    int *inDegree = new int[n];
    for (int i = 0; i < n; ++i) {
        inDegree[i] = 0;
    }

    CalInDegree(inDegree);
    queue<int> topoQueue;
    //将入度为0的顶点存入队列
    for (int j = 0; j < n; ++j) {
        if (inDegree[j] == 0) {
            topoQueue.push(j);
        }
    }

    int v;
    int k;
    while (!topoQueue.empty()) {
        v = topoQueue.front();
        topoQueue.pop();

        cout<<v<<" ";
        for (ENode<T> *w = enodes[v]; w; w = w->next) {
            k = w->adjVex;
            inDegree[k]--;  //所有由顶点v指出的邻接点入度-1
            if (0 == inDegree[k]) {     //更新后入度为0,则存进队列
                topoQueue.push(k);
            }
        }
    }
}

我是先判断是否有环,如果有的话直接返回,没有才来求拓扑序列。
2.用逆后序排列来求拓扑序列
这种做法是基于这样的事实:
一幅有向无环图的拓扑顺序即为DFS中所有顶点的逆后序排列,所以只要求该逆后序排列就好。
如果该图是有环的,就说明拓扑序列不存在,尽管该逆后序排列仍能求出来。
所以在判断不存在环之后就可以直接调用上文已经实现的 CalReversePost()来求得逆后序序列,也就是拓扑序列。
所以也就是可以用DFS的思想来求拓扑序列!

//用DFS来求拓扑序列
//一幅有向无环图的拓扑顺序即为DFS中所有顶点的逆后序排列,所以只要求该逆后序排列就好
//如果该图是有环的,就说明拓扑序列不存在,尽管该逆后序排列仍能求出来
template <typename T>
void Graph<T>::TopoSortByDFS() {
    if (HasCycle()) {       //存在环,直接返回
        cout<<"Cycle exists."<<endl;
        return;
    }
    CalReversePost();

}

(六)用DFS思想来求强连通分量
这和普通DFS不同的地方是搜索顺序不一样。
算法的精髓是:求图G的强连通分量,是根据该图的反向图的顶点逆后序序列来进行DFS,所有在同一个递归DFS调用中被访问到的顶点都在同一个强连通分量中。
而求反向图和求逆后序序列都在上文中给出了具体实现。所以求强连通分量也就非常容易了。

//用DFS思想来求强连通分量
template <typename T>
void Graph<T>::DFSForConnection(int v, bool *visited) {
    visited[v] = true;
    id[v] = connectedCount;
    for (ENode<T> *w = enodes[v]; w; w = w->next) {
        if (!visited[w->adjVex]) {
            DFSForConnection(w->adjVex, visited);
        }
    }
}

//求图的强连通分量
template <typename T>
void Graph<T>::CalculateConnection() {
    connectedCount = 0;
    bool *visited = new bool[n];
    for (int i = 0; i < n; ++i) {
        visited[i] = false;
        id[i] = 0;
    }

    //根据本图的反向图的顶点逆后序序列来进行DFS
    //所有在同一个递归DFS调用中被访问到的顶点都在同一个强连通分量中
    Graph<T> R = this->Reverse();
    R.CalReversePost();
    stack<int> topostack = R.GetReversePost();

    int j;
    while (!topostack.empty()) {
        j = topostack.top();
        topostack.pop();
        if (!visited[j]) {
            connectedCount++;
            DFSForConnection(j, visited);
        }
    }
    delete[] visited;
}

//打印强连通分量
template <typename T>
void Graph<T>::ShowConnection() {
    CalculateConnection();

    set<int> connections[connectedCount];
    for (int i = 0; i < n; ++i) {
        connections[id[i] - 1].insert(i);
    }
    for (int j = 0; j < connectedCount; ++j) {
        cout << "connection " << j + 1 << ":";
        for (set<int>::iterator set_iter = connections[j].begin(); set_iter != connections[j].end(); set_iter++) {
            cout << *set_iter << " ";
        }
        cout << endl;
    }
}

//得到强连通分量数
template <typename T>
int Graph<T>::GetConnectedCount() {
    return connectedCount;
}

//v所在的强连通分量的标识符(1~connectedCount)
template <typename T>
int Graph<T>::ConnectionID(int v) {
    return id[v];
}

——————————————————2017.4.8星期六——————————————————
今天在解决两个树结点的最小公共祖先时,看到有个算法叫做tarjan算法,上网搜索了下,发现原来这个算法是可以用来求有向图的强连通分量的。所以学习了下这个算法,并且在原来图论代码的基础上实现了该算法。在此补充下。
Tarjan算法是基于DFS的算法,每个强连通分量为搜索树中的一棵子树。搜索时,把当前搜索树中未处理的结点加入栈中,回溯时可以判断栈顶到栈中的结点是否为一个强连通分量。
首先,该算法需要的数据结构如下:
visited[u]:顶点u是否被访问过
dfn[u]:DFS遍历时顶点u被搜索的次序,也即时间戳
low[u]:顶点u能够回溯到的最早位于栈中的顶点
tarjanStack:用于存放每次遍历时被搜索到的顶点
inStack[u]:u目前是否在栈中,要配合tarjanStack使用
index:时间戳,随着访问的结点而递增

具体过程如下:

  1. 首先就是按照DFS算法搜索的次序对图中所有结点进行搜索。
  2. 在递归搜索过程中:
    (1) 时间戳的初始化:当首次搜索到点u时,dfn和low数组的值都为到该点的时间戳。
    (2)栈:每搜索到一个未访问过的点,将它压入栈中。
    (3)对于由u指向的结点v,如果此时(时间为dfn[u]时)v还没被访问过,则继续对v进行深度搜索。如果此时结点v已经在栈中,则用u的low值和v的DFN值中最小值来更新low[u]。因为如果DFN[v] < low[u],则根据low值的定义,即能够回溯到的最早已经在栈中的顶点,所以我们应该用DFN[v]来更新low[u],表示u能和v回溯到相同的最早顶点。
  3. 在回溯过程中,即从对结点v的深度搜索中返回后,用u和v两点low值的最小值来更新low[u]。因为顶点v能够回溯到的已经在栈中的顶点,顶点u也一定能回溯到。因为存在从u到v的直接路径,所以v能够到达的顶点u也一定能够到达。
  4. 搜索完从顶点u指出的所有顶点后(也就是子树已经全部遍历),判断该结点的low值和dfn值是否相等。如果相等,则该结点一定是在深度遍历过程中该强连通图中第一个被访问过的顶点,因为它的low值和dfn值最小,不会被该强连通图中其他顶点影响。(论证一下为什么在同一个强连通分量中一定仅有一个结点的low值等于dfn值?因为如果在同一个强连通分量中有两个结点的low值等于dfn值,又这两个结点的dfn值一定不相同,所以它们的low值也一定不相同。可是根据low的定义,既然这两个结点位于同一个连通分量中,也就是这两个结点必然可达,那么这两个结点中其中一个结点的low值一定会被另外一个所影响,导致两个low值相同。这与假设矛盾,所以在同一个强连通分量中不可能存在两对dfn值和low值相等的结点。)既然知道了该顶点是该强连通子树里的根,又根据栈的特性,则该顶点相对于同个连通图中其他顶点一定是在栈的最里面,所以能通过不断地弹栈来弹出该连通子树中的所有顶点,直到弹出根结点即该顶点为止。

伪代码:

tarjan(u){

  DFN[u]=Low[u]=++Index // 为节点u设定次序编号和Low初值

  Stack.push(u)   // 将节点u压入栈中

  for each (u, v) in E // 枚举每一条边

    if (v is not visted) // 如果节点v未被访问过

        tarjan(v) // 继续向下找

        Low[u] = min(Low[u], Low[v])

    else if (v in S) // 如果节点v已经在栈内

        Low[u] = min(Low[u], DFN[v])

  if (DFN[u] == Low[u]) // 如果节点u是强连通分量的根

  repeat v = S.pop  // 将v退栈,为该强连通分量中一个顶点

      print v

  until (u== v)

}

结合一个样例来帮助理解算法执行过程吧。
这里写图片描述
我们来模拟一下完整的过程:
从0开始进入搜索:dfn[0] = low[0] = ++index ————1
入栈,栈中:0
由0进入1:dfn[1] = low[1] = ++index ————2
入栈,栈中:0,1
由1进入2:dfn[2] = low[2] = ++index ————3
入栈,栈中:0,1,2
由2进入4:dfn[4] = low[4] = ++index ————4
入栈,栈中:0,1,2,4
由4往下找,发现结点0,但是0已经在栈中,所以low[4] = min(low[4],dfn[0]) = min(4,1) = 1,然后判断low[4]和dfn[4],不等,则返回。
由4回到2:low[2] = min(low[2],low[4]) = 1,然后判断low[2]和dfn[2],不等,则返回。
由2回到1:low[1] = min(low[1],low[2]) = 1,然后判断low[1]和dfn[1],不等,则返回。
由1回到0:low[0] = min(low[0],low[1]) = 1,然后判断low[0]和dfn[0],相等,说明找到该强连通分量子树的根了。从栈中弹出该强连通分量的所有结点。也就是弹出从栈顶到0的所有结点4,2,1,0。此时,栈中:空。
本次深度搜索完毕,看所有顶点中是否还有未被访问的。发现顶点3还没。
所以从3开始进入搜索:dfn[3] = low[3] = ++index ————5
入栈,栈中:3
由3往下找,发现0、2、4,但是它们都已经被访问过了。最后,判断low[3]和dfn[3],相等,说明找到该强连通分量子树的根了。从栈中弹出该强连通分量的所有结点即3。此时,栈中:空。
本次深度搜索完毕,看所有顶点中是否还有未被访问的。发现都已经被访问过了。tarjan算法结束。
所以,最终得出的结果是该有向图中有两个强连通分量,一个是{4,2,1,0},一个是{3}。

//用tarjan算法求强连通分量,其实也是运用了DFS思想
/*
 * visited[u]:顶点u是否被访问过
 * dfn[u]:DFS遍历时顶点u被搜索的次序,也即时间戳
 * low[u]:顶点u能够回溯到的最早位于栈中的顶点
 * tarjanStack:用于存放每次遍历时被搜索到的顶点
 * inStack[u]:u目前是否在栈中,要配合tarjanStack使用
 * index:时间戳,随着访问的结点而递增
 */
template <typename T>
void Graph<T>::TarjanForConnection(int u,bool *visited,int *dfn,int *low,stack<int> *tarjanStack,bool *inStack,int &index) {
    dfn[u] = low[u] = ++index;      //为顶点u设访问时间戳和low初值
    visited[u] = true;              //修改为已访问
    tarjanStack->push(u);           //顶点u入栈
    inStack[u] = true;

    //搜索从顶点u指出的每个顶点
    for (ENode<T> *w = enodes[u]; w; w = w->next) {
        if (!visited[w->adjVex]) {      //顶点v还没被访问过
            TarjanForConnection(w->adjVex, visited, dfn, low, tarjanStack, inStack, index);
            //从上个递归函数返回后就是回溯过程,用u和v即w->adjVex的最小low值来更新low[u]。
            //因为顶点v能够回溯到的已经在栈中的顶点,顶点u也一定能回溯到。
            //因为存在从u到v的直接路径,所以v能够到达的顶点u也一定能够到达。
            low[u] = low[u] < low[w->adjVex] ? low[u] : low[w->adjVex];
        }else if (inStack[w->adjVex]) { //顶点v已经在栈中
            //用u的low值和v的DFN值中最小值来更新low[u]。
            //如果DFN[v]<low[u],则根据low值的定义,即能够回溯到的最早已经在栈中的顶点,所以我们应该用DFN[v]来更新low[u],
            //表示u能和v回溯到相同的最早顶点
            low[u] = low[u] < dfn[w->adjVex] ? low[u] : dfn[w->adjVex];
        }
    }

    //搜索完从顶点u指出的所有顶点后判断该结点的low值和DFN值是否相等。
    //如果相等,则该结点一定是在深度遍历过程中该强连通图中第一个被访问过的顶点,因为它的low值和DFN值最小,不会被该强连通图中其他顶点影响。
    //既然知道了该顶点是该强连通子树里的根,又根据栈的特性,则该顶点相对于同个连通图中其他顶点一定是在栈的最里面,
    //所以能通过不断地弹栈来弹出该连通子树中的所有顶点,直到弹出根结点即该顶点为止。
    if (low[u] == dfn[u]) {
        connectedCountForTarjan++;  //找到一个强连通分量,计数自增
        int x;
        do {
            x = tarjanStack->top();
            tarjanStack->pop();
            inStack[x] = false;     //注意要和tarjanStack配套使用
            tarjanConnection[connectedCountForTarjan - 1].push_back(x);
        } while (x != u);
    } else {
        return;    //不等则返回
    }
}

//用tarjan算法求强连通分量
template <typename T>
void Graph<T>::TarjanForConnection() {
    connectedCountForTarjan = 0;
    bool *visited = new bool[n];
    int *dfn = new int[n];
    int *low = new int[n];
    stack<int> *tarjanStack = new stack<int>;
    bool *inStack = new bool[n];
    int index = 0;

    memset(visited, false, n);
    memset(dfn, 0, n);
    memset(low, 0, n);
    memset(inStack, false, n);

    for (int i = 0; i < n; ++i) {
        if (!visited[i]) {
            TarjanForConnection(i, visited, dfn, low, tarjanStack, inStack, index);
        }
    }

    for (int i = 0; i < connectedCountForTarjan; ++i) {
        cout<<"connection "<<i+1<<" : ";
        for (auto ite:tarjanConnection[i]) {
            cout<<ite<<" ";
        }
        cout<<endl;
    }

    delete[] visited;
    delete[] dfn;
    delete[] low;
    delete tarjanStack;
    delete[] inStack;
}

——————————————————2017.4.8 补充完。——————————————————

(七)最小代价生成树
一个无向连通图的生成树是一个极小连通子图,它包括图中全部顶点,并且有尽可能少的边。遍历一个连通图得到图的一棵生成树。图的生成树不是唯一的,采用不同的遍历方法,从不同的顶点出发可能得到不同的生成树。
一棵生成树的代价是各条边上的代价之和。一个网络的各生成树中,具有最小代价的生成树称为该网络的最小代价生成树。
著名的求最小代价生成树有2种方法:Prim算法和Kruskal算法。
1.普里姆算法
初始状态下,这棵生成树只有一个顶点,没有边,v0是任意选定的顶点。
从初始状态出发,按照某种准则,每一步从图中选择一条边,共选取n-1条边,构成一棵生成树。
选边准则是:寻找一条代价最小的边(u,v),边(u,v)是所有一个顶点u在构造中的生成树上,而另一个顶点v不在该树上的边(u,v)中代价是最小的。

//普里姆算法求无向图最小代价生成树,私有,内部调用
template <typename T>
void Graph<T>::Prim(int v0,int *nearest, T *lowcost) {
    bool *mark = new bool[n];
    for (int i = 0; i < n; ++i) {
        mark[i] = false;
    }
    ENode<T> *p = NULL;
    nearest[v0] = v0;
    lowcost[v0] = 0;
    mark[v0] = true;

    int k = v0;         //最近加入生成树中的顶点

    for (int i = 1; i < n ; ++i) {
        for (p = enodes[k]; p; p = p->next) {   //更新nearest和lowcost
            int j = p->adjVex;
            if (!mark[j] && lowcost[j] > p->weight) {
                nearest[j] = k;
                lowcost[j] = p->weight;
            }
        }
        T min = INT_MAX;
        for (int j = 0; j < n; ++j) {       //找到最小代价的边
            if (!mark[j] && min > lowcost[j]) {
                min = lowcost[j];
                k = j;
            }
        }
        mark[k] = true;
    }
}

//普里姆算法求无向图最小代价生成树,外部接口
template <typename T>
void Graph<T>::Prim(int v0) {
    if (v0 < 0 || v0 > n - 1) {
        cout << "input error!" << endl;
        return;
    }
    int *nearest = new int[n];      //nearest[v]=u,表示离v最近的是u,其中u在最小生成树中,v是待加入的顶点
    T *lowcost = new T[n];          //lowcost[v]=w(u,v),表示离v最近的点u之间的权值

    for (int i = 0; i < n; ++i) {
        nearest[i] = -1;
        lowcost[i] = INT_MAX;
    }

    Prim(v0, nearest, lowcost);
    cout << "(nearest[i],i,lowcost[i]) = ";
    for (int i = 0; i < n; ++i) {
        cout << "(" << nearest[i] << "," << i << "," << lowcost[i] << ")" << " ";
    }
    cout << endl;
    delete[]nearest;
    delete[]lowcost;

}

2.克鲁斯卡尔算法
初始状态,将所有顶点看成由n棵树组成的森林T,没有边。从初始状态开始,采用每一步选择一条边,共选n-1条边,构成一棵最小代价生成树。
选边准则是:在所有边的集合E中选择一条代价最小的边(u,v),并将其从E中删除;若在T中加入边(u,v)后不形成回路,则将其加进T中(这就要求u和v分属于生成森林的两棵不同的树上,由于边(u,v)的加入,这两棵树连成一棵树),否则继续选择下一条边。直到森林T变成一棵树,也就是生成了最小代价树。

//克鲁斯卡尔算法求无向图最小代价生成树,私有,内部调用
template <typename T>
void Graph<T>::Kruskal(priority_queue<ENode<T>> &pq) {
    ENode<T> kruskalResult[n - 1];
    ENode<T> x;


    //n个结点只要加入n-1条边就可以
    int k = 0;      //已加入最小生成树中的边数

    //每次从pq中取出具有最小代价的边,并且该边的两端顶点不会都已经在树中了
    while (k < n - 1 && !pq.empty()) {
        x = pq.top();
        pq.pop();
        if (!uf->Connected(x.vertex, x.adjVex)) {  //如果找到的最小边两端点还没在最小树中相连
            kruskalResult[k] = x;
            uf->Union(x.vertex, x.adjVex);        //将两端点在树中相连
            k++;
        }
    }

    cout<<"(u,v,weight) = ";
    for (int j = 0; j < n-1; ++j) {
        cout<<"("<<kruskalResult[j].vertex<<","<<kruskalResult[j].adjVex<<","<<kruskalResult[j].weight<<") ";
    }
    cout<<endl;
}

//克鲁斯卡尔算法求无向图最小代价生成树,外部接口
template <typename T>
void Graph<T>::Kruskal() {
    priority_queue<ENode<T>,vector<ENode<T>>> pq;   //最小优先队列

    for (int i = 0; i < n; ++i) {
        for (ENode<T> *w = enodes[i]; w; w = w->next) {
            pq.push(*w);
        }
    }
    Kruskal(pq);
}

为了实现Kruskal算法,需要一个优先权队列,用来取得代价最小的边。选取该边之后,还要判断该边加入最小生成树中会不会形成回路,所以我写了一个类UnionFind来加以判断。这其实是一个动态连通类,对于加入的所有边,可以维护它们所形成的连通分量。

//对于输入的“点-点”数据,求出动态连通性
class UnionFind{
private:
    int *id;    //父链接数组,由触点索引
    int *sz;    //由触点索引的各个根节点所对应的分量的大小
    int count;  //连通分量的数量
public:
    UnionFind(int N);
    ~UnionFind();
    int Find(int p);                //找p所在连通分量的根
    bool Connected(int p, int q);   //p和q是否在同一个连通分量里
    int GetCount();                 //返回连通分量
    void Union(int p, int q);       //将p和q连接起来
};

UnionFind::UnionFind(int N):count(N) {
    id = new int[N];
    sz = new int[N];
    for (int i = 0; i < N; ++i) {
        id[i] = i;
    }
    for (int j = 0; j < N; ++j) {
        sz[j] = 1;
    }
}

UnionFind::~UnionFind() {
    delete[] id;
    delete[] sz;
}

//找p所在连通分量的根
int UnionFind::Find(int p) {
    while (p != id[p]) {
        p = id[p];
    }
    return p;
}

//p和q是否在同一个连通分量里
bool UnionFind::Connected(int p, int q) {
    int pRoot = Find(p);
    int qRoot = Find(q);
    if (pRoot == qRoot) {
        return true;
    } else {
        return false;
    }
}

//返回连通分量
int UnionFind::GetCount() {
    return count;
}

//将p和q连接起来
void UnionFind::Union(int p, int q) {
    int pRoot = Find(p);
    int qRoot = Find(q);

    if (pRoot == qRoot) {   //已经在同个连通分量里,直接返回
        return;
    } else {                //将元素较少的连通分量连接到元素较多的连通分量上
        if (sz[pRoot] < sz[qRoot]) {
            id[pRoot] = qRoot;
            sz[qRoot] += sz[pRoot];
        } else {
            id[qRoot] = pRoot;
            sz[pRoot] += sz[qRoot];
        }
        count--;            //现有连通分量个数减1
    }
}

(八)单源最短路径——Dijkstra算法
单源最短路径问题是:给定带权的有向图,对于给定的源点v0,求从v0到其余所有顶点的最短路径。
算法需要用到的数据结构有:
curShortLen[i]存放从源点v0到i的当前最短路径的长度。
path[i]给出从v0到顶点i的最短路径上,位于顶点i前面的那个顶点。
mark[i]表示顶点i是否已加入单源最短路径里。
Dijkstra算法的具体做法是:
(1)产生从源点v0到它自身的路径,其长度为0,将源点v0加入最短路径S中。这些通过以下代码就可以实现:

    mark[v0] = true;
    path[v0] = -1;
    curShortLen[v0] = 0;

(2)更新和源点v0直接邻接的所有顶点i对应的curShortLen[i]。
(3)然后找出第一条最短路径及其顶点k。更新path[k]=v0。
(4)将顶点k加入S,并对与顶点k直接邻接的所有顶点i对应的curShortLen[i]进行更新,更新公式为curShortLen[i] = min{curShortLen [i],curShortLen[k]+w(k,i)},其中w(k,i)是边< k,i >上的权值。
(5)求下一条最短路径的终点,也就是还没有加入最短路径的顶点中具有最短的curShortLen[k]值的顶点k。
(6)重复(4)和(5),直到将所有从源点v0可达的顶点都加入最短路径中。
其实,上面步骤中的(4)、(5)和(2)、(3)完全是一样的,我在用代码实现的时候也是将其视为统一情况处理。

//Dijkstra算法的辅助函数,用于找出下一条最短路径的终点
template <typename T>
int Graph<T>::FinMinLen(T *curShortLen,bool *mark) {
    int minIndex = -1;
    T minLen = INT_MAX;

    for (int i = 1; i < n; ++i) {
        if (!mark[i] && minLen > curShortLen[i]) {
            minIndex = i;
            minLen = curShortLen[i];
        }
    }

    return minIndex;
}


//迪杰斯特拉算法解决单源最短路径问题,私有,内部调用
template <typename T>
void Graph<T>::Dijkstra(int v0, int *path, T *curShortLen) {
    bool *mark = new bool[n];        //mark[i]表示顶点i是否已加入单源最短路径里
    for (int i = 0; i < n; ++i) {
        mark[i] = false;
    }


    mark[v0] = true;
    path[v0] = -1;
    curShortLen[v0] = 0;

    int k = v0;                     //最近加入单源最短路径中的顶点
    int nextK = -1;                 //即将加入单源最短路径中的顶点
    ENode<T> *p;
    int count = 0;                  //用来计数单源最短路径上有多少条边

    for (int i = 1; i < n; ++i) {   //循环n-1次,将其他顶点都加入单源最短路径中
        for (p = enodes[k]; p; p = p->next) {
            int j = p->adjVex;
            if (!mark[j] && curShortLen[j] > curShortLen[k] + p->weight) {
                curShortLen[j] = curShortLen[k] + p->weight;    //保证curShortLen[j] = min{curShortLen[j],curShortLen[k]+p->weight}
            }
        }
        nextK = FinMinLen(curShortLen, mark);   //找到下一条最短路径的终点
        if (-1 == nextK) {                      //返回-1,说明从v0出发的单源最短路径已经都找到了
            break;
        } else {
            mark[nextK] = true;                     //加入最短路径中
            path[nextK] = k;                        //记录终点信息,方便回溯
            k = nextK;                              //k指向最近加入的顶点
            count++;                                //路径上的边数加1
        }
    }

    //输出最短路径结果
    cout<<"shortest path:"<<endl;
    int lastVer;    //最短路径中的上个顶点
    stack<int> tmp; //用栈来逆序储存最短路径上的顶点
    for (int i = 1; i <= count; ++i) {
        cout<<curShortLen[k]<<" : ";

        tmp.push(k);
        lastVer = path[k];
        while (lastVer != v0) {
            tmp.push(lastVer);
            lastVer = path[lastVer];
        }
        tmp.push(v0);

        cout<<tmp.top();
        tmp.pop();
        while (!tmp.empty()) {
            cout<<"->"<<tmp.top();
            tmp.pop();
        }
        cout<<endl;

        k = path[k];    //将k置为最短路径上在该点之前的那个顶点
    }
}

//迪杰斯特拉算法解决单源最短路径问题
template <typename T>
void Graph<T>::Dijkstra(int v0) {
    if (v0 < 0 || v0 > n - 1) {
        cout << "input error!" << endl;
        return;
    }
    T *curShortLen = new T[n];          //curShortLen[i]存放从源点v0到i的当前最短路径的长度
    int *path = new int[n];             //path[i]给出从v0到顶点i的最短路径上,位于顶点i前面的那个顶点

    for (int i = 0; i < n; ++i) {
        curShortLen[i] = INT_MAX;
        path[i] = -1;
    }

    Dijkstra(v0, path, curShortLen);
}

(九)所有顶点之间的最短路径——Floyd算法
学习了Dijkstra算法,再求任意两对顶点之间的最短路径就很简单了,只需每次选择一个顶点作为源点,重复执行Dijkstra算法n次,便可求得任意两对顶点之间的最短路径,总的执行时间为O(n^3)。
但是呢,还是有必要来学习下Floyd算法,虽然它的时间复杂度也是O(n^3),但是它在形式上更直接。
先来看看需要用到的数据结构:

    int path[n][n];    //n*n矩阵,path[i][j]表示从顶点i到j的最短路径上,顶点j的前一个顶点
    T d[n][n];         //d[i][j]存放从顶点i到顶点j的当前最短路径的长度

Floyd算法的思想是:设集合S的初始状态为空集合,然后依次向集合S中加入顶点0,1…,n-1,每次加入一个顶点,我们更新d[i][j]。d[i][j]被定义为从i到j中间只经过S中的顶点的、所有可能路径中的最短路径的长度。如果从i到j,中间只经过S中的顶点当前没有路径相通,那么d[i][j]为一个大值INT_MAX。随着S中的顶点不断增加,d[i][j]的值不断修正,当所有顶点都加入S中时,d[i][j]的值就是从i到j的最短路径。

//获得边u-v的权值
template <typename T>
T Graph<T>::GetWeight(int u,int v) {
    if (u == v) {
        return 0;
    }
    ENode<T> *p = enodes[u];
    while (p != NULL && p->adjVex != v) {
        p = p->next;

    }

    if (p) {
        return p->weight;
    } else {
//        cout<<"edge "<<u<<"----"<<v<<" is not connected"<<endl;
        return INT_MAX;
    }
}

//弗洛伊德算法求所有顶点之间的最短路径
template <typename T>
void Graph<T>::Floyd() {
    int path[n][n];    //n*n矩阵,path[i][j]表示从顶点i到j的最短路径上,顶点j的前一个顶点
    T d[n][n];         //d[i][j]存放从顶点i到顶点j的当前最短路径的长度
    int i, j, k;

    //初始化矩阵
    for (i = 0; i < n; i++) {
        for (j = 0; j < n; j++) {
            d[i][j] = GetWeight(i, j);
            if (i != j && d[i][j] < INT_MAX) {
                path[i][j] = i;
            } else {
                path[i][j] = -1;
            }
        }
    }

    for (k = 0; k < n; k++) {       //n次更新矩阵
        for (i = 0; i < n; i++) {
            for (j = 0; j < n; j++) {
                if (d[i][k] + d[k][j] < d[i][j]) {  //加入顶点k之后更新矩阵
                    d[i][j] = d[i][k] + d[k][j];
                    path[i][j] = path[k][j];
                }
            }
        }
    }

    //输出所有顶点之间的最短路径结果
    cout<<"shortest path between two vertex:"<<endl;
    stack<int> tmp; //用栈来逆序储存最短路径上的顶点
    int m;
    for (i = 0; i < n; i++) {
        for (j = 0; j < n; j++) {
            if (i == j) {
                continue;
            }
            cout << "from " << i << " to " << j << " : ";

            if (path[i][j] == -1) {     //表示不可达
                cout << "not connected" << endl;
            } else {
                tmp.push(j);
                m = path[i][j];
                while (m != -1) {    //在最短路径上进行反向回溯
                    tmp.push(m);
                    m = path[i][m];
                }

                cout << tmp.top();
                tmp.pop();
                while (!tmp.empty()) {
                    cout << "->" << tmp.top();
                    tmp.pop();
                }
                cout << endl;
            }

        }
    }
}

五、测试用例

1.无权有向无环图
测试DFS、BFS、环、拓扑排序:

int main() {
    int n = 9;
    Graph<int> graph(n);
    set<int> edgeInput[n];
    edgeInput[0].insert({2, 7});
    edgeInput[1].insert({2, 3, 4});
    edgeInput[2].insert({3});
    edgeInput[3].insert({5, 6});
    edgeInput[4].insert({5});
    edgeInput[7].insert({8});
    edgeInput[8].insert({6});
    for (int i = 0; i < n; ++i) {
        for (set<int>::iterator set_iter = edgeInput[i].begin(); set_iter != edgeInput[i].end(); set_iter++) {
            graph.Insert(i, *set_iter, 1);
        }
    }
    //测试深度优先遍历
    cout<<"DFS:";
    graph.DFS();
    cout<<endl;

    //测试宽度优先遍历
    cout<<"BFS:";
    graph.BFS();
    cout<<endl;

    //测试是否有环
    if (graph.HasCycle()) {
        cout<<"cycle:";
        stack<int> cycle = graph.GetCycle();
        while (!cycle.empty()) {
            cout<<cycle.top()<<" ";
            cycle.pop();
        }
        cout<<endl;
    } else {
        cout<<"cycle doesn't exist."<<endl;
    }

    //测试拓扑排序
    cout<<"TopoSort:";
    graph.TopoSort();
    cout<<endl;

    //测试用DFS来求拓扑序列
    cout<<"TopoSort By ReversePost:";
    graph.TopoSortByDFS();
    stack<int> topo = graph.GetReversePost();
    while (!topo.empty()) {
        cout<<topo.top()<<" ";
        topo.pop();
    }
    cout<<endl;
    return 0;
}

这里写图片描述

运行结果:

DFS:0 7 8 6 2 3 5 1 4 
BFS:0 7 2 8 3 6 5 1 4 
cycle doesn't exist.
TopoSort:0 1 7 4 2 8 3 6 5 
TopoSort By ReversePost:1 4 0 2 3 5 7 8 6 

Process finished with exit code 0

2.无权有向有环图
测试DFS、BFS、环、拓扑排序、强连通分量:

int main() {
    int n = 5;
    Graph<int> graph(n);
    set<int> edgeInput[n];
    edgeInput[0].insert({1});
    edgeInput[1].insert({2});
    edgeInput[2].insert({0,4});
    edgeInput[3].insert({0,2,4});
    edgeInput[4].insert({0});


    for (int i = 0; i < n; ++i) {
        for (set<int>::iterator set_iter = edgeInput[i].begin(); set_iter != edgeInput[i].end(); set_iter++) {
            graph.Insert(i, *set_iter, 1);
        }
    }

    //测试深度优先遍历
    cout<<"DFS:";
    graph.DFS();
    cout<<endl;

    //测试宽度优先遍历
    cout<<"BFS:";
    graph.BFS();
    cout<<endl;

    //测试是否有环
    if (graph.HasCycle()) {
        cout<<"cycle:";
        stack<int> cycle = graph.GetCycle();
        while (!cycle.empty()) {
            cout<<cycle.top()<<" ";
            cycle.pop();
        }
        cout<<endl;
    } else {
        cout<<"cycle doesn't exist."<<endl;
    }

    //测试拓扑排序
    cout<<"TopoSort:";
    graph.TopoSort();
    cout<<endl;

    //测试用DFS来求拓扑序列
    cout<<"TopoSort By ReversePost:";
    graph.TopoSortByDFS();
    stack<int> topo = graph.GetReversePost();
    while (!topo.empty()) {
        cout<<topo.top()<<" ";
        topo.pop();
    }
    cout<<endl;

    //测试强连通分量
    graph.ShowConnection();
    cout<<endl;

    //测试tarjan算法求强连通分量
    cout<<"By Tarjan algorithm:"<<endl;
    graph.TarjanForConnection();

    return 0;
}

这里写图片描述

运行结果:

DFS:0 1 2 4 3 
BFS:0 1 2 4 3 
cycle:4 0 1 2 4 
TopoSort:Cycle exists.

TopoSort By ReversePost:Cycle exists.

connection 1:0 1 2 4 
connection 2:3 

By Tarjan algorithm:
connection 1 : 4 2 1 0 
connection 2 : 3 

Process finished with exit code 0

3.有权无向图
测试最小生成树的Prim算法和Kruskal算法:

int main() {
    int n = 6;
    Graph<int> graph(n);
    set<int> edgeInput[n];
    graph.Insert(0, 1, 6);
    graph.Insert(0, 2, 1);
    graph.Insert(0, 3, 5);
    graph.Insert(1, 0, 6);
    graph.Insert(1, 2, 5);
    graph.Insert(1, 4, 3);
    graph.Insert(2, 0, 1);
    graph.Insert(2, 1, 5);
    graph.Insert(2, 3, 5);
    graph.Insert(2, 4, 6);
    graph.Insert(2, 5, 4);
    graph.Insert(3, 0, 5);
    graph.Insert(3, 2, 5);
    graph.Insert(3, 5, 2);
    graph.Insert(4, 1, 3);
    graph.Insert(4, 2, 6);
    graph.Insert(4, 5, 6);
    graph.Insert(5, 2, 4);
    graph.Insert(5, 3, 2);
    graph.Insert(5, 4, 6);

    //测试普里姆算法
    cout<<"Prim:"<<endl;
    graph.Prim(0);

    //测试克鲁斯卡尔算法
    cout<<"Kruskal:"<<endl;
    graph.Kruskal();

    return 0;
}

Prim算法构造最小代价生成树过程:
这里写图片描述

Kruskal算法构造最小代价生成树过程:
这里写图片描述

运行结果:

Prim:
(nearest[i],i,lowcost[i]) = (0,0,0) (2,1,5) (0,2,1) (5,3,2) (1,4,3) (2,5,4) 
Kruskal:
(u,v,weight) = (0,2,1) (3,5,2) (1,4,3) (2,5,4) (2,1,5) 

Process finished with exit code 0

4.有权有向图
测试Dijkstra算法、Floyd算法:

int main() {
    int n = 6;
    Graph<int> graph(n);
    set<int> edgeInput[n];

    graph.Insert(0, 1, 50);
    graph.Insert(0, 2, 10);
    graph.Insert(0, 4, 70);
    graph.Insert(1, 2, 15);
    graph.Insert(1, 4, 10);
    graph.Insert(2, 0, 20);
    graph.Insert(2, 3, 15);
    graph.Insert(3, 1, 20);
    graph.Insert(3, 4, 35);
    graph.Insert(4, 3, 30);
    graph.Insert(5, 3, 3);

    //测试迪杰斯特拉算法
    graph.Dijkstra(0);

    //测试弗洛伊德算法
    graph.Floyd();

    return 0;
}

这里写图片描述

运行结果:

shortest path:
55 : 0->2->3->1->4
45 : 0->2->3->1
25 : 0->2->3
10 : 0->2
shortest path between two vertex:
from 0 to 1 : 0->2->3->1
from 0 to 2 : 0->2
from 0 to 3 : 0->2->3
from 0 to 4 : 0->2->3->1->4
from 0 to 5 : not connected
from 1 to 0 : 1->2->0
from 1 to 2 : 1->2
from 1 to 3 : 1->2->3
from 1 to 4 : 1->4
from 1 to 5 : not connected
from 2 to 0 : 2->0
from 2 to 1 : 2->3->1
from 2 to 3 : 2->3
from 2 to 4 : 2->3->1->4
from 2 to 5 : not connected
from 3 to 0 : 3->1->2->0
from 3 to 1 : 3->1
from 3 to 2 : 3->1->2
from 3 to 4 : 3->1->4
from 3 to 5 : not connected
from 4 to 0 : 4->3->1->2->0
from 4 to 1 : 4->3->1
from 4 to 2 : 4->3->1->2
from 4 to 3 : 4->3
from 4 to 5 : not connected
from 5 to 0 : 5->3->1->2->0
from 5 to 1 : 5->3->1
from 5 to 2 : 5->3->1->2
from 5 to 3 : 5->3
from 5 to 4 : 5->3->1->4

Process finished with exit code 0

六、Prim算法与Dijkstra算法的区别

同:
二者都用了贪心策略,都选择最小代价的边。
异:
1.目的不同:
Prim是计算最小生成树的算法,比如为N个村庄修路,怎么修花销最少。

Dijkstra是计算最短路径的算法,比如从a村庄走到其他任意村庄的距离。
2.权值最低的概念不同:
Prim的“权值最低”是相对于S中的任意一点而言的,也就是把S中的点看成一个整体,每次寻找V-
S中跟S的距离最小(也就是跟S中任意一点的距离最小)的一点加入S;而Dijkstra的“权值最低”是相对于源点v0而言的,也就是每次寻找V-S中跟v0的距离最小的一点加入S。
一个可以说明二者不等价的例子是有四个顶点(v0, v1, v2, v3)和四条边且边值定义为(v0, v1)=20, (v0, v2)=10, (v1, v3)=2, (v3, v2)=15的图,用Prim算法得到的最小生成树中v0跟v1是不直接相连的,也就是在最小生成树中v0v1的距离是v0->v2->v3->v1的距离是27,而用Dijkstra算法得到的v0v1的距离是20,也就是二者直接连线的长度。

————————————————————完整代码——————————————————————

//
// Created by huxijie on 17-3-23.
// 图的邻接表表示

#include <iostream>
#include <set>
#include <queue>
#include <stack>
#include <string.h>

using namespace std;

const int INT_MAX = 0x7fff;

//边结点类
template <typename T>
struct ENode{
    int vertex;         //边的一端顶点
    int adjVex;         //边的另一端顶点
    T weight;           //边的权值
    ENode<T> *next;

    ENode() { next = NULL; }
    ENode(int vertex,int adjvertex,T w,ENode<T> *nextArc) {
        this->vertex = vertex;
        adjVex = adjvertex;
        weight = w;
        next = nextArc;
    }
    operator T() const{ return weight; }

    bool operator <(const ENode<T> &rhs) const {
        return this->weight > rhs.weight;   //最小值优先
    }
};

//自定义优先队列less的比较函数
template <typename T>
struct cmp{
    bool operator()(const ENode<T> &a,const ENode<T> &b) const {
        return a.weight > b.weight;
    }
};

//对于输入的“点-点”数据,求出动态连通性
class UnionFind{
private:
    int *id;    //父链接数组,由触点索引
    int *sz;    //由触点索引的各个根节点所对应的分量的大小
    int count;  //连通分量的数量
public:
    UnionFind(int N);
    ~UnionFind();
    int Find(int p);                //找p所在连通分量的根
    bool Connected(int p, int q);   //p和q是否在同一个连通分量里
    int GetCount();                 //返回连通分量
    void Union(int p, int q);       //将p和q连接起来
};

UnionFind::UnionFind(int N):count(N) {
    id = new int[N];
    sz = new int[N];
    for (int i = 0; i < N; ++i) {
        id[i] = i;
    }
    for (int j = 0; j < N; ++j) {
        sz[j] = 1;
    }
}

UnionFind::~UnionFind() {
    delete[] id;
    delete[] sz;
}

//找p所在连通分量的根
int UnionFind::Find(int p) {
    while (p != id[p]) {
        p = id[p];
    }
    return p;
}

//p和q是否在同一个连通分量里
bool UnionFind::Connected(int p, int q) {
    int pRoot = Find(p);
    int qRoot = Find(q);
    if (pRoot == qRoot) {
        return true;
    } else {
        return false;
    }
}

//返回连通分量
int UnionFind::GetCount() {
    return count;
}

//将p和q连接起来
void UnionFind::Union(int p, int q) {
    int pRoot = Find(p);
    int qRoot = Find(q);

    if (pRoot == qRoot) {   //已经在同个连通分量里,直接返回
        return;
    } else {                //将元素较少的连通分量连接到元素较多的连通分量上
        if (sz[pRoot] < sz[qRoot]) {
            id[pRoot] = qRoot;
            sz[qRoot] += sz[pRoot];
        } else {
            id[qRoot] = pRoot;
            sz[pRoot] += sz[qRoot];
        }
        count--;            //现有连通分量个数减1
    }
}


//图类
template <typename T>
class Graph{
private:
    ENode<T> **enodes;
    int n;                  //顶点个数
    int edges;              //边的个数
    int connectedCount;     //强连通分量个数
    int *id;                //由顶点索引的数组,存放顶点所属的连通分量标识符
    vector<int> *tarjanConnection;    //通过tarjan算法得到的强连通分量
    int connectedCountForTarjan;      //在tarjan算法中使用的强连通分量个数
    bool hasCycle;          //是否有环
    stack<int> cycle;       //有向环中的所有顶点(如果存在)
    stack<int> reversePost; //通过DFS得到的所有顶点的逆后序排列
    UnionFind *uf;           //用于Kruskal算法,用来判断最小生成树森林中是否会构成回路

    void DFS(int v,bool *visited);              //私有DFS,供递归调用
    void BFS(int v,bool *visited);  //私有BFS

    void DFSForCycle(int v,bool *visited,bool *onStack,int *edgeTo);    //用DFS思想来判断环
    void DFSForReversePost(int v,bool *visited);   //用DFS思想来求逆后序序列,用于求拓扑序列或者强连通分量
    void DFSForConnection(int v,bool *visited); //用DFS思想来求强连通分量
    void TarjanForConnection(int u,bool *visited,int *DFN,int *low,stack<int> *tarjanStack,bool *inStack,int &index); //用tarjan算法求强连通分量,其实也是运用了DFS思想

    void ClearCycle();  //清空栈cycle中的记录
    void ClearReversePost();   //清空栈reversePost中的记录
    void CalInDegree(int *inDegree);    //计算所有顶点的入度

    void Prim(int v0,int *nearest, T *lowcost);  //普里姆算法求无向图最小代价生成树,私有,内部调用
    void Kruskal(priority_queue<ENode<T>> &pq); //克鲁斯卡尔算法求无向图最小代价生成树,私有,内部调用
    void Dijkstra(int v0,int  *path, T *curShortLen);    //迪杰斯特拉算法解决单源最短路径问题,私有,内部调用
    int FinMinLen(T *curShortLen,bool *mark);              //Dijkstra算法的辅助函数,用于找出下一条最短路径的终点
public:
    Graph(int mSize);
    ~Graph();
    bool Exist(int u,int v) const;  //边u->v是否存在
    bool Insert(int u,int v,T w);   //插入边u->v
    bool Remove(int u,int v);       //删去边u->v
    Graph<T> Reverse();             //得到反向图
    void DFS();                     //公有接口,深度优先搜索
    void BFS();                     //公有接口,宽度优先搜索

    bool HasCycle();                //判断是否有环
    stack<int> GetCycle();          //返回环
    void CalReversePost();          //通过递归调用DFSForReversePost来求得
    void TopoSort();                //拓扑排序
    void TopoSortByDFS();           //用DFS来求拓扑序列
    stack<int> GetReversePost();    //返回DFS中顶点的逆后序序列
    void CalculateConnection();     //求图的强连通分量
    int GetConnectedCount();        //得到强连通分量数
    int ConnectionID(int v);        //v所在的强连通分量的标识符(1~connectedCount)
    void ShowConnection();          //打印强连通分量
    void TarjanForConnection();     //用tarjan算法求强连通分量


    void Prim(int v0);              //普里姆算法求无向图最小代价生成树,外部接口
    void Kruskal();                 //克鲁斯卡尔算法求无向图最小代价生成树,外部接口
    void Dijkstra(int v0);          //迪杰斯特拉算法解决单源最短路径问题
    void Floyd();                   //弗洛伊德算法求所有顶点之间的最短路径
    T GetWeight(int u,int v);       //获得边u-v的权值
};

template <typename T>
Graph<T>::Graph(int mSize) {
    n = mSize;
    edges = 0;
    connectedCount = 0;
    hasCycle = false;
    enodes = new ENode<T> *[n];
    id = new int[n];
    for (int i = 0; i < n; ++i) {
        enodes[i] = NULL;
        id[i] = 0;
    }
    uf = new UnionFind(n);
}

template <typename T>
Graph<T>::~Graph() {
    ENode<T> *p,*q;
    for (int i = 0; i < n; ++i) {
        p = enodes[i];
        while (p) {
            q = p;
            p = p->next;
            delete (q);
        }
    }
    delete[] enodes;
    delete[] id;
    delete uf;
}

//边u->v是否存在
template <typename T>
bool Graph<T>::Exist(int u, int v) const {
    if (u < 0 || v < 0 || u > n - 1 || v > n - 1 || u == v) {
        return false;   //输入参数无效
    }
    ENode<T> *p = enodes[u];
    while (p && p->adjVex != v) {
        p = p->next;
    }
    if (p) {
        return true;
    } else {
        return false;
    }
}

//插入边u->v
template <typename T>
bool Graph<T>::Insert(int u, int v, T w) {
    if (u < 0 || v < 0 || u > n - 1 || v > n - 1 || u == v) {
        return false;   //输入参数无效
    }
    if (Exist(u, v)) {
        cout<<"Duplicate"<<endl;
        return false;
    } else {
        //将新边结点插在由指针enodes[u]所指示的单链表最前面
        ENode<T> *p = new ENode<T>(u,v, w, enodes[u]);
        enodes[u] = p;
        edges++;
        return true;
    }
}

//删去边u->v
template <typename T>
bool Graph<T>::Remove(int u, int v) {
    if (u < 0 || v < 0 || u > n - 1 || v > n - 1 || u == v) {
        return false;   //输入参数无效
    }
    ENode<T> *p = enodes[u];
    ENode<T> *q = NULL;
    while (p && p->adjVex != v) {
        q = p;
        p = p->next;
    }
    if (!p) {
        cout<<"Not exist."<<endl;
        return false;
    }
    if (p == enodes[u]) {
        q = p;
        enodes[u] = p->next;
        delete (q);
        edges--;
    } else {
        q->next = p->next;
        delete (p);
        edges--;
    }
    return true;
}

//得到反向图
template <typename T>
Graph<T> Graph<T>::Reverse() {
    Graph<T> R(n);
    for (int i = 0; i < n; ++i) {
        for (ENode<T> *w = enodes[i]; w; w = w->next) {
            R.Insert(w->adjVex, i, w->weight);
        }
    }
    return R;
}

//私有DFS,供递归调用
template <typename T>
void Graph<T>::DFS(int v, bool *visited) {
    visited[v] = true;
    cout<<v<<" ";
    for (ENode<T> *w = enodes[v]; w; w = w->next) {
        if (!visited[w->adjVex]) {
            DFS(w->adjVex, visited);
        }
    }
}

//公有接口,深度优先搜索
template <typename T>
void Graph<T>::DFS() {
    bool *visited = new bool[n];
    for (int i = 0; i < n; ++i) {
        visited[i] = false;
    }
    for (int j = 0; j < n; ++j) {
        if (!visited[j]) {
            DFS(j, visited);
        }
    }
    delete[] visited;
}

//私有BFS
template <typename T>
void Graph<T>::BFS(int v, bool *visited) {
    visited[v] = true;
    cout<<v<<" ";
    queue<int> myqueue;
    myqueue.push(v);

    int s;
    while (!myqueue.empty()) {
        s = myqueue.front();
        myqueue.pop();
        for (ENode<T> *w = enodes[s]; w; w = w->next) {
            if (!visited[w->adjVex]) {
                visited[w->adjVex] = true;
                cout<<w->adjVex<<" ";
                myqueue.push(w->adjVex);
            }
        }
    }
}

//公有接口,宽度优先搜索
template <typename T>
void Graph<T>::BFS() {
    bool *visited = new bool[n];
    for (int i = 0; i < n; ++i) {
        visited[i] = false;
    }

    for (int j = 0; j < n; ++j) {
        if (!visited[j]) {
            BFS(j, visited);
        }
    }
    delete[] visited;
}

//用DFS思想来求逆后序序列,用于求拓扑序列或者强连通分量
template <typename T>
void Graph<T>::DFSForReversePost(int v, bool *visited) {
    visited[v] = true;
    for (ENode<T> *w = enodes[v]; w; w = w->next) {
        if (!visited[w->adjVex]) {
            DFSForReversePost(w->adjVex, visited);
        }
    }
    reversePost.push(v);    //这是和普通的DFS唯一不同的地方!
}

//通过调用DFSForReversePost来求得
template <typename T>
void Graph<T>::CalReversePost() {
    ClearReversePost();
    bool *visited = new bool[n];
    for (int i = 0; i < n; ++i) {
        visited[i] = false;
    }
    for (int j = 0; j < n; ++j) {
        if (!visited[j]) {
            DFSForReversePost(j, visited);
        }
    }


    delete[] visited;
}

//返回DFS中顶点的逆后序序列
template <typename T>
stack<int> Graph<T>::GetReversePost() {
    //因为栈的特殊性,这里直接返回一个拷贝,以保证源栈不会因为外界操作而改变
    stack<int> tmp(reversePost);

    return tmp;
}

//清空栈reversePost中的记录
template <typename T>
void Graph<T>::ClearReversePost() {
    while (!reversePost.empty()) {
        reversePost.pop();
    }
}

//用DFS思想来判断环
template <typename T>
void Graph<T>::DFSForCycle(int v, bool *visited, bool *onStack, int *edgeTo) {
    onStack[v] = true;
    visited[v] = true;

    for (ENode<T> *w = enodes[v]; w; w = w->next) {
        if (hasCycle) {
            return;
        }else if (!visited[w->adjVex]) {
            edgeTo[w->adjVex] = v;
            DFSForCycle(w->adjVex, visited, onStack, edgeTo);
        } else if (onStack[w->adjVex]) {    //此顶点已经在递归调用的栈上,再次访问说明出现环了
            //用栈cycle将环上的点都保存起来
            for (int i = v; i != w->adjVex; i = edgeTo[i]) {
                cycle.push(i);
            }
            cycle.push(w->adjVex);
            cycle.push(v);
            hasCycle = true;
        }
    }
    onStack[v] = false;     //消除在此递归调用栈上的记录,因为已经递归结束了
}

//判断是否有环
template <typename T>
bool Graph<T>::HasCycle() {
    bool *visited = new bool[n];
    bool *onStack = new bool[n];    //由顶点索引的数组,以标记递归调用栈上的所有顶点
    int *edgeTo = new int[n];       //edgeTo[i]存放指向i的边的点
    ClearCycle();

    for (int i = 0; i < n; ++i) {
        visited[i] = false;
        onStack[i] = false;
        edgeTo[i] = -1;
    }

    for (int j = 0; j < n; ++j) {
        if (!visited[j]) {
            DFSForCycle(j, visited, onStack, edgeTo);
        }
    }
    delete[] visited;
    delete[] onStack;
    delete[] edgeTo;
    if (hasCycle) {
        return true;
    } else {
        return false;
    }

}

//返回环
template <typename T>
stack<int> Graph<T>::GetCycle() {
    stack<int> tmp(cycle);
    return tmp;
}

//清空栈cycle中的记录
template <typename T>
void Graph<T>::ClearCycle() {
    while (!cycle.empty()) {
        cycle.pop();
    }
}



//计算所有顶点的入度
template <typename T>
void Graph<T>::CalInDegree(int *inDegree) {
    for (int i = 0; i < n; ++i) {
        for (ENode<T> *w = enodes[i]; w; w = w->next) {
            inDegree[w->adjVex]++;
        }
    }
}

//拓扑排序
template <typename T>
void Graph<T>::TopoSort() {
    if (HasCycle()) {       //存在环,直接返回
        cout<<"Cycle exists."<<endl;
        return;
    }

    int *inDegree = new int[n];
    for (int i = 0; i < n; ++i) {
        inDegree[i] = 0;
    }

    CalInDegree(inDegree);
    queue<int> topoQueue;
    //将入度为0的顶点存入队列
    for (int j = 0; j < n; ++j) {
        if (inDegree[j] == 0) {
            topoQueue.push(j);
        }
    }

    int v;
    int k;
    while (!topoQueue.empty()) {
        v = topoQueue.front();
        topoQueue.pop();

        cout<<v<<" ";
        for (ENode<T> *w = enodes[v]; w; w = w->next) {
            k = w->adjVex;
            inDegree[k]--;  //所有由顶点v指出的邻接点入度-1
            if (0 == inDegree[k]) {     //更新后入度为0,则存进队列
                topoQueue.push(k);
            }
        }
    }
}


//用DFS来求拓扑序列
//一幅有向无环图的拓扑顺序即为DFS中所有顶点的逆后序排列,所以只要求该逆后序排列就好
//如果该图是有环的,就说明拓扑序列不存在,尽管该逆后序排列仍能求出来
template <typename T>
void Graph<T>::TopoSortByDFS() {
    if (HasCycle()) {       //存在环,直接返回
        cout<<"Cycle exists."<<endl;
        return;
    }
    CalReversePost();

}

//用DFS思想来求强连通分量
template <typename T>
void Graph<T>::DFSForConnection(int v, bool *visited) {
    visited[v] = true;
    id[v] = connectedCount;
    for (ENode<T> *w = enodes[v]; w; w = w->next) {
        if (!visited[w->adjVex]) {
            DFSForConnection(w->adjVex, visited);
        }
    }
}

//求图的强连通分量
template <typename T>
void Graph<T>::CalculateConnection() {
    connectedCount = 0;
    bool *visited = new bool[n];
    for (int i = 0; i < n; ++i) {
        visited[i] = false;
        id[i] = 0;
    }

    //根据本图的反向图的顶点逆后序序列来进行DFS
    //所有在同一个递归DFS调用中被访问到的顶点都在同一个强连通分量中
    Graph<T> R = this->Reverse();
    R.CalReversePost();
    stack<int> topostack = R.GetReversePost();

    int j;
    while (!topostack.empty()) {
        j = topostack.top();
        topostack.pop();
        if (!visited[j]) {
            connectedCount++;
            DFSForConnection(j, visited);
        }
    }
    delete[] visited;
}

//打印强连通分量
template <typename T>
void Graph<T>::ShowConnection() {
    CalculateConnection();

    set<int> connections[connectedCount];
    for (int i = 0; i < n; ++i) {
        connections[id[i] - 1].insert(i);
    }
    for (int j = 0; j < connectedCount; ++j) {
        cout << "connection " << j + 1 << ":";
        for (set<int>::iterator set_iter = connections[j].begin(); set_iter != connections[j].end(); set_iter++) {
            cout << *set_iter << " ";
        }
        cout << endl;
    }
}

//得到强连通分量数
template <typename T>
int Graph<T>::GetConnectedCount() {
    return connectedCount;
}

//v所在的强连通分量的标识符(1~connectedCount)
template <typename T>
int Graph<T>::ConnectionID(int v) {
    return id[v];
}

//用tarjan算法求强连通分量,其实也是运用了DFS思想
/*
 * visited[u]:顶点u是否被访问过
 * dfn[u]:DFS遍历时顶点u被搜索的次序,也即时间戳
 * low[u]:顶点u能够回溯到的最早位于栈中的顶点
 * tarjanStack:用于存放每次遍历时被搜索到的顶点
 * inStack[u]:u目前是否在栈中,要配合tarjanStack使用
 * index:时间戳,随着访问的结点而递增
 */
template <typename T>
void Graph<T>::TarjanForConnection(int u,bool *visited,int *dfn,int *low,stack<int> *tarjanStack,bool *inStack,int &index) {
    dfn[u] = low[u] = ++index;      //为顶点u设访问时间戳和low初值
    visited[u] = true;              //修改为已访问
    tarjanStack->push(u);           //顶点u入栈
    inStack[u] = true;

    //搜索从顶点u指出的每个顶点
    for (ENode<T> *w = enodes[u]; w; w = w->next) {
        if (!visited[w->adjVex]) {      //顶点v还没被访问过
            TarjanForConnection(w->adjVex, visited, dfn, low, tarjanStack, inStack, index);
            //从上个递归函数返回后就是回溯过程,用u和v即w->adjVex的最小low值来更新low[u]。
            //因为顶点v能够回溯到的已经在栈中的顶点,顶点u也一定能回溯到。
            //因为存在从u到v的直接路径,所以v能够到达的顶点u也一定能够到达。
            low[u] = low[u] < low[w->adjVex] ? low[u] : low[w->adjVex];
        }else if (inStack[w->adjVex]) { //顶点v已经在栈中
            //用u的low值和v的DFN值中最小值来更新low[u]。
            //如果DFN[v]<low[u],则根据low值的定义,即能够回溯到的最早已经在栈中的顶点,所以我们应该用DFN[v]来更新low[u],
            //表示u能和v回溯到相同的最早顶点
            low[u] = low[u] < dfn[w->adjVex] ? low[u] : dfn[w->adjVex];
        }
    }

    //搜索完从顶点u指出的所有顶点后判断该结点的low值和DFN值是否相等。
    //如果相等,则该结点一定是在深度遍历过程中该强连通图中第一个被访问过的顶点,因为它的low值和DFN值最小,不会被该强连通图中其他顶点影响。
    //既然知道了该顶点是该强连通子树里的根,又根据栈的特性,则该顶点相对于同个连通图中其他顶点一定是在栈的最里面,
    //所以能通过不断地弹栈来弹出该连通子树中的所有顶点,直到弹出根结点即该顶点为止。
    if (low[u] == dfn[u]) {
        connectedCountForTarjan++;  //找到一个强连通分量,计数自增
        int x;
        do {
            x = tarjanStack->top();
            tarjanStack->pop();
            inStack[x] = false;     //注意要和tarjanStack配套使用
            tarjanConnection[connectedCountForTarjan - 1].push_back(x);
        } while (x != u);
    } else {
        return;    //不等则返回
    }
}

//用tarjan算法求强连通分量
template <typename T>
void Graph<T>::TarjanForConnection() {
    connectedCountForTarjan = 0;
    bool *visited = new bool[n];
    int *dfn = new int[n];
    int *low = new int[n];
    stack<int> *tarjanStack = new stack<int>;
    bool *inStack = new bool[n];
    int index = 0;

    memset(visited, false, n);
    memset(dfn, 0, n);
    memset(low, 0, n);
    memset(inStack, false, n);

    for (int i = 0; i < n; ++i) {
        if (!visited[i]) {
            TarjanForConnection(i, visited, dfn, low, tarjanStack, inStack, index);
        }
    }

    for (int i = 0; i < connectedCountForTarjan; ++i) {
        cout<<"connection "<<i+1<<" : ";
        for (auto ite:tarjanConnection[i]) {
            cout<<ite<<" ";
        }
        cout<<endl;
    }

    delete[] visited;
    delete[] dfn;
    delete[] low;
    delete tarjanStack;
    delete[] inStack;
}

//普里姆算法求无向图最小代价生成树,私有,内部调用
template <typename T>
void Graph<T>::Prim(int v0,int *nearest, T *lowcost) {
    bool *mark = new bool[n];
    for (int i = 0; i < n; ++i) {
        mark[i] = false;
    }
    ENode<T> *p = NULL;
    nearest[v0] = v0;
    lowcost[v0] = 0;
    mark[v0] = true;

    int k = v0;         //最近加入生成树中的顶点

    for (int i = 1; i < n ; ++i) {
        for (p = enodes[k]; p; p = p->next) {   //更新nearest和lowcost
            int j = p->adjVex;
            if (!mark[j] && lowcost[j] > p->weight) {
                nearest[j] = k;
                lowcost[j] = p->weight;
            }
        }
        T min = INT_MAX;
        for (int j = 0; j < n; ++j) {       //找到最小代价的边
            if (!mark[j] && min > lowcost[j]) {
                min = lowcost[j];
                k = j;
            }
        }
        mark[k] = true;
    }
}

//普里姆算法求无向图最小代价生成树,外部接口
template <typename T>
void Graph<T>::Prim(int v0) {
    if (v0 < 0 || v0 > n - 1) {
        cout << "input error!" << endl;
        return;
    }
    int *nearest = new int[n];      //nearest[v]=u,表示离v最近的是u,其中u在最小生成树中,v是待加入的顶点
    T *lowcost = new T[n];          //lowcost[v]=w(u,v),表示离v最近的点u之间的权值

    for (int i = 0; i < n; ++i) {
        nearest[i] = -1;
        lowcost[i] = INT_MAX;
    }

    Prim(v0, nearest, lowcost);
    cout << "(nearest[i],i,lowcost[i]) = ";
    for (int i = 0; i < n; ++i) {
        cout << "(" << nearest[i] << "," << i << "," << lowcost[i] << ")" << " ";
    }
    cout << endl;
    delete[]nearest;
    delete[]lowcost;

}


//克鲁斯卡尔算法求无向图最小代价生成树,私有,内部调用
template <typename T>
void Graph<T>::Kruskal(priority_queue<ENode<T>> &pq) {
    ENode<T> kruskalResult[n - 1];
    ENode<T> x;


    //n个结点只要加入n-1条边就可以
    int k = 0;      //已加入最小生成树中的边数

    //每次从pq中取出具有最小代价的边,并且该边的两端顶点不会都已经在树中了
    while (k < n - 1 && !pq.empty()) {
        x = pq.top();
        pq.pop();
        if (!uf->Connected(x.vertex, x.adjVex)) {  //如果找到的最小边两端点还没在最小树中相连
            kruskalResult[k] = x;
            uf->Union(x.vertex, x.adjVex);        //将两端点在树中相连
            k++;
        }
    }

    cout<<"(u,v,weight) = ";
    for (int j = 0; j < n-1; ++j) {
        cout<<"("<<kruskalResult[j].vertex<<","<<kruskalResult[j].adjVex<<","<<kruskalResult[j].weight<<") ";
    }
    cout<<endl;
}

//克鲁斯卡尔算法求无向图最小代价生成树,外部接口
template <typename T>
void Graph<T>::Kruskal() {
    priority_queue<ENode<T>,vector<ENode<T>>> pq;   //最小优先队列

    for (int i = 0; i < n; ++i) {
        for (ENode<T> *w = enodes[i]; w; w = w->next) {
            pq.push(*w);
        }
    }
    Kruskal(pq);
}

//Dijkstra算法的辅助函数,用于找出下一条最短路径的终点
template <typename T>
int Graph<T>::FinMinLen(T *curShortLen,bool *mark) {
    int minIndex = -1;
    T minLen = INT_MAX;

    for (int i = 1; i < n; ++i) {
        if (!mark[i] && minLen > curShortLen[i]) {
            minIndex = i;
            minLen = curShortLen[i];
        }
    }

    return minIndex;
}


//迪杰斯特拉算法解决单源最短路径问题,私有,内部调用
template <typename T>
void Graph<T>::Dijkstra(int v0, int *path, T *curShortLen) {
    bool *mark = new bool[n];        //mark[i]表示顶点i是否已加入单源最短路径里
    for (int i = 0; i < n; ++i) {
        mark[i] = false;
    }


    mark[v0] = true;
    path[v0] = -1;
    curShortLen[v0] = 0;

    int k = v0;                     //最近加入单源最短路径中的顶点
    int nextK = -1;                 //即将加入单源最短路径中的顶点
    ENode<T> *p;
    int count = 0;                  //用来计数单源最短路径上有多少条边

    for (int i = 1; i < n; ++i) {   //循环n-1次,将其他顶点都加入单源最短路径中
        for (p = enodes[k]; p; p = p->next) {
            int j = p->adjVex;
            if (!mark[j] && curShortLen[j] > curShortLen[k] + p->weight) {
                curShortLen[j] = curShortLen[k] + p->weight;    //保证curShortLen[j] = min{curShortLen[j],curShortLen[k]+p->weight}
            }
        }
        nextK = FinMinLen(curShortLen, mark);   //找到下一条最短路径的终点
        if (-1 == nextK) {                      //返回-1,说明从v0出发的单源最短路径已经都找到了
            break;
        } else {
            mark[nextK] = true;                     //加入最短路径中
            path[nextK] = k;                        //记录终点信息,方便回溯
            k = nextK;                              //k指向最近加入的顶点
            count++;                                //路径上的边数加1
        }
    }

    //输出最短路径结果
    cout<<"shortest path:"<<endl;
    int lastVer;    //最短路径中的上个顶点
    stack<int> tmp; //用栈来逆序储存最短路径上的顶点
    for (int i = 1; i <= count; ++i) {
        cout<<curShortLen[k]<<" : ";

        tmp.push(k);
        lastVer = path[k];
        while (lastVer != v0) {
            tmp.push(lastVer);
            lastVer = path[lastVer];
        }
        tmp.push(v0);

        cout<<tmp.top();
        tmp.pop();
        while (!tmp.empty()) {
            cout<<"->"<<tmp.top();
            tmp.pop();
        }
        cout<<endl;

        k = path[k];    //将k置为最短路径上在该点之前的那个顶点
    }
}

//迪杰斯特拉算法解决单源最短路径问题
template <typename T>
void Graph<T>::Dijkstra(int v0) {
    if (v0 < 0 || v0 > n - 1) {
        cout << "input error!" << endl;
        return;
    }
    T *curShortLen = new T[n];          //curShortLen[i]存放从源点v0到i的当前最短路径的长度
    int *path = new int[n];             //path[i]给出从v0到顶点i的最短路径上,位于顶点i前面的那个顶点

    for (int i = 0; i < n; ++i) {
        curShortLen[i] = INT_MAX;
        path[i] = -1;
    }

    Dijkstra(v0, path, curShortLen);
}

//获得边u-v的权值
template <typename T>
T Graph<T>::GetWeight(int u,int v) {
    if (u == v) {
        return 0;
    }
    ENode<T> *p = enodes[u];
    while (p != NULL && p->adjVex != v) {
        p = p->next;

    }

    if (p) {
        return p->weight;
    } else {
//        cout<<"edge "<<u<<"----"<<v<<" is not connected"<<endl;
        return INT_MAX;
    }
}

//弗洛伊德算法求所有顶点之间的最短路径
template <typename T>
void Graph<T>::Floyd() {
    int path[n][n];    //n*n矩阵,path[i][j]表示从顶点i到j的最短路径上,顶点j的前一个顶点
    T d[n][n];         //d[i][j]存放从顶点i到顶点j的当前最短路径的长度
    int i, j, k;

    //初始化矩阵
    for (i = 0; i < n; i++) {
        for (j = 0; j < n; j++) {
            d[i][j] = GetWeight(i, j);
            if (i != j && d[i][j] < INT_MAX) {
                path[i][j] = i;
            } else {
                path[i][j] = -1;
            }
        }
    }

    for (k = 0; k < n; k++) {       //n次更新矩阵
        for (i = 0; i < n; i++) {
            for (j = 0; j < n; j++) {
                if (d[i][k] + d[k][j] < d[i][j]) {  //加入顶点k之后更新矩阵
                    d[i][j] = d[i][k] + d[k][j];
                    path[i][j] = path[k][j];
                }
            }
        }
    }

    //输出所有顶点之间的最短路径结果
    cout<<"shortest path between two vertex:"<<endl;
    stack<int> tmp; //用栈来逆序储存最短路径上的顶点
    int m;
    for (i = 0; i < n; i++) {
        for (j = 0; j < n; j++) {
            if (i == j) {
                continue;
            }
            cout << "from " << i << " to " << j << " : ";

            if (path[i][j] == -1) {     //表示不可达
                cout << "not connected" << endl;
            } else {
                tmp.push(j);
                m = path[i][j];
                while (m != -1) {    //在最短路径上进行反向回溯
                    tmp.push(m);
                    m = path[i][m];
                }

                cout << tmp.top();
                tmp.pop();
                while (!tmp.empty()) {
                    cout << "->" << tmp.top();
                    tmp.pop();
                }
                cout << endl;
            }

        }
    }
}



int main() {
    int n = 6;
    Graph<int> graph(n);
    set<int> edgeInput[n];

    //无权有向无环图
    //测试DFS、BFS、环、拓扑排序:
//    edgeInput[0].insert({2, 7});
//    edgeInput[1].insert({2, 3, 4});
//    edgeInput[2].insert({3});
//    edgeInput[3].insert({5, 6});
//    edgeInput[4].insert({5});
//    edgeInput[7].insert({8});
//    edgeInput[8].insert({6});

    //无权有向有环图
    //测试DFS、BFS、环、拓扑排序、强连通分量
//    edgeInput[0].insert({1});
//    edgeInput[1].insert({2});
//    edgeInput[2].insert({0,4});
//    edgeInput[3].insert({0,2,4});
//    edgeInput[4].insert({0});
//
//
//    for (int i = 0; i < n; ++i) {
//        for (set<int>::iterator set_iter = edgeInput[i].begin(); set_iter != edgeInput[i].end(); set_iter++) {
//            graph.Insert(i, *set_iter, 1);
//        }
//    }

    //有权无向图
    //测试最小生成树的Prim算法和Kruskal算法
//    graph.Insert(0, 1, 6);
//    graph.Insert(0, 2, 1);
//    graph.Insert(0, 3, 5);
//    graph.Insert(1, 0, 6);
//    graph.Insert(1, 2, 5);
//    graph.Insert(1, 4, 3);
//    graph.Insert(2, 0, 1);
//    graph.Insert(2, 1, 5);
//    graph.Insert(2, 3, 5);
//    graph.Insert(2, 4, 6);
//    graph.Insert(2, 5, 4);
//    graph.Insert(3, 0, 5);
//    graph.Insert(3, 2, 5);
//    graph.Insert(3, 5, 2);
//    graph.Insert(4, 1, 3);
//    graph.Insert(4, 2, 6);
//    graph.Insert(4, 5, 6);
//    graph.Insert(5, 2, 4);
//    graph.Insert(5, 3, 2);
//    graph.Insert(5, 4, 6);

    //有权有向图
    //测试Dijkstra算法、Floyd算法:
    graph.Insert(0, 1, 50);
    graph.Insert(0, 2, 10);
    graph.Insert(0, 4, 70);
    graph.Insert(1, 2, 15);
    graph.Insert(1, 4, 10);
    graph.Insert(2, 0, 20);
    graph.Insert(2, 3, 15);
    graph.Insert(3, 1, 20);
    graph.Insert(3, 4, 35);
    graph.Insert(4, 3, 30);
    graph.Insert(5, 3, 3);

//    //测试深度优先遍历
//    cout<<"DFS:";
//    graph.DFS();
//    cout<<endl;
//
//    //测试宽度优先遍历
//    cout<<"BFS:";
//    graph.BFS();
//    cout<<endl;
//
//    //测试是否有环
//    if (graph.HasCycle()) {
//        cout<<"cycle:";
//        stack<int> cycle = graph.GetCycle();
//        while (!cycle.empty()) {
//            cout<<cycle.top()<<" ";
//            cycle.pop();
//        }
//        cout<<endl;
//    } else {
//        cout<<"cycle doesn't exist."<<endl;
//    }
//
//    //测试拓扑排序
//    cout<<"TopoSort:";
//    graph.TopoSort();
//    cout<<endl;
//
//    //测试用DFS来求拓扑序列
//    cout<<"TopoSort By ReversePost:";
//    graph.TopoSortByDFS();
//    stack<int> topo = graph.GetReversePost();
//    while (!topo.empty()) {
//        cout<<topo.top()<<" ";
//        topo.pop();
//    }
//    cout<<endl;
//
//    //测试强连通分量
//    graph.ShowConnection();
//    cout<<endl;

//    //测试tarjan算法求强连通分量
//    cout<<"By Tarjan algorithm:"<<endl;
//    graph.TarjanForConnection();

//    //测试普里姆算法
//    cout<<"Prim:"<<endl;
//    graph.Prim(0);
//
//    //测试克鲁斯卡尔算法
//    cout<<"Kruskal:"<<endl;
//    graph.Kruskal();

    //测试迪杰斯特拉算法
    graph.Dijkstra(0);

    //测试弗洛伊德算法
    graph.Floyd();

    return 0;
}
  • 13
    点赞
  • 40
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值