众所周知图的遍历有八种:有向、无向->邻接矩阵、邻接表->DFS、BFS,所以不可能都写一遍 如果有机会再补上吧
一、代码
#include<stdio.h>
#include<stdlib.h>
#define MAXSIZE 10
#define MaxV 10 //顶点最大数量
typedef struct stack{
int data[MAXSIZE];
int top;
}stack;
typedef struct Graph{
char vertex[MaxV]; //顶点数组
int edge[MaxV][MaxV]; //邻接矩阵 , 顶点的二维数组也就是 ,边表
int vertexNums , edgeNums ; // 图的当前顶点数 和 边数
}Graph;
void initGraph(Graph *graph , int vertexNums){
graph->vertexNums = vertexNums; //设置顶点数
printf("自动设定顶点元素: ");
for(int i = 0 ; i < vertexNums ; i++){
graph->vertex[i] = 'a' + i; //自动设置 固定顶点
printf("%d:%c ",i,graph->vertex[i]);
//手动输入顶点值 测试起来太费劲 并且没有意义 因为边是固定的
// scanf("%c",&graph->vertex[i]);
// getchar(); //用getchar来消耗掉换行符
}
graph->edgeNums = 0; //设置边数 但是边数只能由addEdge函数来自动增加
//边全部初始化为0 互不连通
for(int i = 0 ; i < vertexNums ; i++){
for(int j = 0 ; j < vertexNums ; j++){
graph->edge[i][j] = 0;
}
}
}
int getIndexOfV(Graph *graph , char v){
//获取v在顶点数组中的位置 v的下标
for(int i = 0 ; i < graph->vertexNums ; i++){
if(graph->vertex[i] == v){
return i;
}
}
return -1;
}
void addNoDirEdge(Graph *graph , char c1 , char c2){
int i , j;
i = getIndexOfV(graph,c1);
j = getIndexOfV(graph,c2);
if(i == -1 || j== -1){
printf("未找到指定的顶点 %c or %c !",c1,c2);
return;
}
//无向图
graph->edge[i][j] = 1;
graph->edge[j][i] = 1;
graph->edgeNums += 1;
}
int firstNeighbor(Graph *graph , int v){
//顶点v的第一个邻接点的顶点号(下标)
//遍历v所在的行 第一个值为1的
for(int j = 0 ; j < graph->vertexNums ; j++){ //第一个邻居要从头开始遍历
if(graph->edge[v][j] == 1){
return j;
}
}
return -1;
}
int nextNeighbor(Graph *graph , int v , int t ,int visited[]){
//假设图中顶点t是顶点v的一个邻接点,返回除了t之外顶点v的下一个邻接点的顶点号
// v -> t -> 要求的那个点
//获取到x所在的行
//然后从头遍历 但是如果遇到t所在的列 就跳过,因为要返回除了t的下一个邻居
for(int j = 0 ; j < graph->vertexNums ; j++){
if(graph->edge[v][j] == 1 && j != t && !visited[j]){
return j;
}
}
// printf("顶点: %c(第%d号顶点)没有下一个邻居!",graph->vertex[v],v);
return -1; //用#表示没有邻居
}
void visit(Graph *graph , int v){
//简单打印功能
printf("%c ",graph->vertex[v]);
}
void DFS(Graph *graph , int v , int visited[] ,int len){
visit(graph,v); //对当前顶点号元素进行访问
visited[v] = 1; // 将当前顶点号元素设置为1 已访问
// for(int i = 0 ; i < len ; i++) printf("--vid{%d}:%d-- ",i,visited[i]);
//t -- temp || v -- vertex
//从当前顶点开始
for(int t = firstNeighbor(graph,v) ; t>=0 ; t = nextNeighbor(graph,v,t,visited)){
// printf("-%c-",graph->vertex[t]);
if(!visited[t]){
DFS(graph,t,visited,len); //确实是该递归调用 因为你的v也需要变到下一个元素
}
}
return;
}
void DFSTraverse(Graph *graph){
int len = graph->vertexNums; //获取图中顶点数量 也就是visited数组的长度
int visited[len] = {0};
for(int i = 0 ; i < len ; i++){
visited[i] = 0; //遍历前全为0
}
for(int i = 0 ; i < len ; i++){
if(!visited[i]){ // 没有遍历到的 这是因为可能图是非连通图,那么会从数组中继续遍历
DFS(graph,i,visited,len);
}
}
}
void printGraph(Graph *graph){
int n = graph->vertexNums;
printf("\n即将打印图,图中共有%d个顶点\n----------图----------\n",n);
printf("\t");
for(int j = 0 ; j < n ; j++){
printf("%c\t",graph->vertex[j]);
}
printf("\n\n");
for(int i = 0; i < n ;i++){
printf("%c\t",graph->vertex[i]);
for(int j = 0 ; j < n ; j++){
printf("%d\t",graph->edge[i][j]);
}
printf("\n\n");
}
printf("----------------------\n",n);
}
int main(){
Graph *ndgraph; //no direct 这是一个无向图
ndgraph = (Graph*)malloc(sizeof(Graph)); //务必要分配内存 但不清楚底层原因 待以后再探索吧
initGraph(ndgraph,7); //顶点数组 01234 -> abcde
// for + scanf 可以自己手动连线
addNoDirEdge(ndgraph,'a','c'); //a,c之间添加一条边
addNoDirEdge(ndgraph,'c','d');
addNoDirEdge(ndgraph,'b','d');
addNoDirEdge(ndgraph,'d','e');
addNoDirEdge(ndgraph,'f','g');
/**
a —— c e f —— g
| |
| |
b———— d
*/
printGraph(ndgraph);
printf("DFS: ");
DFSTraverse(ndgraph);
return 0;
}
二、效果图
三、代码实现中思考过的问题
- 其实在代码中 有向图和无向图的区别就在于addEdge()函数中,给某两个顶点号增加边的时候是否是双向的
- 关于设置参数的问题,最开始我是使用的顶点值来进行传递,后来发现这样在进行便利的时候会造成代码冗长,比如在获取第一个邻接点和下一个邻接点的时候的传递问题。
- 关于nextNeighbor()函数,这里有一个坑,写代码的时候较易忽略。
nextNeighbor函数中寻找下一节点应该有三个条件:
(0)首先看一下关于nextNeighbor的解释:
int nextNeighbor(Graph *graph , int v , int t ,int visited[])
假设图中顶点t是顶点v的一个邻接点,返回除了t之外顶点v的下一个邻接点的顶点号
然后从头遍历 但是如果遇到t所在的列 就跳过,因为要返回除了t的下一个邻居
(1)这一顶点在二维数组中应为1,这个自然不必多说,因为需要两个顶点有边才能遍历
(2)应该跳过刚才遍历的点,这也就是函数本身的含义,即(0)的解释
(3)在遍历的时候应该跳过已经遍历过的点,坑就在这里,
如果我们不对这里加以判断,那么当某一结点有两个边的时候,就会陷入死循环:
a-b-c,从a走到b的时候,b根据(2)判断应走向c,
但是c再次走回b的时候,如果没有跳过已经遍历过的边(a,b,c都已遍历),
就会只根据函数跳过c再次走向a。从而陷入死循环
但其实 (!visited[j])这个条件相当于已经包括了(2) 所以(2)显得可有可无