C++ 数据结构学习 ---- 图

目录

1. 头文件

1.1图

1.2 邻接矩阵头文件

2. 图的相关算法

2.1 广度优先搜索算法

2.2 深度优先搜索算法

2.3  基于DFS的 双连通分量分解(BCC)算法

2.4  基于BFS的 双连通分量分解(BCC)算法

3. 支撑树搜索算法

3.1 最短路径Dijkstra算法

3.2 Prim算法

3.3 拓扑排序算法

3.4 优先级搜索

3.5 更新器

4. 运行结果截图


1. 头文件

1.1图

#include "Stack.h"
#include"Queue.h"

#define hca(x) (fTime(x)) //利用此处闲置的fTime[]充当hca[]

using VStatus = enum { UNDISCOVERED, DISCOVERED, VISITED }; //顶点状态
using EType = enum { UNDETERMINED, TREE, CROSS, FORWARD, BACKWARD }; //边在遍历树中所属的类型

//图Graph模板类
template <typename Tv, typename Te> class Graph {
private:
    void reset() { //所有顶点、边的辅助信息复位
        for (Rank v = 0; v < n; v++) { //所有顶点的
            status(v) = UNDISCOVERED; dTime(v) = fTime(v) = -1; //状态,时间标签
            parent(v) = -1; priority(v) = INT_MAX; //(在遍历树中的)父节点,优先级数
            for (Rank u = 0; u < n; u++) //所有边的
                if (exists(v, u)) type(v, u) = UNDETERMINED; //类型
        }
    }
    void BFS(Rank, int&); //(连通域)广度优先搜索算法
    void DFS(Rank, int&); //(连通域)深度优先搜索算法
    void BCC(Rank, int&, Stack<Rank>&); //(连通域)基于DFS的双连通分量分解算法
    bool TSort(Rank, int&, Stack<Tv>*); //(连通域)基于DFS的拓扑排序算法
    template <typename PU> void PFS(Rank, PU); //(连通域)优先级搜索框架
public:
    // 顶点
    int n; //顶点总数
    virtual Rank insert(Tv const&) = 0; //插入顶点,返回编号
    virtual Tv remove(Rank) = 0; //删除顶点及其关联边,返回该顶点信息
    virtual Tv& vertex(Rank) = 0; //顶点的数据(该顶点的确存在)
    virtual int inDegree(Rank) = 0; //顶点的入度(该顶点的确存在)
    virtual int outDegree(Rank) = 0; //顶点的出度(该顶点的确存在)
    virtual Rank firstNbr(Rank) = 0; //顶点的首个邻接顶点
    virtual Rank nextNbr(Rank, Rank) = 0; //顶点(相对当前邻居的)下一邻居
    virtual VStatus& status(Rank) = 0; //顶点的状态
    virtual int& dTime(Rank) = 0; //顶点的时间标签dTime
    virtual int& fTime(Rank) = 0; //顶点的时间标签fTime
    virtual Rank& parent(Rank) = 0; //顶点在遍历树中的父亲
    virtual int& priority(Rank) = 0; //顶点在遍历树中的优先级数
 // 边:这里约定,无向边均统一转化为方向互逆的一对有向边,从而将无向图视作有向图的特例
    int e; //边总数
    virtual bool exists(Rank, Rank) = 0; //边(v, u)是否存在
    virtual void insert(Te const&, int, Rank, Rank) = 0; //在两个顶点之间插入指定权重的边
    virtual Te remove(Rank, Rank) = 0; //删除一对顶点之间的边,返回该边信息
    virtual EType& type(Rank, Rank) = 0; //边的类型
    virtual Te& edge(Rank, Rank) = 0; //边的数据(该边的确存在)
    virtual int& weight(Rank, Rank) = 0; //边(v, u)的权重
 // 算法
    void bfs(Rank); //广度优先搜索算法
    void dfs(Rank); //深度优先搜索算法
    void bcc(Rank); //基于DFS的双连通分量分解算法
    Stack<Tv>* tSort(Rank); //基于DFS的拓扑排序算法
    void prim(Rank); //最小支撑树Prim算法
    void dijkstra(Rank); //最短路径Dijkstra算法
    template <typename PU> void pfs(Rank, PU); //优先级搜索框架
};

