目录
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++语言版数据结构》!