邓俊辉《数据结构》学习笔记-第六章 图(自用)
1.基本术语
2.表示和实现
2.1 两种表示方式
2.2 实现
Graph模板类:
template <typename Tv,typename Te> class Graph{//顶点类型,边类型
private:
int n;//顶点
int e;//边
void reset(){//所有顶点、边的辅助信息复位
for(int i=0;i<n;i++){
status(i)=UNDISCOVERED;//顶点的状态信息
dTime(i)=fTime(i)=-1;//顶点的时间标签
parent(i)=-1;//父节点信息
priority(i)=INT_MAX;//优先级
for(int j=0;j<n;j++)//边
if(exisits(i,j)) //判断两个顶点是否存在联边
status(i,j)=UNDETERMINED;//设置边的状态
}
}
public:
/*...顶点操作、边操作、图算法...无论如何实现,接口必须统一!*/
};
想要实现这些接口,就必须先把一个图给表示出来,在基本术语里已经知道图是由顶点和边组成的,所以先将顶点和边类实现
Vertex顶点类:
typedef enum{UNDISCOVERED,DISCOVERED,VISTED} VStatus;
template <typename Tv> struct Vertex{//顶点对象(并未严格封装)
Tv data;int inDegree,outDegree;//数据,出入度数
VStatus status;//状态
int dTime,fTime;//时间标签,记录顶点被发现以及访问完毕的时刻
int parent;//在遍历所生成的遍历树中的父节点
int priority;//基于优先级的遍历算法中,记录在遍历树中的优先级(最短通路,极短跨边等)
Vertex(Tv const & d)://构造新顶点
data(d), inDegree(0),outDegree(0),status(UNDISCOVERED),
dTime(-1),fTime(-1), parent(-1), priority(INT_MAX){}
};
Edge边类:
ypedef enum{UNDETERMINED,TREE,CROSS,FORWARD,BACKWARD} EStatus;
template <typename Te> struct Edge{//边对象(并未严格封装)
Te data;//数据
int weight;//权重
EStatus status;//类型
Edge(Te const & d,int w)://构造新边
data(d),weight(w),status(UNDISCOVERED){}
};
2.2.1 基于Adjacency_Matrix 邻接矩阵(空间利用率极低)
template <typename Tv,typename Te> class GraphMatrix:public Graph<Tv,Te>{//邻接矩阵
private:
vector<Vertex<Tv>> V;//顶点集
vector<vector<Edge<Te>*>> E;//边集
public:
/*操作接口:顶点相关、边相关*/
GraphMatrix(){n=e=0;}//构造
~GraphMatrix(){//析构
for(int j=0;j<n;j++)
for(int k=0;k<e;k++)
delete E[j][k];
}
};
由于vector类对[ ]操作符的重载,所以可以很方便的对顶点和边操作
静态操作-顶点操作
除此之外,还要实现对于任意顶点枚举其所有的邻接顶点
int nextNbr(int i,int j){//若已经枚举至邻居j,则转向下一个邻居
while((-1<j)&&!exists(i,--j));//逆向查找
return j;
}
int firstNbr(int i){
return nextNbr(i,n);//尽管n不存在,但是可以将其假象为一个哨兵
}
静态操作-边操作
动态操作-边操作(矩阵规模不发生变化)
1.插入
void insert (Te const & edge,int w,int i,int j){
if(exists(i,j)) return;//忽略已有的边
E[i][j]=new Edge<Te>(edge,w);//创建新边
e++;
V[i].outDegree++;
V[j].inDegree++;
}
2.删除
void remove (int i,int j){
Te eBak=edge(i,j);//备份边
delete E[i][j];
E[i][j]=NULL;//删除边
e--;//更新边计数
V[i].outDegree--;
V[j].inDegree--;
return eBak;
}
动态操作-顶点操作(矩阵规模变化)
1.插入
int insert(Tv const & vertex){//返回编号
for(int j=0;j<n;j++) E[j].insert(NULL);n++;//1
E.insert( vector<Edge<Te>*>(n,n,NULL) );//2,3:E中插入一个记录边的行向量,总数为n
return V.insert(Vertex<Tv>(vertex));//4
}
2.删除
Tv remove (int i){//删除顶点及其联边,返回该顶点的信息
for(int j=0;j<n;j++)
if(exists(i,j)){//删除所有出边
delete E[i][j];
V[j].inDegree--;
}
E.remove(i); n--;//删除第i行
for(int j=0;j<n;j++)
if(exists(j,i)){//删除所有入边及第i列
delete E[j].remove(j); V[j].outDegree--;
}
Tv vBak=vertex(i);
V.remove(i);//删除顶点i
return vBak;
}
2.2.2 基于Adjacency_List 邻接表
2.2.3 两种方式对比
3.遍历
3.1 Breadth-First Search BFS广度优先搜索
概念
实现
template <typename Tv,typename Te>
void Graph<Tv,Te>::BFS(int v,int & clock){
queue<int> Q;
status(v)=DISCOVERED;
Q.enqueue(v);
while(!Q.empty()){
int v=Q.dequeue();
dTime(v)=++clock;//取出队首顶点并
for(int u=firstNbr(v);-1<u;u=nextNbr(v,u)){//考察每一邻居
if(UNDISCOVERED==status(u)){
status(u)=DISCOVERED;
Q.enqueue(u);//发现该点
status(v,u)=TREE;//引入树边
parent(u)=v;
}
else{
status(v,u)=CROSS;//归于跨边
}
}
status(v)=VISITED;//至此,当前顶点访问完毕
}
}
若一幅图存在多个连通域
template <typename Tv,typename Te>
void Graph<Tv,Te>::bfs(int s){//s为起始点
reset();
int clock=0;int v=s;
do{//逐一检查所有顶点,一旦遇到尚未发现的顶点
if(UNDISCOVERED==status(v))
BFS(v,clock);//即从该顶点出发启动一次BFS
}
while(s != (v = (++v%n) ) );//按序号访问
}
树/森林:
3.2 Depth-First Search DFS深度优先搜索
概念
实现(仔细揣摩老师说的实例!06-D-5)
template <typename Tv,typename Te>
void Graph<Tv,Te>::DFS(int v,int & clock){
dTime(v)=++clock;
status(v)=DISCOVERED;//发现当前顶点
for(int u=firstNbr(v);-1<u;u=nextNbr(v,u)){//考察每一邻居,注意是要等邻居都遍历完毕才转交控制权
switch(status(u)){
case UNDISCOVERED://u尚未被发现,意味着支撑树可在此发展
status(v,u)=TREE;
parent(u)=v;
DFS(u,clock);//递归
break;
case DISCOVERED:
status(v,u)=BACKWARD;//回向边 (后代指向祖先)
break;
default://看u和v谁更早被发现,分为前向边(祖先指向后代)或者跨边 (两个顶点之间没有祖先或者直系后代的血缘关系 )
status(v,u)=dTime(v)<dTime(u)?FORWARD:CROSS;
break;
}
}
status(v)=VISITED;//至此,当前顶点访问完毕
fTime(v)=++clock;//节点被访问完毕的时刻
}
但是可能有的顶点并不能被访问到,这时需要在外层包装一层遍历所有顶点的循环
template <typename Tv,typename Te>
void Graph<Tv,Te>::dfs(int s){
reset();
int clock=0;int v=s;
do{//逐一检查所有顶点,一旦遇到尚未发现的顶点
if(UNDISCOVERED==status(v))
DFS(v,clock);//即从该顶点出发启动一次DFS
}
while(s != (v = (++v%n) ) );//按序号访问
}
关于括号引理: