1、图
1)图 Graph
是一种非线性的数据结构
节点之间的关系是任意的,图中任意的两个数据元素之间都有可能相关
图的形式化描述
Graph = ( V, R )
其中 V = { Vi | Vi属于图中的数据元素(i=0,1,2,3,...n-1),顶点的集合,当n=0时,V是空集}
R = { <vi,vj> | vi,vj都属于集合V,且vi和vj之间存在路径的 0<= i,j <= n-1}
是顶点之间的关系的集合
<vi,vj>为顶点vi到顶点vj之间是否存在路径的判定条件
即 如果顶点vi到顶点vj之间是存在路径的,那么<vi,vj>属于集合R
图是一个二元组,是 顶点 以及 顶点之间的路径的 集合
2)分类
无向图
路径 --> 边
如果<vi,vj>存在,那么<vj,vi>就一定存在
有向图
路径 --> 弧
如果<vi,vj>存在,那么<vj,vi>就不一定存在
3)网
如果在图的关系<vi,vj>或者<vj,vi>上 附加一个权值w,称w为弧或者边上的权
带权值的图 称为 网
4)顶点的度
顶点的边或者弧的条数
有向图 分为 :入度 和 出度
5)图的物理存储结构
存储图需要存储哪些东西?
顶点的集合 --> 数据元素的集合
关系的集合 --> 顶点与顶点之间的关系
"数据表示法" 邻接矩阵
"邻接表"
"十字链表"
"邻接多重表"
邻接点:
在无向图中,如果存在一条边(vi,vj),则vi和vj互为邻接点
在有向图中,如果存在一条弧<vi,vj>,则vi是该弧的起点,vj是弧的终点
称 vj是vi的邻接点
() --> 路径
<> --> 关系
2、"数组表示法" 邻接矩阵 Adjacency matrix
G = (V, R)
用两个数组来保存图G
一个 一维数组 来存储顶点的集合
一个 二维数组 来存储图G中 顶点之间的关系
typedef char VType; //顶点的数据类型
typedef int AdjType; //权值的类型
#define MAXN 256
struct Graph
{
VType V[MAXN]; //一维数组 来存储顶点的集合
AdjType Adj[MAXN][MAXN]; //二维数组 来存储图G中 顶点之间的关系
int vex_num; //顶点的个数
int arc_num; //图中 弧的个数
};
3、"邻接表" Adjacency List
数组 + 链表
所谓 邻接表就是将图中的每一个顶点v 和 由v出发的边或者弧 构成的一个单链表
邻接表 是 图的一种链式的存储结构
struct adj //邻接表 节点的类型
{
int termIndex; //边的终点在顶点数组中的下标
int w; //权值
struct adj *next; //指向下一个点
};
struct Vertex //顶点结构体数组的类型
{
VType data;
struct adj *first;
};
struct Vertex graph[MAXN]; //顶点结构体数组
4、代码实现
邻接矩阵 方法实现图
typedef char VType; //顶点的数据类型
typedef int AdjType; //权值的类型
#define MAXN 256
struct Graph
{
VType V[MAXN]; //一维数组 来存储顶点的集合
AdjType Adj[MAXN][MAXN]; //二维数组 来存储图G中 顶点之间的关系
int vex_num; //顶点的个数
int arc_num; //图中 弧的个数
};
Graph.c / Graph.h
练习:
1)创建一个图
//创建一个图
struct Graph * create_graph()
{
//1.创建一个空图,把关系集合初始化
struct Graph *g = malloc(sizeof(struct Graph));
g->vex_num = 0;
g->arc_num = 0;
//把关系的集合否赋值为无穷大/小 INT_MAX / INT_MIN
//或者 自定义一个宏 #define ADJ_MAX 65535
int i,j;
for( i=0; i<MAXN; i++ )
{
for(j=0; j<MAXN; j++ )
{
g->Adj[i][j] = ADJ_MAX;
}
}
//2.根据用户的输入 初始化顶点的集合
printf("please input the Vertex value (eg: abcdefg ) :\n");
char vex[MAXN] = {0};
scanf("%s", vex);
getchar();//吸收掉回车
i=0;
while( vex[i] )
{
g->V[i] = vex[i]; //把顶点数据 保存到图g中顶点集合V
g->vex_num ++ ;
i++;
}
//3.根据用户的输入 初始化邻接矩阵(关系的集合:顶点s到顶点d的权值为w)
printf("please input the Relations value (NO space ! eg: ab15 ac2 ad12 ) :\n");
while(1)
{
VType s,d;
int w;
scanf("%c%c%d", &s, &d, &w ); //注意:输入时一组数据中不用空格隔开
getchar(); //吸收掉回车
if( s == '#' || d == '#' ) //人为约定退出条件
{
break;
}
//找出顶点s,d 在顶点集合V中对应的下标
int si = find_index( g->V, g->vex_num, s );
int di = find_index( g->V, g->vex_num, d );
//保存关系 权值
if( si != -1 && di != -1 )
{
g->Adj[si][di] = w;
g->arc_num ++ ;
}
}
return g;
}
//打印一个图
void print_graph(struct Graph *g)
{
//打印顶点数组
int i,j;
putchar('\t');
for(i=0; i<g->vex_num; i++ )
{
printf("%c\t", g->V[i] );
}
putchar('\n');
//打印关系
for( i=0; i<g->vex_num; i++ )
{
printf("%c:\t", g->V[i] );
for( j=0; j<g->vex_num; j++ )
{
if( g->Adj[i][j] == ADJ_MAX )
{
printf("-\t");
}
else
{
printf("%d\t", g->Adj[i][j] );
}
}
putchar('\n');
}
}
5、图的遍历
图的遍历 是树的遍历的推广,是按照某种规则访问图中的每一个顶点,且只访问一次
也就是 将网状结构按照某种规则线性化的过程
对于图的遍历: "深度优先搜索" 和 "广度优先搜索"
1)深度优先搜索 DFS: Depth First Search ---》 类似于树的先序遍历
设 初始时,图中各个顶点都是未被访问的,
从图中的某个顶点出发(设为v0),先访问v0,
然后再找v0的邻接点vi,如果vi未被访问,那么就按照同样的方式去访问vi,
再去搜索vi的邻接点 ... (深度优先搜索)
如果该顶点所有的邻接点都被访问完毕,则回溯到它的上一个顶点
然后再去以该顶点 以"深度优先搜索"的方式 去搜索其余的邻接点 ...
直到那个访问的顶点 全部都被访问完毕
DFS( g, v ) --> 从图g中以顶点v出发,按照深度优先搜索的算法
去访问v的邻接点,v的邻接点v1,v2,v3,...vn
v未被访问
visit v
if( not visit v1 )
{
visit v1
DFS( g, v1 ); //按照同样的规则DFS去访问v1的邻接点
}
if( not visit v2 )
{
visit v2
DFS( g, v2 ); //按照同样的规则DFS去访问v2的邻接点
}
...
if( not visit vn )
{
visit vn
DFS( g, vn ); //按照同样的规则DFS去访问vn的邻接点
}
代码实现:
// ============= DFS 深度优先搜索 ====================================
int visit[MAXN]; //访问 标志位数组,表示下标对应的顶点是否以及访问
//0表示没有访问,1表示已经被访问了
//在图g中,找到v的第一个邻接点的下标
int FirstAdjVex(struct Graph *g, int v)
{
//遍历,判断与各个顶点是否存在路径
int i;
for( i=0; i<g->vex_num; i++ )
{
if( g->Adj[v][i] != ADJ_MAX ) //关系存在
{
return i;
}
}
return -1;
}
//在图g中,找到v的邻接点w的下一个邻接点的下标
int NextAdjVex(struct Graph *g, int v, int w)
{
int i;
for( i=w+1; i<g->vex_num; i++ )
{
if( g->Adj[v][i] != ADJ_MAX ) //关系存在
{
return i;
}
}
return -1;
}
//深度优先搜索
void DFS(struct Graph *g, int v)
{
//1.先访问v
visit[v] = 1;
printf("%c ", g->V[v] );
//2.找到v的第一个邻接点的下标w,如果x未被访问
//就按照 深度优先搜索的方式去访问w
//for( 取v的第一个邻接点w; w != -1; 再取下一个邻接点 )
int w;
for( w=FirstAdjVex(g, v); w != -1; w=NextAdjVex(g, v, w) )
{
if( visit[w] == 0 ) //未被访问
{
DFS( g, w );
}
}
}
//以深度优先搜索的算法,去遍历图g中的每一个顶点
void DFS_Traverse(struct Graph *g)
{
//先把标志位全部置0
int i;
for( i=0; i<g->vex_num ; i++ )
{
visit[i] = 0;
}
printf("\n------- DFS --------\n");
//深度优先搜索去遍历图中的每一个顶点
for( i=0; i<g->vex_num; i++ )
{
if( visit[i] == 0 )
{
DFS( g, i); //从顶点i出发,按照深度优先搜索的算法,去遍历图g中的每一个顶点
}
}
putchar('\n');
}
2)广度优先搜索 BFS:Breath First Search ---》 类似于树的层次遍历
设 初始时,图中各个顶点都是未被访问的,
从图中的某个顶点v0出发,去访问它,并依次访问完v0的所有的邻接点
然后 再从这些已经访问的邻接点中,仍然按照广度优先搜索 的方式去访问它的邻接点...
直到所有的顶点全部访问完毕
用到 队列的思想
入队元素 先访问再入队
出队元素 把它所有未被访问的邻接点入队
// ============= BFS 广度优先搜索 ====================================
//广度优先搜索
void BFS( struct Graph * g, int v )
{
//初始化一个队列 CircleQueue.c / CircleQueue.h
struct CircleQueue * cq = InitQueue();
//访问v,然后再把v入队(只需要入队顶点的下标)
visit[v] = 1;
printf("%c ", g->V[v] );
EnQueue( cq, v ); //把v入队
while( ! IsEmpty(cq) ) //队列不为空
{
//出队,获取该顶点的下标i,把i所有的未被访问的邻接点入队
int i = 0;
DeQueue( cq, &i );
//for( 取i的第一个邻接点w; w != -1; 再取下一个邻接点 )
int w = 0;
for( w=FirstAdjVex(g, i); w != -1 ; w=NextAdjVex(g,i,w) )
{
if( visit[w] == 0 )
{
//先访问,再入队
visit[w] = 1;
printf("%c ", g->V[w] );
EnQueue(cq, w);
}
}
}
//销毁队列
DestroyQueue(cq);
}
//以广度优先搜索的算法,去遍历图
void BFS_Traverse(struct Graph *g)
{
//先把标志位全部置0
int i;
for( i=0; i<g->vex_num; i++ )
{
visit[i] = 0;
}
//以广度优先搜索的算法,去遍历图
printf("\n------- BFS --------\n");
for( i=0; i<g->vex_num; i++ )
{
if( visit[i] == 0 )
{
BFS( g , i ); //以顶点i出发,按照广度优先搜索的方法去访问
}
}
putchar('\n');
}
6、最短路径问题
解决带权有向图中 两个顶点之间的最短距离的问题
两个经典的算法:
Dijkstra(迪杰斯特拉)算法 和 Floyd(弗洛伊德)算法
都是基于比较 v0vi 与 v0vk+vkvi 的大小的基本算法
Dijkstra(迪杰斯特拉)算法:使用两层循环 计算一个顶点到其他各个顶点的最短距离
Floyd(弗洛伊德)算法 :使用三层循环 计算每一个顶点到其他各个顶点的最短距离
Dijkstra(迪杰斯特拉)算法:
是解决网络中任意的顶点(源点)出发,
求它到其他的各个顶点(终点)的最短距离
基本思想:
如果顶点vi --》顶点vj的最短距离是经过顶点vk的
那么 <vi,vj>路径中,顶点vi到顶点vk的路径 一定是vi到vk的最短路径
需要三个辅助变量:
v:表示以v为下标的顶点(源点)
(1)向量 S[n] 标记最短路径是否已经求出来了 (标志位数组)
S[i] == 1 源点v到顶点i的最短距离已经被求出来了
S[i] == 0 源点v到顶点i的最短距离还没有被求出来
初始时 S[v] = 1, 其他的S[i] = 0
(2)向量 dist[n]
dist[i] --> 存放 源点v 到 顶点i 的最短距离的长度
初始时,dist[i]
<v,vi>上的权为w,如果p(v,vi)存在,也就是v到vi有一条直接路径
如果(v,vi)不存在,dist[i] = 无穷大 ,也就是说 v到vi没有直接路径
(3)向量 path[n]
path[i] --> 存放 源点v 到 顶点i 的最短路径(v -> ... -> vi )
初始时 path[i] = {v}
算法:
1)把源点v 到 其他各个顶点的第一条最短路径长度(直接路径)求出来 //初始化dist[n]
2)dist[m] = MIN( dist[i] | i=0,1,2,... 且S[i] == 0 )
表示在所有未被求出来的最短路径中 找出一条最短的,其长度当作成当前求出的最短路径
- 对于所有的 S[i] == 0 (源点v到顶点i的最短距离还没有被求出来)
if( dist[m] + <m, i> < dist[i] )
{
更新 dist[i] = dist[m] + <m, i> ;
}
重复 2) 和 3) ....
/*
用Dijkstra算法 求从v出发到其他各个顶点的最短距离
*/
//================== Dijkstra算法 ======================
int S[MAXN]; //是否已经求出最短路径,标志位数组
int dist[MAXN]; //存放源点到其他顶点的最短路径长度
int path[MAXN]; //存放源点到终点i的最短路径,时利用哪些最优点更新的
/*
用Dijkstra算法 求从v出发到其他各个顶点的最短距离
*/
void Dijkstra(struct Graph *g, int v)
{
//1.初始化辅助变量
int i;
for( i=0; i<g->vex_num; i++ )
{
S[i] = 0;
dist[i] = g->Adj[v][i];
path[i] = v;
}
S[v] = 1;
dist[v] = 0;
//在所有未被求出来的最短路径中 找出一条最短的,其长度当作成当前求出的最短路径
int min; //保存最小值
int min_i; //最优点的下标
int j; //寻找最短路径的次数,每次找一条
for( j=0; j<g->vex_num; j++ )
{
//2.求出当前的最优点 (在dist[n]找最小值)
min = ADJ_MAX;
for( i=0; i<g->vex_num; i++ )
{
if( S[i] == 0 && dist[i] <= min )
{
min = dist[i];
min_i = i;
}
}
S[min_i] = 1;
//3.根据最优的顶点去比较,更新其他的dist
for( i=0; i<g->vex_num; i++ )
{
if( S[i] == 0 )
{
// dist[m] + <m, i> < dist[i]
if( dist[min_i]+ g->Adj[min_i][i] < dist[i] )
{
//更新
dist[i] = dist[min_i]+ g->Adj[min_i][i];
path[i] = min_i;
}
}
}
}
}
// a --> b : 15
void print_dist(struct Graph *g, int v)
{
printf("\n ------ dist[n] ------ \n");
int i;
for( i=0; i<g->vex_num; i++ )
{
printf("%c --> %c : %d\n", g->V[v], g->V[i], dist[i] );
}
}
// a --> g : a c f d g
void print_path(struct Graph *g, int v)
{
printf("\n ------ path[n] ------ \n");
int i;
for( i=0; i<g->vex_num; i++ )
{
if( dist[i] == ADJ_MAX )
{
printf("%c --> %c : is no path !!!\n",g->V[v], g->V[i]);
continue;
}
printf("%c --> %c : ",g->V[v], g->V[i]);
int j = path[i];
char a[MAXN];
int count = 0;
a[count++] = g->V[i];
while(1)
{
a[count++] = g->V[j];
if( j == v )
{
break;
}
j = path[j];
}
int k;
for( k=count-1; k>=0; k-- )
{
printf("%c ", a[k] );
}
putchar('\n');
}
}