1.2 邻接矩阵头文件

#include"Graph.h"


//顶点对象(为简化起见,并未严格封装)
template <typename Tv> struct Vertex { 
	Tv data; int inDegree, outDegree; VStatus status; //数据、出入度数、状态
	int dTime, fTime; //时间标签
	Rank parent; int priority; //在遍历树中的父节点、优先级数
	Vertex(Tv const& d = (Tv)0) : //构造新顶点
	       data(d), inDegree(0), outDegree(0), status(UNDISCOVERED),
	       dTime(-1), fTime(-1), parent(-1), priority(INT_MAX) {} //暂不考虑权重溢出
	
};
//边对象(为简化起见,并未严格封装)
 template <typename Te> struct Edge { 
	  Te data; int weight; EType type; //数据、权重、类型
	  Edge(Te const& d, int w) : data(d), weight(w), type(UNDETERMINED) {} //构造
	
};

 template <typename Tv, typename Te> //顶点类型、边类型
 class GraphMatrix : public Graph<Tv, Te> { //基于向量,以邻接矩阵形式实现的图
 private:
	    Vector< Vertex< Tv > > V; //顶点集(向量)
	    Vector< Vector< Edge< Te >* > > E; //边集(邻接矩阵)
public:
	GraphMatrix() { Graph<Tv, Te>::n = Graph<Tv, Te>::e = 0; } //构造
	~GraphMatrix() { //析构
	       for (Rank v = 0; v < Graph<Tv, Te>::n; v++) //所有动态创建的
		         for (Rank u = 0; u < Graph<Tv, Te>::n; u++) //边记录
		            delete E[v][u]; //逐条清除
	}
	 // 顶点的基本操作:查询第v个顶点(0 <= v < n)
	    virtual Tv & vertex(Rank v) { return V[v].data; } //数据
	    virtual int inDegree(Rank v) { return V[v].inDegree; } //入度
	    virtual int outDegree(Rank v) { return V[v].outDegree; } //出度
	    virtual Rank firstNbr(Rank v) { return nextNbr(v, Graph<Tv, Te>::n); } //首个邻接顶点
	    virtual Rank nextNbr(Rank v, Rank u) //相对于顶点u的下一邻接顶点(改用邻接表可提高效率)
	    { while ((-1 < u) && (!exists(v, --u))); return u; } //逆向线性试探
	    virtual VStatus & status(Rank v) { return V[v].status; } //状态
	    virtual int& dTime(Rank v) { return V[v].dTime; } //时间标签dTime
	    virtual int& fTime(Rank v) { return V[v].fTime; } //时间标签fTime
	    virtual Rank & parent(Rank v) { return V[v].parent; } //在遍历树中的父亲
	    virtual int& priority(Rank v) { return V[v].priority; } //在遍历树中的优先级数
	 // 顶点的动态操作
		    virtual Rank insert(Tv const& vertex) { //插入顶点,返回编号
		       for (Rank u = 0; u < Graph<Tv, Te>::n; u++) E[u].insert(NULL);  Graph<Tv, Te>::n++; //各顶点预留一条潜在的关联边
		       E.insert(Vector<Edge<Te>*>(Graph<Tv, Te>::n, Graph<Tv, Te>::n, (Edge<Te>*) NULL)); //创建新顶点对应的边向量
		       return V.insert(Vertex<Tv>(vertex)); //顶点向量增加一个顶点
		
	}
	 virtual Tv remove(Rank v) { //删除第v个顶点及其关联边(0 <= v < n)
	    for (Rank u = 0; u < Graph<Tv, Te>::n; u++) //所有出边
	          if (exists(v, u)) { delete E[v][u]; V[u].inDegree--;  Graph<Tv, Te>::e--; } //逐条删除
	    E.remove(v);  Graph<Tv, Te>::n--; //删除第v行
	    Tv vBak = vertex(v); V.remove(v); //删除顶点v
	    for (Rank u = 0; u < Graph<Tv, Te>::n; u++) //所有入边
	          if (Edge<Te>* x = E[u].remove(v)) { delete x; V[u].outDegree--;  Graph<Tv, Te>::e--; } //逐条删除
	    return vBak; //返回被删除顶点的信息
	}
	 // 边的确认操作
		    virtual bool exists(Rank v, Rank u) //边(v, u)是否存在
		    { return (v < Graph<Tv, Te>::n) && (u < Graph<Tv, Te>::n) && E[v][u] != NULL; }
	 // 边的基本操作:查询顶点v与u之间的联边(0 <= v, u < n且exists(v, u))
		    virtual EType & type(Rank v, Rank u) { return E[v][u]->type; } //边(v, u)的类型
	    virtual Te & edge(Rank v, Rank u) { return E[v][u]->data; } //边(v, u)的数据
	    virtual int& weight(Rank v, Rank u) { return E[v][u]->weight; } //边(v, u)的权重
	 // 边的动态操作
		    virtual void insert(Te const& edge, int w, Rank v, Rank u) { //插入权重为w的边(v, u)
		     //  if (exists(v, u)) return; //确保该边尚不存在
				if (v < 0 || u > this->n-1) {
				cout << "插入有误!" << endl;
				return;
				} 
		       E[v][u] = new Edge<Te>(edge, w); //创建新边
			   Graph<Tv, Te>::e++; V[v].outDegree++; V[u].inDegree++; //更新边计数与关联顶点的度数
		
	}
	virtual Te remove(Rank v, Rank u) { //删除顶点v和u之间的联边(exists(v, u))
	       Te eBak = edge(v, u); delete E[v][u]; E[v][u] = NULL; //备份后删除边记录
		   Graph<Tv, Te>::e--; V[v].outDegree--; V[u].inDegree--; //更新边计数与关联顶点的度数
	       return eBak; //返回被删除边的信息
	
	}
};

2. 图的相关算法

2.1 广度优先搜索算法

//广度优先搜索BFS算法(全图)
 template < typename Tv, typename Te> void Graph<Tv, Te>::bfs(Rank s) { //s < n
    reset(); int clock = 0; Rank v = s; //初始化
    do //逐一检查所有顶点
           if (UNDISCOVERED == status(v)) //一旦遇到尚未发现的顶点
              BFS(v, clock); //即从该顶点出发启动一次BFS
    while (s != (v = ((v + 1) % n))); //按序号检查,故不漏不重
    cout << "广度优先搜索BFS算法执行成功!" << endl;
}
//广度优先搜索BFS算法(单连通域)
 template < typename Tv, typename Te> void Graph<Tv, Te>::BFS(Rank v, int& clock) { //v < n
     Queue<Rank> Q; //引入辅助队列
     status(v) = DISCOVERED; Q.enqueue(v); //初始化起点
     dTime(v) = clock; clock = 0; //dTime继承自前一联通/可达分量
     while (!Q.empty()) { //在Q变空之前,不断
         // 可以在此处输出顶点
           Rank v = Q.dequeue(); //轮到队首顶点v接受访问
           for (Rank u = firstNbr(v); -1 < u; u = nextNbr(v, u)) //枚举v的所有邻居u
              if (UNDISCOVERED == status(u)) { //若u尚未被发现,则
                        status(u) = DISCOVERED; Q.enqueue(u); dTime(u) = dTime(v) + 1; //发现该顶点
                        type(v, u) = TREE; parent(u) = v; //引入树边拓展支撑树
            
        }
            else { //若u已被发现,或者甚至已访问完毕,则
                         type(v, u) = CROSS; //将(v, u)归类于跨边
            
        }
         status(v) = VISITED; fTime(v) = clock++; //v访问完毕
         if (Q.empty())
                clock = dTime(v) + 1; //为可能的下一连通/可达分量,预备好起始顶点的dTime
         else if (dTime(v) < dTime(Q.front()))
                clock = 0; //dTime的增加,意味着开启新的一代  
    }
}

