数据结构定义
邻接表
// 邻接表定义
// 边表
typedef struct ArcNode{
int adjvex; // 顶点域
struct ArcNode *nextarc; // 下一条边
}ArcNode;
// 表头结点表
typedef struct VertexNode{
int data;
ArcNode *firstNode; // 结点指向的第一条边
}VertexNode;
图
// 定义图
typedef struct{
VertexNode vertex[MAX_VERTEX_NUM]; // 邻接表
int vexnum, arcnum;
int arc[MAX_VERTEX_NUM][MAX_VERTEX_NUM]; // 邻接矩阵
}Grapth;
基本操作
DFS
// dfs 伪代码描述
void DepthFirstSearch(Grapth g, int v0){
// 访问当前结点
visit(v0);
vis[v0] = true;
// 访问其下一个结点
w = FirstAdjVertex(g, v0); // 访问第一个结点
while(w != -1){
if(!vis[w]){
DepthFirstSearch(g, w); // 访问该结点的邻接点
w = NextAdjVertex(g, v0, w); // 递归回溯后,访问下一个邻接点
}
}
}
// dfs-邻接矩阵实现
void DepthFirstSearch2(Grapth g, int v0){
// 访问当前结点
visit(v0);
vis[v0] = true;
// 使用矩阵for循环遍历
for(int vj = 0; vj < g.vexnum; vj++){
if(!vis[vj] && g.arc[v0][vj] == 1) DepthFirstSearch(g, vj);
}
}
// dfs-邻接表实现 O(n+e)
// dfs 模板
void DepthFirstSearch3(Grapth g, int v0){
// 访问当前结点
visit(v0);
vis[v0] = true;
// 访问其第一个结点
ArcNode *p = g.vertex[v0].firstNode;
while(p != NULL){
if(!vis[p->adjvex]) DepthFirstSearch(g,p->adjvex);
p = p->nextarc;
}
}
// 调用dfs实现遍历,为什么还要来个for循环?
//我的理解是一次dfs只能遍历图的一个联通分量,没有访问到的必须借助该循环再来一次DFS
void TraverseGrapth(Grapth g){
for(int vi = 0; vi < g.vexnum; vi ++) vis[vi] = false; // 初始化
for(int vi = 0; vi < g.vexnum; vi ++){
if(!vis[vi]) DepthFirstSearch(g, vi);
// cnt ++; // 这里可以用来统计树的联通子图个数
}
}
DFS(非递归)
// dfs 非递归伪代码 感受一下和递归的区别处在哪
void DepthFirstSearch_v2(Grapth g, int v0){
InitStack(&S); // 初始化一个栈
Push(&S, v0);
while(!IsEmpty(S)){
pop(&S, &v);
if(!vis[v]){ // 栈中可能有重复结点
visit(v);
vis[v] = true;
w = FindAdjVertex(g, v); // 查找v的第一个邻接点
while(w != -1){
if(! vis[w]) Push(&s, w);
w = NextAdjVertex(g, v, w); // 查找v在w后的下一个结点
}
}
}
}
BFS(附单源最短路问题)
// bfs 伪代码描述最终版本
// 可以类比层次遍历,要是说找路径的话 需要记录一下前驱
int pre[MAX_VERTEX_NUM] = {-1}; // 更新前驱,我们最后往前找即可
void BreathFirstSearch(Grapth g, int v0){
visit(v0);
vis[v0] = true;
InitQueue(&Q);
EnterQueue(&Q, v0);
while(!empty(Q)){
DeleteQueue(&Q, &v);
w = FirstAdjVertex(g, v); // v的第一个邻接点
while(w!=-1){
if(!vis[w]){
visit(w);
int u = g.vertex[v]->data;
int v = g.vertex[w]->data;
printf("(%d,%d) ", u, v); // 输出该结点
pre[v] = u; // 记录u到达该结点的最短前驱结点
vis[w] = true;
EneterQueue(&Q, w);
}
w = NextAdjVertex(g, v ,w); // v下一个邻接点
}
}
}
// 因为图可能多个联通子图,不写这个的话咱只能遍历一组了。。
int TraverseGrapth2(Grapth g){
for(int vi = 0; vi < g.vexnum; vi ++) vis[vi] = false; // 初始化
for(int vi = 0; vi < g.vexnum; vi ++){
if(!vis[vi]) BreathFirstSearch(g, vi);
// cnt ++; // 这里可以用来统计树的联通子图个数
// prtintf("\n") ; // 或者比如说要输出每个联通分量的结点,这里可以换行输出下一个树
}
// 接下来要输出 u 到 v 的路径了,我们因为只有逆序列,为了倒过来输出,借助栈
Push(S, v);
if(vis[v]){
while(v != u){
v = pre[v];
Push(S, v)
if(v == -1) return Error; // 查找失败
}
Push(S, u);
}
while(!empty(S)){
pop(S, i);
printf("%d ", G.vertex[i]->data);
}
}
拓扑排序
// 拓扑排序
// 子程序:找入度为0的顶点入栈,然后出栈,周围邻接的入度-1,减掉后为0的进栈,通过计数器cnt来判别成功与否
void FindID(Grapth G, int indegree[]){
// 求每个顶点的入度
ArcNode *p;
for(int i = 0; i < G.vexnum; i++){
indegree[i] = 0; //初始化为0
}
for(int i = 0; i < G.vexnum; i++){
p = G.vertex[i].firstNode;
while(p != NULL){
indegree[p->adjvex] ++;
p = p->nextarc;
}
}
}
void TopoSort(Grapth G){
Stack S;
ArcNode *p; // 临时遍历边表的指针
int k ; // 遍历邻接结点时用以临时存储结点的
int indegree[MAX_VERTEX_NUM];
FindID(G, indegree); // 初始化,求各顶点入度
InitStack(&S);
for(int i = 0;i <G.vexnum; i++){
if(indegree[i] == 0) Push(&S, i);
}
int cnt = 0;
while(!Empty(S)){
// 度为0的顶点出栈 输出
Pop(&S, &i);
printf("%c ", G.vertex[i].data);
cnt ++; // 每输出一个结点,计数器加1
p = G.vertex[i].firstNode;
// 对该顶点邻接点的入度 -1
while(!p){
k = p->adjvex;
indegree[k]--; // 邻接点的每个结点入度-1
if(indegree[k] == 0) Push(&S, k); // 入度为0 进栈
p = p->nextarc;
}
}
if(G.vexnum == cnt) return OK;
else return Error;
}