图
A -> B 为一条弧 其中 A为头也被称作终端点,B为弧尾 这是一条有方向的弧 由这样的弧构成的图称为有向图 A B均称为顶点。
如果<A,B> 和 <B,A> 同时存在与图中 则用 (A,B)来代替这一个有序对,表示A,B中的一条边,此时的图称为无向图
1.无向图中边的数目的取值范围是:0 - n*(n - 1)/ 2 如果取最大值 则这个无向图称为无向完全图
2.有向图中边的数目的取值范围是:0 - n*(n - 1) 如果取最大值 则这个有向图称为有向完全图
3.对边数很少的图称为稀疏图
邻接点
在无向图中如果(v,v’)存在于图中 则这两个点互为邻接点
- 边(v,v’)依附于 v 和 v’
- 边(v,v’)和 v 和 v’ 相关联
- 对于有向图来说 则为顶点 v 邻接到 v’ 或者 v’ 邻接自 v
度
顶点的度TD指的是和该顶点有关的相关联边的数目 度分为入度ID和出度OD
TD = ID + OD
权和网
当图的边上与数字值进行了绑定后数字值则为权 这时的图就成了赋权图或者网
连通图
- 连通图:对于图中任意的两个顶点都是联通的 则该无向图为连通图 无向图中的极大连通子图称为无向图的连通分量
- 强连通图:对于有向图来说 对于任何两个顶点v1 和 v2 来说 v1 到 v2 和 v2 到 v1 之间都存在边 则称该有向图为强连通图
有向图的极大强连通子图称为有向图的强连通分量
邻接矩阵表示法
#define MAX_VERTEX_NUM 20 //表示最多的顶点数目
#define INFINTY 32768 //表示无关系
typedef enum{DG,DN,UDG,UDN};//有向图 有向网 无向图 无向网
typedef char VertexData; // char == VertexData
typedef struct ArcNode{
AdiType adj;//无权图中 用1和0来表示是否相邻 对带权图来说则为权值的类型
OtherInfo info;
}ArcNode;
typedef struct{
VertexData vertex[MAX_VERTEX_NUM];//顶点数组
ArcNode arcs[MAX_VERTEX_NUM][MAX_VERTEX_NUM];//存放弧的信息的矩阵 每一个元素都是一个ArcNode结构体
int vexnum,arcnum; //图的顶点数 和 弧的数目
GraphKind kind;//表示图的种类
}AdjMartrix;
//创建有向图 / 网
int LocateVertex(AdjMatrix *G,VertexData v)//寻找v作为顶点 在图中的序号
{
int j = Error,k;//Error 为人为设定的值 常为-1
for(k = 0 ; k < G -> vexnum ; k++)
{
if(G -> vertex[k] == v)
{
j = k;
break;
}
}
return j;
}
int CreateDN(AdjMatrix *G)
{
int i,j,k,weight;
VertexData v1,v2;
scanf("%d %d",&G->arcnum,&G->vexnum);//读入图的弧的数目和顶点的数目
for(i = 0 ; i < G -> vexnum ; i++)
{
for(j = 0 ; j < G->vexnum ; j++)
{
G -> arcs[i][j].adj = INFINITY;
}
}//双重循环 置空整个矩阵
for(i = 0 ; i < G -> vexnum ; i++)
{
scanf("%d",&G -> vertex[i]);
}//输入所有的顶点的值
for(k = 0 ; k < G -> arcnum ; k++)
{
scanf("%c %c %d",&v1,&v2,&weight);//v1为尾 v2为头
i = LocateVex(G,v1);//找到v1 在图中顶点的序号 即该弧在矩阵中的行数
j = LocateVex(G,v2);//找到v2 在图中顶点的序号 即该弧在矩阵中的列数
G->arcs[i][j].adj = weight; //读入权值 或者是 该处是否为弧的标志值
}
return(OK); //OK也是之前设置好的值 常为1
}
//时间复杂度为:n^2 + en
邻接表表示法(链式存储法)(不是很重要的算法)
在存储稀疏表的时候可以大幅度地减少空间的占用
邻接表分为2个表 一个是表头节点表(存放所有的顶点) 一个是边表(存放所有由每个顶点构成的弧) 每个边表跟在节点表的每个节点后面
#defien MAX_VERTEX_NUM 20//最多顶点个数
typedef enum{DG,DN,UDG,UDN} GrahKind;//图种类
typedef struct ArcNode{
int adjvex;//该弧指向顶点的位置
struct ArcNode *nextarc;//指向下一条弧的指针
OtherInfo inf;//其他信息 例如权值之类的
}ArcNode;//边表
typedef struct VertexNode{
VertexData data;//顶点数据
ArcNote *firstarc;//指向第一台弧的指针
}VertexNode;//顶点表
typedef struct{
VertexNode vertex[MAX_VERTEX_NUM];
int vexnum,arcnum;//图的顶点数和弧的数目
GraphKind kind;
}AdjList;
这种存储的方法对计算图的出度和入度十分不友好
十字链表(链式存储的升级版本 可以便于求度的数目)
有向图的每一条弧都是一个节点 每个顶点也是一个节点
#defien MAX_VERTEX_NUM 20//最多顶点个数
typedef enum{DG,DN,UDG,UDN} GrahKind;//图种类
typedef struct ArcNode{
int tailvex,headvex;//表示弧尾顶点在图中的位置 表示弧头节点在图中的位置
struct ArcNode *hlink,*tlink;//指向和该弧弧头相同的下一条弧 指向和该弧弧尾相同的下一条弧
}ArcNode;
typedef struct VertexNode{
VertexData data;
ArcNode *firstin,*firstout;//用于指向以该顶点为弧头的第一个弧节点 用于指向以该顶点为弧尾的第一个弧节点
}VertexNode;
typedef struct {
VertexNode vertex[MAX_VERTEX_NUM];// 顶点表
int vexnum,arcnum;//图的顶点数和弧的数目
GraphKind kind;
}OrthList;
//创建十字链表
void CrtOrthList(OrthList *g)
{
scanf("%d %d",&n,&e);//顶点的个数和弧的条数目
g->vexnum = n;
g->arcnum = e;
for(int i = 0 ; i < n ; i++)
{
scanf("%c",&(g->vertex[i].data));
g->vertex[i].firstin = NULL;
g->vertex[i].firstout = NULL;
}
int vt,vh;
for(int k = 0 ; k < e ; k++)
{
scanf("%c %c",&vt,&vh);//输入弧的尾和头
i = Locate(g,vt);//弧头在图中的位置
j = Locate(g,vh);//弧尾在图中的位置
ArcNode p = (ArcNode *)malloc(sizeof(ArcNode));
p->tlink = i;
p->hlink = j;//在新的边节点中读入这个弧的尾和头
p->tlink = g->vertex[i].firstout;//与该节点的尾顶点相同的下一条弧就是顶点表中这个弧尾作为顶点的firstout表(现在已知的所有以该顶点为弧尾的弧)
g->vertex[i].firstout = p;//尾接法构造这个表 填入现在这个节点
p->hlink = g->vertex[j].firstin;
g->vertex[j].firstin = p;//同理
}
}
图的遍历
深度优先搜索 DFS
过程:
1.先从图中的某个节点v0出发 首先访问v0
2.找出v0的第一个临接节点 访问该顶点 以该节点为新节点为新的顶点重复该步骤 知道刚刚被访问的节点没有邻接点为止
3.返回上一层(上一个被访问的邻接点且任有没有访问的邻接点的顶点,找出下一个未被访问的邻接点,访问,再执行2)
一条路要走到底 再一个一个倒回去走岔路口的另一条路
#define True 1;
#define False 0;
#define Error -1;
#define OK 1;
int visited[MAX_VERTEX_NUM];//标记数组 为了应对非连通图的情况 无法一次搜索就可以访问完所有的节点
void TraverseGraph(Graph g)
//在图中寻找没有被访问的顶点作为起始点 调用BFS进行遍历 Graph为图的存储类型
{
int vi = 0;
for(vi = 0 ; vi < g.vexnum ; vi++)
{
visted[vi] = False;
}
for(vi = 0 ; vi < g.venum ; vi++)
{
if(visited[vi] != 0)
{
DepthFirstSearch(g,vi);
}
}
}
对于不同的存储方式DepthFirstSearch的写法不同
邻接矩阵方式表示
void DepthFirstSearch(AdjMatrix g , int v0)
{
visit(v0);//访问操作
visted[v0] = True;//已经访问过的标志
int vj = 0;
for(vj = 0 ; vj < g.vexnum ; vj++)
{
if(visted[vj] != 0 && g.arcs[v0][vj] == 1)//找现在的节点的第一个邻接点继续访问
{
DepthFirstSearch(g,vj);
}
}
}
采用邻接表表示的方法
void DepthFirstSearch(AdjList g , int v0)
{
visit(v0);//访问操作
visited[v0] = True;//已经访问过的标志
VertexNode p = g.vertex[v0].firstarc;// p 等于该顶点的第一条弧
while( p != NULL)
{
if(visited[p->adjvex] == 0)// 该顶点在图中的位置对应的访问标记数组的值为0(这个顶点没有被访问过)
{
DepthFirstSearch(g,p->adjvex);//去找现节点的邻接点的邻接点
p = p->nextarc;//返回上一层去找上一层顶点的另外的邻接点遍历下去
}
}
}
广度优先搜索BFS
1.从图中的某个顶点v0出发,首先访问v0.
2.依次访问v0的各个未被访问的邻接点
3.分别从这些邻接点出发,依次访问他们的每个没有被访问的邻接点。当vi和vk为当前的邻接点,且vi和vk之前被访问,则vi所有的未被访问的
邻接点应在vk的所有未被访问的邻接点之前访问 (分层的)
void BreadFirstSearch(Graph g,int v0)
{
visit(v0);
visited[v0] = True;
InitQueue(&Q);//初始化辅助队列 (保证层数的划分清晰)
EnterQueue(&Q,v0);//将当前的顶点入队 Q中其实只存了同一层的各个元素 出队完了就下一层
while(!Empty(Q))//Q不为空
{
DeleteQueue(&Q,&v);//队头元素出队
w = FirstAdjVertxt(g,v);//v的第一个邻接点
while(w != -1)
{
if(visited[w] == 0)
{
visited[w] = True;
visit(w);
EnterQueue(&Q,w);
}
w = NextAdjvertex(g,v,w);//求 v 相对于 w 的下一个邻接点
}
}
}