图的内容太多了,分为两部分写
定义:图G由顶点集V和关系VR组成,记为G=(V, VR)。
V是顶点(元素)的有穷非空集。
若图G任意两顶点a,b之间的关系为有序对,即∈VR,则称为从a到b的一条弧 / 有向边。其中a是的弧尾,b是的弧头;由顶点集和弧的集合组成的图成为有向图。
举个栗子
由顶点集合与边的集合组成的图称为无向图。
举个栗子
(a,b)表示a,b互为邻接点,(a,b)依附于a和b, (a,b)与a和b相互关联。
n代表顶点的数目,e代表边或弧的数目。
e的取值范围为0到n(n-1)/2。
有n个顶点和n(n-1)/2条边的无向图称为完全图。
边(弧)上加权的图。
带权值的无向图称为无向网,带权值的有向图称为有向网。
对图G=(V,VR)和G’=(V’,VR’),若V’ ⊆ V且VR’ ⊆ VR,则称G’是G的一个子图。
如图,G1,G2,G3为G的子图,G4不是。
与顶点V相关联的边(x,y)的数目,称为V的度。
记作TD(V)或D(V)。
以顶点V为弧尾的弧的数目称为V的出度,记作OV(V).
O指out
以顶点V为弧头的弧的数目,称为V的入度,记作ID(V).
I指in
举个栗子
顶点Vi到Vj有路径是指存在一个顶点序列 Vi,Vi1,Vi2…Vim,Vj.
其中(Vi,Vi1)、(Vi1,Vi2)…(Vim, Vj)是图的边或弧。
若以顶点Vi到Vj有路径则称Vi和Vj是连通的。
若图G任意两点是连通的,则称G是连通图。
若图G’是G的一个极大连通子图,则称G’是G的一个连通分量。
连通图的分量就是自身,非连通图有多个连通分量。
G1,G2,G3都是G的连通分量。
若在图G中每对顶点Vi,Vj之间,从Vi到Vj且从Vj到Vi都存在路径,则称G是强连通图。
若图G’是G的一个极大连通子图,则称G’是G的强连通分量。强连通图的强连通分量是自身。
G1,G2是G的强连通分量。
设G=(V,E), G’=(V’,E’), 且V=V’,若G是连通图,G’是G的一个极小连通子图,则G’是G的一个生成数。
CreateCraph(&G,V,VR) | 生成图 |
DestroyCraph(&G) | 销毁图 |
Locate(G,u) | 查找顶点u的位置 |
GetVex(G,v) | 读取顶点V的信息 |
PutVex(&G,v,value) | 给顶点V赋值value |
FirstAdjVex(G,v) | 读v的第一个邻接点 |
NextAdjVex(G,v,w) | 读v(相对于w)的下一个邻接点 |
InsertVex(&G,v) | 插入顶点 |
DeletsVex(&G,v) | 删除顶点 |
DFSTraverse(G,vists()) | 深度优先遍历 |
BFSTraverse(G,visit()) | 广度优先遍历 |
数组表示法 | 用两个数组分别存储数据元素(顶点)的信息和数据元素之间的关系 |
顶点数组 | 用一维数组存储顶点的(元素) |
邻接矩阵 | 用二维数组存储顶点(元素)之间的关系(边或弧),0表示无关,1表示有关 |
举个栗子
对与有向网和无向网,需要在邻接矩阵中加上关系的权值,如果两个顶点间有邻接关系,就用权值代替原来的1,否则用∞代替0。
举个栗子
顶点数组vexs |
邻接矩阵arcs |
顶点数 |
图的类型 |
弧(边)数 |
#define MAX_VERTEX_NUM 20
typedef enum{DT, DN, UDG, UDN
}GraphKind;
typedef struct{
VertexTpye vexs[MAX_VERTEX_NUM];
VRType arcs[MAX_VERTEX_NUM][MAX_VERTEX_NUM];
int vexnum,arcnum;
GraphKind kind;
}Mgraph;
这是一种顺序 + 链式的物理存储结构,通过头结点数组保存所有的信息,用单链表保存顶点之间的关系。
为图G的每个顶点建立一个单链表,第i个单链表中的结点表示依附于顶点的Vi的边。
为图G的每个顶点建立一个单链表,第i个单链表中的结点表示依附于顶点的Vi的边。 |
若无向图G有n个顶点和e条边,需要n个表头结点和2e个表结点 |
无向图G的邻接表,顶点Vi的度为第i个单链表的长度 |
第i个单链表中的表结点的值为j,表示以顶点Vi为尾的一条弧(Vi, Vj) |
若有向图G有n个顶点和条弧,则需n个表头结点和e个表结点 |
有向图G的邻接表顶点Vi 的出度为第i个单链表的长度 |
求顶点Vi的入度徐遍历全部单链表,统计结点值为i的结点数 |
第j个单链表中的表结点的值为i,表示以顶点Vi为弧尾的一条弧(Vi, Vj) |
若有向图有n个顶点e条弧,则需n个表头结点和e个表结点 |
有向图G的逆邻接表,顶点Vi的入度为第i个单链表的长度 |
求顶点Vi的出度需遍历全部单链表,统计结点值为i的结点数 |
表结点表示边或弧,所以就对表结点扩充一个属性域,表结点至少包含顶点序号、权值和下一个表结点指针3个属性。
由此终于可以得出邻接表是如何定义的了
#define MAX_VERTEX_NUM 20
typedef struct ArcNode{//表结点类型定义,对网需要加权值属性
int adjvex;//顶点位置编号
struct ArcNode *nextarc;//下一结点指针
InfoType *info;
}ArcNode;
typedef struct VNode{//头结点及其数组类型定义
VertexType data;//顶点信息
ArcNode *firstarc;//指向第一条弧
}VNode,AdjList[MAX_VERTEX_NUM];
typedef struct {//邻接表的类型定义
AdjList vertice;//头结点数组
int vexnum,arcnum;//顶点数,弧数
GraphKind kind;//图的类型
}ALGraph;
tailvex:弧尾的位置
headvex:弧头的位置
hlink:指向下一条弧头相同的弧
tlink:指向下一条弧尾相同的弧
2)每个顶点有一个顶点结点
data:顶点信息
firstin:指向以该顶点为弧头的第一条弧
firstout:指向以该顶点为弧尾的第一条弧
以邻接图为基础,扩展结点的属性成起止结点序号
再添加逆邻接表的信息
这个图的画法还是有技巧的,眼尖的同学可能注意到了,我在每个弧结点的下方有有标记,邻接表的指向和标记的顺序是相同的,AB就是AB,而逆邻接表刚好和标记相反,AB则为BA。
#define MAX_VERTEXT_NUM 20
typedef struct ArcBox{
int tailvex,headvex; //该弧的尾和头顶点位置
struct ArcBox *hlink,*tlink;//分别为弧头相同和弧尾相同的弧的链域
}ArcBox;
typedef struct VexNode{
VertexType data;
ArcBox *firstin,*firstout;//分别指向该顶点的第一条入弧和出弧
}VexNode;
typedef struct{
VexNode xlink[MAX_VERTEXT_NUM];//表头变量
int vexnum,arcnum;//有向图的当前顶点数和弧数
}OLGraph;
1)每个顶点有一个头结点
data | firstedge |
---|
data:顶点信息
firstedge:指向第一条依附于该顶点的边
mark:标志域,可以标记该边是否被搜索过
ivex,jvex:值该边依附的两个顶点在顶点数组的位置
ilink:指向下一条依附于顶点vi的边
jlink:指向下一条依附于顶点vj的边
//无向图邻接多重表
#define MAX_VERTEXT_NUM 20
typedef enum{ unvisited,visited}visitedIf;
typedef struct EBox{
visitedIf mark;//访问标记
int ivex,jvex;//该边依附的两个顶点的位置
struct EBox *ilink,*jlink;//分别指向依附与这两个顶点的下一条边
}EBox;
typedef struct VexBox{
VertexType data;
EBox *firstedge;//指向第一条依附于该顶点的边
}VexBox;
typedef struct{
VexBox adjmulist[MAX_VERTEXT_NUM];
int vexnum,edgenum;//无向图的当前顶点数和边数
}AMLGraph;
从V1出发,访问一个未访问过的V1的邻接点V2,以V2作为新的出发点,继续深度优先搜索,直到把与V1相连通的点都被访问,如果还有未访问的点,需要在其中选择一个作为新的起点,继续进行深度优先搜索,直到所有顶点都被访问到。
//深度优先遍历
boolean visited [MAX]
void DFDTraverse(Graph G, Status (*visit)())
{
for(v=0;v<G.vesnum;v++)//初始化各顶点未访问状态
visited[v]=false;
for(v=0;v<G.vexnum;v++)
if(!visit[v])//从一个未访问的顶点开始
DFS(G,v,visit);
}
void DFS(Graph G,int v,Status (*visit)())
{
visited[v]=true;visited(v);//标记顶点,并且访问顶点
for(w=FirstAdjVex(G,v),w>0;w=NextAdjVex(G,v,w))//查找与顶点v相邻并且没有访问过的顶点
if(!visited[w])//处理所有未访问的邻接顶点
DFS(G,w,visited); //递归,继续深度优先遍历
}
假定从V1出发,先访问V1,再访问所有未被访问过的V1的邻结点,访问次序满足:先被访问过的顶点的邻接点的访问先于后被访问过的顶点的邻结点的访问。
直到与V1相连通的点都被访问。如果还有未被访问过的顶点,选择其中一个作为出发点,继续广度优先搜索,直到所有的顶点被访问。
举个栗子
//广度优先遍历
void BFSTraverse(Graph G,Status (*visit)())
{
for(v=0;v<G.vexnum;v++)
visited[v]=false; //初始化visited数组
InitQueue(Q);//初始化队列
for(v=0;v<G.vexnum;v++) //按顶点位置序号依次选择顶点
if(!visited[v]){//遇到未访问过的顶点开始遍历
visited[v]=true;visit(v);EnQueue(Q,v);//标记顶点,并访问顶点,把顶点加入到队列 Q中
while(!QueueEmpty(Q)){//如果队列非空,进行出队操作
DeQueue(Q,u);//出队顶点为u
for(w=FirstAdjVex(G,u),w>=0;w=NextAdjVex(G,u,w))//对u的相邻顶点进行分析
if(!visited[w]){//如果某个相邻顶点w没有被访问过
visited[w]=true;//标记顶点
visited(w);//开始访问
EnQueue(Q,w);//把w入队
}
}
}
}
遍历几次就有几个连通分量
DFS生成树
对一个无向连通图,进行DFS遍历时,遍历过程中经过的点和边组成的图,称为DFS生成树。
BFS生成树
DFS生成森林
对一个非连通图,由于需要多次遍历才能访问到所有顶点,每次DFS遍历都得到一课树所以得到一个深度优先生成森林。
BFS生成森林
对一个非连通图,由于需要多次遍历才能访问到所有顶点,每次BFS遍历都得到一课树所以得到一个广度优先生成森林。
在有向图G中,从某个顶点V出发,顺着弧的方向进行DFS遍历,得到顶点集合V1,再从顶点V出发,逆着弧的方向进行DFS遍历,得到顶点集合V2。就可以得到一个强连通分量Gs=(Vs,VRs),其中:
Vs=V1∩V2;VRs为Vs中所有顶点在G中的弧
将得到的强连通分量从图中去掉。剩下的在重复上述操作,则可以求出全部强连通分量。
先来举个栗子:
假如现在又n个城市,要建立一个通信网,要连通n个城市,只需要n-1条线路,每条线路直接连通两个城市,城市与城市之间可以进行中继。如何建立这个通信网,使得经费最省。也就是说,如何建立n-1条线路,使得总的费用最低,并且任意两个城市之间可以互相通信。
我们可以把城市看成顶点,线路看成带权值的边,每一种方法可以看成一颗生成树,问题就成了如何求解一颗生成树使得费用最低,也就是各边权值之和具有最小值。
在网G的生成树中,其中各边的权之和最小的生成树,称为G的最小代价生成树,也就是最小生成树。
设G={V,E}是一个连通网,U是V的一个非空子集。如果边(u,v)是G中所有一端在U中(即u∈U),而另一端在V-U中(即v∈V-U),具有最小权值的一条边,则由一颗包含边(u,v)的最小生成树。
对n个顶点的连通网,初始时,T=(U,T,E),U中只有一个开始顶点U0,TE=ψ,以后根据MST性质,每增加一个顶点和一条边,重复n次。U不断增大,V-U不断减小直到为空。
开始可能存在多条具有最小权值的边,选择次序不同,故最小生产树不一定唯一。
怎么用计算思维表达这一过程,接着就是借助邻接矩阵和closedge这样一个结构体数组。
图终于画完了,接着给出伪代码
//图的最小生成树,prime算法
struct{
VertexType adjvex; //最小权值的边,在U中依附的顶点
VRType lowcost; //记录最小权值lowcost
}closedge[MAX_VERTEXT_NUM];
void Prime(MGraph G, VertexType u)
{
k=LocateVex(G,u); //确定起始顶点u的位置序号
for(j=0;j<G.vexnum;j++)//初始化最短边权值和依附顶点值
if(j!=k)
closedge[j]={u,G.arcs[k][j].adj}
closedge[k].lowcost=0;//选定起点u到U中
for(i=1;i<G.vexnum;i++){//依次加入n-1个顶点、n-1条边
k=minimum(closedge);//选择下一条最短边对应的顶点序号
printf(closedge[k].adjvex,G.vexs[k]);//输出生成树的边
closedge[k].lowcost=0; //顶点k加入到U中
for(j=0;j<G.vexnum;j++) //替换最小权值的依附顶点
if(G.arcs[k][j].adj<closedge[j].lowcost)
closedge[j]={G.vexs[k],G.arcs[k][j].adj}
}
}
//算法适合边稠密的无向连通图 ,T(n,e)=O(n*n)
学习进度到了图这一课,本来我是打算把数据结构从头到尾整理一遍,但刚好学到了图,就打算先把图的笔记先写出来。
从单链表到树,搞懂之后觉得数据结构学起来很爽,直到我遇见了图,图这玩意太难了,这篇笔记写着写着让我想要放弃,但写了这么多,不写了实在可惜。
一开始只是感jio有知识盲区
接着越来越难懂了
莫得办法,只能自己反复看,看懂了然后从草稿箱拿出这篇笔记接着写
看一下字数:
写还不是最难的,举例子是要画图的,做图又不会做,盗图又不会盗,就只能靠一笔一画去画这种东西,才能勉强举的了栗子这样子。
这么多东西自己都写不下去了,让大家怎么看,于是我先写了前4节,后面再写有向无环图及其应用和最短路径。
已经尽力去学了,如果有些错误,还请包涵并指出。