每条边都没有方向,用(Vi,Vj)表示某条边
每条边都有方向,用
与顶点连接的边数
入度:流入该顶点的边数
出度:流出该顶点的边数
不存在重复的边的图
图中任意两顶点之间存在边,边的条数为n(n-1)/2,n为顶点数
图中任意两顶点之间存在方向相反的两条边,边的条数为n(n-1),n为顶点数
每条边都带有权值的图
图中任意两个顶点和连接这两个顶点的边的组合体
路径是一个顶点到另一个顶点序列,路径长度是路径上边的条数
从顶点x(x为图中任一顶点)经过某条路径回到顶点x
序列中顶点不重复出现的路径
序列中顶点不重复出现的环
无向图中,Vi到Vj有路径,则这两个顶点连通;若任意一对顶点连通,称为连通图
无向图中的极大连通子图
有向图中,Vi到Vj有路径,则这两个顶点连通;若任意的Vi到Vj有路径,称为强连通图
有向图中的极大连通子图
如由5个数字顶点所组成的有向不带权图,我们想要存储这个有向图的顶点信息和顶点之间关系的信息.
首先顶点之间关系有:
0~(0,1,2,3,4)
1~(0,1,2,3,4)
2~(0,1,2,3,4)
3~(0,1,2,3,4)
4~(0,1,2,3,4)
也即顶点关系为25种
如果用一个二维数组表示就是这样:
0 1 2 3 4
0 x x x x x
1 x x x x x
2 x x x x x
3 x x x x x
4 x x x x x
x=1表示<Vi,Vj>存在;x=0表示<Vi,Vj>不存在
将上述图改成有向带权图,如果用一个二维数组表示就是这样:
0 1 2 3 4
0 x x x x x
1 x x x x x
2 x x x x x
3 x x x x x
4 x x x x x
x=X(X∈N*)表示<Vi,Vj>边的权值数;x=∞表示<Vi,Vj>不存在
邻接矩阵的结构体设计:
typedef struct
{
int no;//顶点的下标
}VertexType;
图的结构体设计:
typedef struct
{
//二维数组存放(不带权,有边:1,无边:2;带权,存放边的权值)
float edges[maxSize][maxSize];
int n,e;//存放顶点数和边数
VertexType vex[maxSize];//存放顶点信息(这里只存放了下标)
}MGraph;
边结点的结构体设计:
typedef struct ArcNode
{
int adjV;//邻接顶点
struct ArcNode* next;//指向下一个边结点的指针
}ArcNode;
图顶点的结构体设计:
typedef struct
{
int data;//顶点信息
ArcNode* first;//指向头一个边结点的指针
}VNode;
图的结构体设计
typedef struct
{
VNode adjList[maxSize];//存放结点信息
int n,e;//顶点和边的个数
}AGraph;
设起点流出的边为出边,流入起点的边为入边
为了使邻接表能够同时满足邻接和逆邻接的需求,设计十字链表
边结点的结构体设计:
typedef struct ArcNode
{
int start;//起点
int end;//终点
struct ArcNode* nextIn;//入边到下一个边结点的指针
struct ArcNode* nextOut;//出边到下一个边结点的指针
}ArcNode;
图顶点的结构体设计:
typedef struct
{
int data;//顶点信息
ArcNode* firstIn;//入边到头一个边结点的指针
ArcNode* firstOut;//出边到头一个边结点的指针
}VNode;
图的结构体设计
typedef struct
{
VNode adjList[maxSize];//存放结点信息
int n,e;//顶点和边的个数
}AGraph;
void DFS(int v,AGraph *G)
{
visit[v]=1;
Visit(v);
ArcNode *q=G->adjList[v].first;
while(q!=NULL)
{
if(visit[q->adjV]==0)
DFS(q->adjV,G);
q=q->next;
}
}
void BFS(AGraph *G,int v,int visit[maxSize])
{
ArcNode *p;
int que[maxSize],front=0,rear=0;
int j;
Visit(v);
visit(v)=1;
rear=(rear+1)%maxSize;
que[rear]=v;
while(front!=rear)
{
front=(front+1)%maxSize;
j=que[front];
p=G->adjList[j].first;
while(p!=NULL)
{
if(visit(p->adjV)==0)
{
Visit(p->adjV);
visit(p->adjV)=1;
rear=(rear+1)%maxSize;
que[rear]=p->adjV;
}
p=p->next;
}
}
}
贪心算法的特点:
每一次选取局部最优,不去考虑全局最优
Prim算法的特点:
1.只能用图里的边
2.n个顶点正好用掉n-1条边
3.不能有回路
构造最小生成树的步骤:
1.从顶点A开始,选择与其相连的权值最小的边,连接顶点B,得到生成树AB
2.选择与生成树AB相连的权值最小的边,连接顶点C,得到生成树ABC
3.选择与生成树ABC相连的权值最小的边,连接顶点D,得到生成树ABCD
...
n.连接完所有的顶点,构造出一棵代价最小的生成树
求最小生成树所需:
1.一张具有n个顶点的带权图
2.三个数组:
-MGraph[ ][n]
MGraph[v][j]//表示顶点v到当前顶点j的权值
-vSet[n]
vSet[v]=1或0//1表示顶点v已经被并入生成树中,0表示未并入.
-lowCost[n]
lowCost[v]//当前生成树到顶点v的最小边的权值.
求最小生成树代价的代码:
//
void Prim(int n,float MGraph[][n],int v0,float &sum)
{
/*
v:始终指向刚并入的顶点.
min:最小权值.
sum:最小权值之和.
*/
int lowCost[n],vSet[n];
int v,k,min;
v=v0;
vSet[v]=1;
sum=0;
//顶点v0已经并入生成树,还剩n-1个顶点未并入,需要处理n-1次
for(i=0;i<n-1;i++)
{
min=veryBig;//veyBig代表无穷大(用一个比所有边都大的数表示)
//遍历n个顶点
for(int j=0;j<n;++j)
{
//如果该顶点未并入生成树且到v的权值最小
if(vSet[j]==0 && lowCost[j]<min)
{
min=lowCost[j];//保存最小权值
k=j;//j为局部变量,把j赋值给全局变量k
}
vSet[k]=1;//将下标为k的顶点并入生成树
v=k;//为了确保指向新并入的顶点
sum+=min;//累加最小权值
/*并入了新顶点之后,当前生成树到其它顶点的最小权值发生了变化
n个顶点的lowCost[]值需要更新n次
*/
for(int j=0;j<n;j++)
{
//如果该结点未被并入生成树且顶点v到当前顶点的权值小于最小权值
if(vSet[j]==0&&MGraph[v][j]<lowCost[j])
{
//lowCost[]更新当前顶点的权值
lowCost[j]=MGraph[v][j];
}
}
}
}
}
构造最小生成树的步骤:
1.画一张图:假设有A B C D E F六个顶点
2.将六个顶点放在并查集中:A B C D E F
3.假设这张图有9条边,分别为1~9
4.假设取权值为1的边AB,A、B不是同一棵树,合并生成树AB(A表示这棵树的根结点)
A
B C D E F
5.假设取权值为2的边CD,C、D不是同一棵树,合并生成树CD(C表示这棵树的根结点)
A C
B D E F
6.假设取权值为3的边BC时,发现B和C所在的两棵树的根结点不同,所以也不是同一棵树,
合并生成树ABCD(CD的根结点接在AB的叶子结点上)
A
B E F
C
D
7.假设取权值为4的边AD时,发现A和D所在的两棵树都是以A作为根结点,是同一棵树,pass
8.假设取权值为5的边BE时,发现B和E所在的两棵树的根结点不同,所以也不是同一棵树,合并生成树ABCDEF(EF的根结点接在ABCD的叶子结点上)
A
B
C
D
E
F
结论:利用并查集可以很好的排除环,使得小树成功长成一棵大树
图的结构体设计
typedef struct
{
int a,b;//边的起点和终点
int weight;//边的权值
}Route;
Route route[maxSize];
求最小生成树代价的代码:
int getRoot(int p)
{
while(p!=DSU[p])p=DSU[p];
return p;
}
void Kruskal(Route route[],int n,int e,int &sum)
{
int a,b;
sum=0;
for(int i=0;i<n;i++)DSU[i]=i;
sort(route,e);
for(int i=0;i<e;++i)
{
a=getRoot(route[i].a);
b=getRoot(route[i].b);
if(a!=b)
{
DSU[a]=b;
sum+=route[i].weight;
}
}
}