数据结构ky备战Day4 - 图

数据结构定义

邻接表

// 邻接表定义
// 边表
typedef struct ArcNode{
	int adjvex; // 顶点域
	struct ArcNode *nextarc; // 下一条边
}ArcNode;

// 表头结点表
typedef struct VertexNode{
	int data;
	ArcNode *firstNode; // 结点指向的第一条边
}VertexNode;

image-20231022210458431

// 定义图
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;
}
posted @ 2023-10-22 21:16  yuezi2048  阅读(6)  评论(0编辑  收藏  举报