2.2 深度优先搜索算法

 //深度优先搜索DFS算法(全图)
 template <typename Tv, typename Te>void Graph<Tv, Te>::dfs(Rank s) { //s < n
     reset(); int clock = 0; Rank v = s; //初始化
     do //逐一检查所有顶点
         if (UNDISCOVERED == status(v)) //一旦遇到尚未发现的顶点
             DFS(v, clock); //即从该顶点出发启动一次DFS
     while (s != (v = ((v + 1) % n))); //按序号检查,故不漏不重
     cout << "广度优先搜索DFS算法执行成功!" << endl;
 }
//深度优先搜索DFS算法(单个连通域)
 template <typename Tv, typename Te> void Graph<Tv, Te>::DFS(Rank v, int& clock) { //v < n
     dTime(v) = ++clock; status(v) = DISCOVERED; //发现当前顶点v
     for (Rank u = firstNbr(v); -1 < u; u = nextNbr(v, u)) //枚举v的所有邻居u
         switch (status(u)) { //并视其状态分别处理
         case UNDISCOVERED: //u尚未发现,意味着支撑树可在此拓展
             type(v, u) = TREE; parent(u) = v; DFS(u, clock); break;
         case DISCOVERED: //u已被发现但尚未访问完毕,应属被后代指向的祖先
             type(v, u) = BACKWARD; break;
         default: //u已访问完毕(VISITED,有向图),则视承袭关系分为前向边或跨边
             type(v, u) = (dTime(v) < dTime(u)) ? FORWARD : CROSS; break;
         }
     status(v) = VISITED; fTime(v) = ++clock; //至此,当前顶点v方告访问完毕
 }

2.3  基于DFS的 双连通分量分解(BCC)算法

//基于DFS的双连通分量分解算法
 template <typename Tv, typename Te> void Graph<Tv, Te>::BCC(Rank v, int& clock, Stack<Rank>& S) { //assert: 0 <= v < n
     hca(v) = dTime(v) = ++clock; status(v) = DISCOVERED; S.push(v); //v被发现并入栈
     for (int u = firstNbr(v); -1 < u; u = nextNbr(v, u)) //枚举v的所有邻居u
         switch (status(u)) { //并视u的状态分别处理
         case UNDISCOVERED:
             parent(u) = v; type(v, u) = TREE; BCC(u, clock, S); //从顶点u处深入
             if (hca(u) < dTime(v)) //遍历返回后,若发现u(通过后向边)可指向v的真祖先
                 hca(v) = min(hca(v), hca(u)); //则v亦必如此
             else //否则,以v为关节点(u以下即是一个BCC,且其中顶点此时正集中于栈S的顶部)
                  {
                 cout << "BCC的根节点是:" << vertex(v);
                Stack<int> temp; 
                do { 
                    temp.push(S.pop()); 
                   // cout << vertex(temp.top());
                } while (u != temp.top()); 
               // cout << vertex(parent(u));
                while (!temp.empty()) S.push(temp.pop());
                 while (u != S.pop()); //弹出当前BCC中(除v外)的所有节点,可视需要做进一步处理
                 cout << endl;
             }
             break;
         case DISCOVERED:
             type(v, u) = BACKWARD; //标记(v, u),并按照“越小越高”的准则
             if (u != parent(v)) hca(v) = min(hca(v), dTime(u)); //更新hca[v]
             break;
         default: //VISITED (digraphs only)
             type(v, u) = (dTime(v) < dTime(u)) ? FORWARD : CROSS;
             break;
         }
     status(v) = VISITED; //对v的访问结束
 }

2.4  基于BFS的 双连通分量分解(BCC)算法

/基于DFS的BCC分解算法
 template <typename Tv, typename Te> void Graph<Tv, Te>::bcc(Rank s) { 
     reset(); int clock = 0; int v = s; Stack<int> S; //栈S用以记录已访问的顶点
     do
         if (UNDISCOVERED == status(v)) { //一旦发现未发现的顶点(新连通分量)
             BCC(v, clock, S); //即从该顶点出发启动一次BCC
             S.pop(); //遍历返回后,弹出栈中最后一个顶点——当前连通域的起点
         }
     while (s != (v = (++v % n)));
     cout << "成功调用了基于DFS的BCC分解算法!" << endl;
 }

3. 支撑树搜索算法

3.1 最短路径Dijkstra算法

//最短路径Dijkstra算法:适用于一般的有向图
 template <typename Tv, typename Te>  void Graph<Tv, Te>::dijkstra(Rank s) { //s < n
     reset(); priority(s) = 0;
     for (int i = 0; i < n; i++) { //共需引入n个顶点和n-1条边
         status(s) = VISITED;
         if (-1 < parent(s)) type(parent(s), s) = TREE; //引入当前的s
         for (Rank j = firstNbr(s); -1 < j; j = nextNbr(s, j)) //枚举s的所有邻居j
             if ((status(j) == UNDISCOVERED) && (priority(j) > priority(s) + weight(s, j))) //对邻接顶点j做松弛
             {
                 priority(j) = priority(s) + weight(s, j); parent(j) = s;
             } //与Prim算法唯一的不同之处
         for (int shortest = INT_MAX, j = 0; j < n; j++) //选出下一最近顶点
             if ((status(j) == UNDISCOVERED) && (shortest > priority(j)))
             {
                 shortest = priority(j); s = j;
             }
     }
     cout << "dijkstra算法运行成功!"<<endl;
 } //对于无向连通图,假设每一条边表示为方向互逆、权重相等的一对边

3.2 Prim算法

/Prim算法:无向连通图,各边表示为方向互逆、权重相等的一对边
 template <typename Tv, typename Te> void Graph<Tv, Te>::prim(Rank s) { //s < n
     reset(); priority(s) = 0;
     for (int i = 0; i < n; i++) { //共需引入n个顶点和n-1条边
         status(s) = VISITED;
         if (-1 < parent(s)) type(parent(s), s) = TREE; //引入当前的s
         for (Rank j = firstNbr(s); -1 < j; j = nextNbr(s, j)) //枚举s的所有邻居j
             if ((status(j) == UNDISCOVERED) && (priority(j) > weight(s, j))) //对邻接顶点j做松弛
             {
                 priority(j) = weight(s, j); parent(j) = s;
             } //与Dijkstra算法唯一的不同之处
         for (int shortest = INT_MAX, j = 0; j < n; j++) //选出下一极短跨边
             if ((status(j) == UNDISCOVERED) && (shortest > priority(j)))
             {
                 shortest = priority(j); s = j;
             }
     }
     cout << "Prim算法运行成功!"<<endl;
 }

3.3 拓扑排序算法

//基于DFS的拓扑排序算法
 template <typename Tv, typename Te> Stack<Tv>* Graph<Tv, Te>::tSort(Rank s) { //assert: 0 <= s < n
     reset(); int clock = 0; Rank v = s;
     Stack<Tv>* S = new Stack<Tv>; //用栈记录排序顶点
     do {
         if (UNDISCOVERED == status(v))
             if (!TSort(v, clock, S)) { //clock并非必需
                 while (!S->empty()) //任一连通域(亦即整图)非DAG
                     S->pop(); break; //则不必继续计算,故直接返回
             }
     } while (s != (v = (++v % n)));
     cout << "基于DFS的拓扑排序算法运行成功!"<<endl;
     return S; //若输入为DAG,则S内各顶点自顶向底排序;否则(不存在拓扑排序),S空
 }
 //基于DFS的拓扑排序算法(单趟)
 template <typename Tv, typename Te>bool Graph<Tv, Te>::TSort(Rank v, int& clock, Stack<Tv>* S) { //v < n
     dTime(v) = ++clock; status(v) = DISCOVERED; //发现顶点v
     for (Rank u = firstNbr(v); -1 < u; u = nextNbr(v, u)) //枚举v的所有邻居u
         switch (status(u)) { //并视u的状态分别处理
         case UNDISCOVERED:
             parent(u) = v; type(v, u) = TREE;
             if (!TSort(u, clock, S)) //从顶点u处出发深入搜索
                 return false; //若u及其后代不能拓扑排序(则全图亦必如此),故返回并报告
             break;
         case DISCOVERED:
             type(v, u) = BACKWARD; //一旦发现后向边(非DAG),则
             return false; //不必深入,故返回并报告
         default: //VISITED (digraphs only)
             type(v, u) = (dTime(v) < dTime(u)) ? FORWARD : CROSS;
             break;
         }
     status(v) = VISITED; S->push(vertex(v)); //顶点被标记为VISITED时,随即入栈
     return true; //v及其后代可以拓扑排序
 }

