图
图的基本概念
定义:图(graph)是一种非线性结构,形式化的描述: graph = (V,R) 其中,V = {vi | vi 属于某种数据类型 i=0,1,2,3,4,5.....}是图中的元素,vi就是图中的顶点,V就是顶点一个集合!!! 就是图中的顶点,V就是顶点的集合,n代表 图中的顶点个数 如果n = 0 , 就说明是一个空集 其中,R代表V集合中任意两个顶点之间的关系。 p(vi,vj)标识顶点vi到vj的路径 如果两个顶点之间存在路径,我们说关系(vi,vj)是属于R
图的分类
有向图: 两个顶点之间的路径,“弧” 无向图: 如P(vi,vj)存在,则一定P(vj,vi)也存在 两点之间的关系,”边“ 网: 如果给图的关系<vi,vj>加上一个值w(权值),称w为弧或边上的权值。带权的图我们叫网 权值w的具体含义:根据图在不同的领域的应用而定 如:我们建立一个网。网中每一个顶点代表一个城市,权值就是代表每个城市之间的距离!! 顶点度: 顶点的边或者弧的数量 有向图: 入度和出度
连通图/非连通图 在无向图中,若从顶点vi到顶点vj有路径存在,则称vi和vj是连通 若图G中任意两个顶点都是连通的,我们则可以说G是连通图,否则非连通图 路径: 从某个顶点到另外一个顶点是否存在一个路径 直接路径,间接路径
图的存储方式
”数组表示法“:邻接矩阵 ”链接法“:邻接表 ”十字链表“ ”邻接多重表“
邻接矩阵(数组表示法)
G(V,R); // V表示的是顶点的集合,R表示的关系的集合 可以用两个数组来存储一个图!! 一个数组用来存储V 存每一个顶点 一个数组用来存储R 存储两个顶点之间的关系,两个顶点之间的权值
int data 100; //用来存储两个顶点之间的关系 char v[6] = {'A','B','C','D','E','F'}; //图中的顶点集合 <v0,v1> data0 = 6; ...... 第i行,表示从那个顶点出发(边的起始点) 第j列,表示一个路径上的终点
创建一个图:
#define MAX 100 struct graph { char v[MAX]; //表示这个图里面最多可以存储100个顶点 int data[MAX][MAX]; //表示两个顶点之间的权值 int num; //表示图中顶点的个数 };
邻接表
用链式结构去存储一个图!! 所谓的邻接表,就是将图中的每一个顶点V与由V发出的边或者弧构成一个单链表
数据类型:
终点元素结构体 struct Endnode { int stop_index; //终点元素的下标 int w; //代表边上的权值 struct Endnode * next; //指向下一条边 } 顶点元素的结构体 struct vertex { char data ; //顶点元素的数据值 struct * first; //指向终点元素的第一条边 }
图的遍历
广度优先搜索和深度优先搜索 图的遍历就是树的遍历的推广,是按照某种规则(或次序)访问图中各个顶点一次,有且仅有一次的操作,也是将网状解决按照某种规则线性化的过程 对图的遍历通常有两种“深度优先”和”广度优先“,这两者都是人工智能(AI)的基础 int visit[]; //用来表示图中的一个顶点还没有被访问 visit[i] == 0; //表示i对应的顶点还没有被访问 visit[i] == 1; //表示i对应的顶点已经被访问过了
深度优先搜索(DFS:depth first search)
初始时,图中的各个顶点都还没有被访问过,假设以图中V0这个顶点出发,先访问V0,在visit数组中标识V0这个顶点已经被访问过了。接下来要去找V0的邻接点vi(先判断vi有没有被访问过),如果被访问过,访问vi,在visit数组中标识vi这个顶点已经被访问了,再去找vi的邻接点...........如果vi的所有的邻接点都被访问了,返回v0,找到v0下一个邻接点
判断这个点能不能去(如果没有路径就不能去吧) 判断这个点该不该去(如果有路径但是被访问过了,就不能去吧)
算法是如何实现的: int visit[p->num] ={0} ; //表示所有顶点都没有被访问过 1: 表示被访问了 0: 表示没有被访问 DFS(p,vo) //p表示图,v0顶点的下标 (1)、先去访问v0,并标记v0 printf p->v[v0]; visit[v0] = 1;
(2)、找v0的下一个邻接点vi for(vi=0;vi<p->num;vi++) { p->data[v0][vi] 判断这个点能不能去(如果没有路径就不能去吧) 判断这个点该不该去(如果有路径但是被访问过了,就不能去吧) DFS(p,vi); }
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#define MAX 100
#define VER_BIG 999999999
int visit[MAX];//表示顶点是否被访问,
//0:未被访问,1:已被访问
//图类型
typedef struct graph
{
char v[MAX];//表示这个图里面最多可以存放MAX个顶点
int data[MAX][MAX];//表示两个顶点之间的权值
int num;//图里面的顶点个数
}graph;
//找到字符x的下标
int find_i(graph* g,char x)
{
if(g==NULL)
{
return -1;
}
int i;
for(i=0;i<g->num;i++)
{
if(g->v[i]==x)
{
return i;
}
}
return -1;
}
//创建一个邻接矩阵
graph* initgraph(void)
{
int i,j,d,n;
graph* g=malloc(sizeof(graph));
g->num=0;
printf("输入顶点\n");
char ch;
while(1)
{
scanf("%c",&ch);
if(ch=='#')
{
break;
}
g->v[g->num]=ch; //把顶点存储到一维数组里面去
g->num+=1;
}
getchar();
n=g->num;
for(i=0;i<g->num;i++)
{
visit[i]=0;
}
for(i=0;i<n;i++)
{
for(j=0;j<n;j++)
{
g->data[i][j]=VER_BIG;
}
}
char start,stop;
int w;
int start_i,stop_i;
while(1)
{
printf("输入边<a,b>及边权\n");
scanf("%c%c%d",&start,&stop,&w);
if(start=='#'||stop=='#'||w==0)
{
break;
}
getchar();
start_i=find_i(g,start);
stop_i=find_i(g,stop);
if(stop_i==-1||start_i==-1)
{
continue;
}
//无向图
g->data[start_i][stop_i]=w;
g->data[stop_i][start_i]=w;
}
return g;
}
//输出邻接矩阵
void print_graph(graph* g)
{
printf("------邻接矩阵------\n");
int i,j;
printf(" ");
for(i=0;i<g->num;i++)
{
printf("%c ",g->v[i]);
}
printf("\n");
for(i=0;i<g->num;i++)
{
printf("%c ",g->v[i]);
for(j=0;j<g->num;j++)
{
if(g->data[i][j]==VER_BIG)
{
printf("# ");
}
else
{
printf("%d ",g->data[i][j]);
}
}
printf("\n");
}
printf("------------\n");
}
//深度优先搜索
//i:顶点下标
void DFS(graph* g,int i)
{
//访问vi
printf("%c",g->v[i]);
visit[i]=1;
//访问下一个邻接点
int j;
for(j=0;j<g->num;j++)
{
//(1)判断这个点能不能去(是否有路径)
//(2)判断这个点该不该去(是否已访问)
if(g->data[i][j]!=VER_BIG&&visit[j]==0)
{
printf("->");
DFS(g,j);
}
}
}
int main()
{
graph* g=initgraph();
print_graph(g);
DFS(g,0);
}
广度优先搜索(BFS:Breadth first search)
广度优先搜索类似于树的层次遍历,初始时图中的各个顶点都没有被访问 从图中的一个顶点v0出发,先让v0入队 while(队列不为空) { 让队首元素出队 接下来就是找队首元素的所有邻接点 { 判断这个点能不能去(如果没有路径就不能去吧) 判断这个点该不该去(如果有路径但是被访问过了,就不能去吧) 如果这两个条件都满足的话,将这个点进行入队操作 } }
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#define MAX 100
#define VER_BIG 999999999
int visit[MAX];//表示顶点是否被访问,
//0:未被访问,1:已被访问
int q[MAX];//队列,先进先出
//图类型
typedef struct graph
{
char v[MAX];//表示这个图里面最多可以存放MAX个顶点
int data[MAX][MAX];//表示两个顶点之间的权值
int num;//图里面的顶点个数
}graph;
//找到字符x的下标
int find_i(graph* g,char x)
{
if(g==NULL)
{
return -1;
}
int i;
for(i=0;i<g->num;i++)
{
if(g->v[i]==x)
{
return i;
}
}
return -1;
}
//创建一个邻接矩阵
graph* initgraph(void)
{
int i,j;
graph* g=malloc(sizeof(graph));
g->num=0;
printf("输入顶点\n");
char ch;
while(1)
{
scanf("%c",&ch);
if(ch=='#')
{
break;
}
g->v[g->num]=ch; //把顶点存储到一维数组里面去
g->num+=1;
}
getchar();
for(i=0;i<g->num;i++)
{
visit[i]=0;
}
for(i=0;i<n;i++)
{
for(j=0;j<n;j++)
{
g->data[i][j]=VER_BIG;
}
}
char start,stop;
int w;
int start_i,stop_i;
while(1)
{
printf("输入边<a,b>及边权\n");
scanf("%c%c%d",&start,&stop,&w);
if(start=='#'||stop=='#'||w==0)
{
break;
}
getchar();
start_i=find_i(g,start);
stop_i=find_i(g,stop);
if(stop_i==-1||start_i==-1)
{
continue;
}
g->data[start_i][stop_i]=w;
g->data[stop_i][start_i]=w;
}
return g;
}
//输出邻接矩阵
void print_graph(graph* g)
{
printf("------邻接矩阵------\n");
int i,j;
printf(" ");
for(i=0;i<g->num;i++)
{
printf("%c ",g->v[i]);
}
printf("\n");
for(i=0;i<g->num;i++)
{
printf("%c ",g->v[i]);
for(j=0;j<g->num;j++)
{
if(g->data[i][j]==VER_BIG)
{
printf("# ");
}
else
{
printf("%d ",g->data[i][j]);
}
}
printf("\n");
}
printf("------------\n");
}
//广度优先搜索
//i:顶点下标
void BFS(graph* g,int i)
{
//q[MAX] //队列,先进先出
int head;//队首元素
//访问vi
visit[i]=1;
int front=0,rear=0,sum=0;//队头,队尾以及队列元素个数
q[front]=i;//入队
sum+=1;
int ans;
while(sum>0)
{
head=q[front];//访问队头
//访问下一个邻接点
int j;
for(j=0;j<g->num;j++)
{
//(1)判断这个点能不能去(是否有路径)
//(2)判断这个点该不该去(是否已访问)
if(g->data[head][j]!=VER_BIG&&visit[j]==0)
{
q[++rear]=j;//入队
sum++;//队列元素+1
visit[j]=1;//标记已被访问
}
}
ans=q[front++];//出队
sum-=1;//队列元素-1
if(sum==0)//队列的最后一个元素
{
printf("%c",g->v[ans]);
}
else
{
printf("%c->",g->v[ans]);
}
}
}
int main()
{
graph* g=initgraph(); //创建邻接矩阵
print_graph(g);//输出邻接矩阵
BFS(g,0);//广度优先搜索,从下标0开始
}
最短路径问题
#include<stdio.h> #include<stdlib.h> #include<string.h> #define MAX 100 #define VER_BIG 9999999 //图类型 typedef struct graph { char v[MAX];//表示这个图里面最多可以存放MAX个顶点 int data[MAX][MAX];//表示两个顶点之间的权值 int num;//图里面的顶点个数 }graph; //找到字符x的下标 int find_i(graph* g,char x) { if(g==NULL) { return -1; } int i; for(i=0;i<g->num;i++) { if(g->v[i]==x) { return i; } } return -1; } //创建一个邻接矩阵 graph* initgraph(void) { int i,j; graph* g=malloc(sizeof(graph)); g->num=0; printf("输入顶点\n"); char ch; while(1) { scanf("%c",&ch); if(ch=='#') { break; } g->v[g->num]=ch; //把顶点存储到一维数组里面去 g->num+=1; } getchar(); for(i=0;i<g->num;i++) { for(j=0;j<g->num;j++) { g->data[i][j]=VER_BIG; } } char start,stop; int w; int start_i,stop_i; while(1) { printf("输入边<a,b>及边权\n"); scanf("%c%c%d",&start,&stop,&w); if(start=='#'||stop=='#'||w==0) { break; } getchar(); start_i=find_i(g,start);//获取顶点下标,弧的头顶点 stop_i=find_i(g,stop);//获取顶点下标,弧的尾顶点 if(stop_i==-1||start_i==-1)//没有找到顶点位置 { continue; } g->data[start_i][stop_i]=w;//存放有向图邻接关系 //g->data[stop_i][start_i]=w; } return g; } //输出邻接矩阵 void print_graph(graph* g) { printf("------邻接矩阵------\n"); int i,j; printf(" "); for(i=0;i<g->num;i++) { printf("%c ",g->v[i]); } printf("\n"); for(i=0;i<g->num;i++) { printf("%c ",g->v[i]); for(j=0;j<g->num;j++) { if(g->data[i][j]==VER_BIG) { printf("# "); } else { printf("%d ",g->data[i][j]); } } printf("\n"); } printf("------------\n"); } //Dijkstra 有三个辅助向量 int S[MAX]; /* s[i]=0 源点V 到 Vi的最短路径没有求出 s[i]=1 源点V 到 Vi的最短路径已经求出 */ int Dist[MAX]; //保存的是v到vi的最短路径的长度 char Path[MAX][MAX]; //Path[i] 保存是从源点V0 到 Vi 的最短路径上所经历的“顶点” //Path[i][0] = 'A' //Path[i][1] = 'B' //Path[i][2] = 'C' //....... //Path[i][n] = 'vi' //Path[i][n+1] = '\0' //求最短路径,Dijsktra算法 void Dijkstra(graph* g,int v0) { int i,j; int n=1;//已经找到的边数 //1.初始化,三个辅助数组 for (i=0;i<g->num;i++) { S[i]=(i==v0?1:0); Dist[i]=g->data[v0][i];//从源点v0到vi的距离(初始时不借助其他点) Path[i][0]=g->v[v0];//从源点v0出发 if(Dist[i]!=VER_BIG)//如果源点v0可以直接到达点vi { Path[i][1]=g->v[i]; Path[i][2]='\0';//字符串末尾加一个'\0'标识结束 } else//如果源点v0不可以直接到达点vi { Path[i][1]='\0'; } } //有g->num的点,找g->num-1条边 while(n++<g->num) { //2.找到S[i]==0的Dist[i]的最小值 int Dist_min=VER_BIG;//Dist[i]最小值 int w;//记录Dist最小值的下标 for(i=0;i<g->num;i++) { if(S[i]==0)//最短路径未被求出 { if(Dist[i]<Dist_min) { Dist_min=Dist[i]; w=i;//当前最短路径的记录下标 } } } S[w]=1;//w最短路径已被找到 //3.更新,如果Dist[w]+<w,u><Dist[u]; //即通过w点可以有一条更短的路径到达目标点vu; for(int u=0;u<g->num;u++) { if(S[u]==0)//点u未找到最短路径 { //检查源点到点u的当前路径长度 //与源点到当前最短路径的点的长度+该点到点u的路径长度的大小关系 if(Dist[w]+g->data[w][u]<Dist[u]) { Dist[u]=Dist[w]+g->data[w][u]; //path[u]---->path[w]+u strcpy(Path[u],Path[w]); int l=strlen(Path[u]); Path[u][l]=g->v[u]; Path[u][l+1]='\0'; } } } } //输出最短路径 for(i=0;i<g->num;i++) { printf("%c -> %c=%d :",g->v[v0],g->v[i],Dist[i]); for(j=0;Path[i][j]!='\0';j++) { printf("%c ",Path[i][j]); } printf("\n"); } } int main() { graph* g=initgraph(); //创建邻接矩阵 print_graph(g);//输出邻接矩阵 Dijkstra(g,0);//输出最短路径 }