图的存储结构
邻接矩阵(数组表示法)
用一个一维数组存储途中定点的信息,用一个二维数组(邻接矩阵)存储图中各顶点之间的邻接关系。
假设图G=(V,E)有n个顶点,则邻接矩阵是一个n×n的方阵,定义为:
类型定义:
enum GraphType{DG, UG, DN, UN};
typedef char VertexType;
typedef struct
{
VertexType vexs[MAX];//顶点表
int arcs[MAX][MAX];//邻接矩阵
int vexnum, arcnum;//顶点数和边数
GraphType kind;//图的类型
}MGraph;
无向图的邻接矩阵
无向图
无向图邻接矩阵的特点:主对角线为0,且一定是对称矩阵。
如何求顶点i的度?:邻接矩阵第i行/第i列的非零元素个数。
如何判断顶点i和j之间是否存在边?:测试邻接矩阵中的arcs[ i ][ j ] 是否为1.
如何求顶点i的所有邻接点?:将数组中第i行元素扫描一遍,若arcs[i][j]为1,则顶点j为顶点i的邻接点。
有向图
有向图的邻接矩阵一定不对称吗?不一定,例如有向完全图。
如何求顶点i的出度?:邻接矩阵的第i行元素之和
如何求顶点i的入度?:邻接矩阵的第i列元素之和
如何判断从顶点i到顶点j是否存在边?:测试邻接矩阵中的arcs[ i ][ j ] 是否为1.
网图的邻接矩阵可定义为:
邻接表
所有边表的头指针和存储顶点信息的一维数组构成了顶点表。
将所有邻接于vi的顶点链成一个单链表,称为顶点vi的边表
typedef struct
{
VertexType data;
EdgeList firstedge;
}VexNode;
typedef struct
{
int adjvex;//邻接点域,边的重点在下一个顶点表的下标
Node *next;
}Node, *EdgeList;
网图的邻接表
图的遍历
图的遍历是在从图中某一顶点出发,对图中所有顶点访问一次且仅访问一次。
图的遍历要解决的关键问题
- 因图中可能存在回路,某些顶点可能会被重复访问,如何避免遍历因回路而陷入死循环?:附设访问标志数组visited[n]
- 在图中,一个顶点可以和其他多个顶点相连,当这样的顶点访问过后,如何选取下一个要访问的顶点?:深度优先遍历和广度优先遍历
深度优先遍历 DFS(MGraph G, int v)
基本思想:
- 访问顶点v
- 从v的未被访问的邻接点中选取一个顶点i,从i出发进行深度优先遍历
- 重复上述两步,直至图中所有和v有路径相通的顶点都被访问到。
//连通图的深度优先遍历递归算法
bool visited[MAX] = {false};
void DFS(MGraph G, int v)
{
cout<<G.vexs[v];
visited[v] = true;
for(int i = 0; i < G.vexnum; i++)
{
if(G.arcs[v][i]!=0 && !visited[i])
DFS(G, i);
}
}
广度优先遍历
基本思想:
- 访问顶点v
- 依次访问v的各个未被访问的邻接点
- 分别从这些邻接点出发,依次访问它们未被访问的邻接点,并使“先被访问顶点的邻接点”先于“后被访问顶点的邻接点”被访问。直至图中所有与顶点v有路径相通的顶点都被访问到。
首先要理解搜索步,一个完整的搜索步包括两个处理
a) 获得当前位置上,有几条路可供选择
b) 根据选择策略,选择其中一条路,并走到下个位置
相当于在漆黑的夜里,你只能看清你站的位置和你前面的路,但你不知道每条路能够通向哪里。搜索的任务就是,给出初始位置和目标位置,要求找到一条到达目标的路径。
深度优先就是,从初始点出发,不断向前走,如果碰到死路了,就往回走一步,尝试另一条路,直到发现了目标位置。这种不撞南墙不回头的方法,即使成功也不一定找到一条好路,但好处是需要记住的位置比较少。广度优先就是,从初始点出发,把所有可能的路径都走一遍,如果里面没有目标位置,则尝试把所有两步能够到的位置都走一遍,看有没有目标位置;如果还不行,则尝试所有三步可以到的位置。这种方法,一定可以找到一条最短路径,但需要记忆的内容实在很多,要量力而行。
宽泛意义上:深度优先一般是解决连通性问题,而广度优先一般是解决最短路径问题。
最小生成树
生成树的代价:设G=(V,E)是一个无向连通图,生成树上各边的权值之和成为该生成树的代价。
最小生成树:在图G所有生成树中,代价最小的生成树称为最小生成树。
Prim算法的基本思想:
- 初始时,从V中任取一个顶点(如v1),将其放入U中,使得T的初始状态为U={v1}, TE = {},初始状态的最小生成树可以看成是一颗只含有一个顶点的树。
- 重复执行:
选择一条权值最小的交叉边(vi, vj),其中,vi∈U,vj∈V-U,并把该边(vi, vj)和其不再最小生成树尚的顶点vj分别加入T的顶点集U和边集TE中,直到图G中的n个顶点均被加入到最小生成树T中为止。
Prim算法的关键:
- 如何找到连接U和V-U的最短边
可用下述方法构造候选最短边集:对应V-U中的每个顶点,保留从该顶点到U中各顶点的最短边。
引入一个辅助数组miniedges[],用于存放集合V-U中的各顶点到集合U中顶点的最小交叉边及其权值:
typedef struct
{
VertexType vex;
float lowcost;
}Edge;
- 初始化辅助数组miniedges[]
- 输入顶点v,将顶点v加入集合U中
- 重复执行下列操作n-1次
- 选取出全职最小的候选交叉边,取其对应的定点序号k,
- 将顶点k加入到集合U中
- 调整数组miniedges[]