3.4 优先级搜索

//优先级搜索(全图)
 template <typename Tv, typename Te> template <typename PU> void Graph<Tv, Te>::pfs(Rank s, PU prioUpdater) { //s < n
     reset(); Rank v = s; //初始化
     do //逐一检查所有顶点
         if (UNDISCOVERED == status(v)) //一旦遇到尚未发现的顶点
             PFS(v, prioUpdater); //即从该顶点出发启动一次PFS
     while (s != (v = ((v + 1) % n))); //按序号检查,故不漏不重
 }
//优先级搜索(单个连通域)/*优先级更新器*/
 template <typename Tv, typename Te> template <typename PU> void Graph<Tv, Te>::PFS(Rank v, PU prioUpdater)

3.5 更新器

/*优先级搜索对应的优先级更新器*/
 //BFS算法对应的优先级更新器
 template<typename Tv, typename Te>struct  BfsPu {
     virtual void operator()(Graph<Tv, Te>* g, int uk, int v) {
         if (g->status(v) == UNDISCOVERED)
             if (g->priority(v) > g->priority(uk )+1) {
                 g->priority(v) = g->priority(uk) + 1;
                 g->parent(v) = uk;
             }
     }
     BfsPu() {
         cout << "成功调用了pfs算法对应的BFS优先级更新器!"<<endl;
     }
 };
 //DFS算法对应的优先级更新器
 template<typename Tv, typename Te>struct  DfsPu {
     virtual void operator()(Graph<Tv, Te>* g, int uk, int v) {
         if (g->status(v) == UNDISCOVERED)
             if (g->priority(v) > g->priority(uk)-1) {
                 g->priority(v) = g->priority(uk)-1;
                 g->parent(v) = uk;
             }
     }
     DfsPu(){
         cout << "成功调用了pfs算法对应的DFS优先级更新器!" << endl;
     }
 };
 //针对Dijkstra算法的顶点优先级更新器
 template <typename Tv, typename Te> struct DijkPU {
     virtual void operator() (Graph<Tv, Te>* g, Rank v, Rank u) {
         if (UNDISCOVERED == g->status(u)) //对于v每一尚未被发现的邻接顶点u,按Dijkstra策略
             if (g->priority(u) > g->priority(v) + g->weight(v, u)) { //做松弛
                 g->priority(u) = g->priority(v) + g->weight(v, u); //更新优先级(数)
                 g->parent(u) = v; //并同时更新父节点
             }
     }
     DijkPU(){
         cout << "成功调用了pfs算法对应的dijkstra优先级更新器!" << endl;
     }
 };
 //针对Prim算法的顶点优先级更新器
 template <typename Tv, typename Te> struct PrimPU {
     virtual void operator() (Graph<Tv, Te>* g, Rank v, Rank u) {
         if (UNDISCOVERED == g->status(u)) //对于v每一尚未被发现的邻接顶点u
             if (g->priority(u) > g->weight(v, u)) { //按Prim策略做松弛
                 g->priority(u) = g->weight(v, u); //更新优先级(数)
                 g->parent(u) = v; //更新父节点
             }
     }
     PrimPU(){
         cout << "成功调用了pfs算法对应的prim优先级更新器!" << endl;
     }
 };

4. 运行结果截图

以上相关代码参考邓俊辉老师的《c++语言版数据结构》!

猜你喜欢

转载自blog.csdn.net/qq_58240448/article/details/127480487
今日推荐