图 学习笔记(11.21)
Posted 未定_
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了图 学习笔记(11.21)相关的知识,希望对你有一定的参考价值。
最近数据结构和离散数学都学习了有关图的知识,自己学习算法也对图有了更加深入的了解,下面做一下汇总,便于复习翻看。(思路是自己理解写的,代码非原创)
图
一、图的概念
二、图的存储
先补充两种图的遍历方式:
1.邻接矩阵
思想:一个一维数组vertex[i]存顶点,一个二维数组edge[i][j]存边。
设图G=(V,E)有n个顶点,则邻接矩阵是一个n*n的方阵:
边无权值时,0表示顶点i和j之间无边,1表示有边:
e
d
g
e
[
i
]
[
j
]
=
0
若
(
v
i
,
v
j
)
∈
E
或
<
v
i
,
v
j
>
∈
E
1
否
则
edge[i][j]= \\begincases 0& &若(v_i,v_j)∈E或<v_i,v_j>∈E\\\\ 1& &否则 \\endcases
edge[i][j]=01若(vi,vj)∈E或<vi,vj>∈E否则
边有权值时,wij表示顶点i到j的权值,0表示i=j,∞表示i和j之间无边:
e
d
g
e
[
i
]
[
j
]
=
w
i
j
若
(
v
i
,
v
j
)
∈
E
或
<
v
i
,
v
j
>
∈
E
0
若
i
=
j
∞
否
则
edge[i][j]= \\begincases w_ij& &若(v_i,v_j)∈E或<v_i,v_j>∈E\\\\ 0& &若i=j\\\\ ∞& &否则 \\endcases
edge[i][j]=⎩⎪⎨⎪⎧wij0∞若(vi,vj)∈E或<vi,vj>∈E若i=j否则
无向图的邻接矩阵是对称矩阵,即edge[i][j]=edge[j][i],有向图则不符合这个特点。邻接矩阵的主对角线为0,无向图中顶点i的度为第i行/列中非零元素的个数,有向图中顶点i的出度为第i行元素之和,入度为第i列元素之和。
注意:edge[i][j]中i和j代表顶点的顺序取决于vertex[i]中存储的顺序,如:vertex[4]=v0,v3,v1,v2,则edge[2][1]对应的边是(v1,v3)而不是(v2,v1)。
时间和空间复杂度O(n2)
const int MaxSize=10;
int visited[MaxSize];
class Mgraph
public:
Mgraph(int a[],int n,int e);//构造函数,建立n个顶点e条边的图
~Mgraph()
void DFSTraverse(int v);//深度优先遍历图
void BFSTraverse(int v);//广度优先遍历图
private:
int vertex[MaxSize];
int edge[MaxSize][MaxSize];
int vertexNum,edgeNum;//顶点数与边数
;
Mgraph::Mgraph(int a[],int n,int e)
int i,j,k;
vertexNum=n;
edgeNum=e;
for(i=0; i<vertexNum; i++)
vertex[i]=a[i];//顶点数组赋值
for(i=0; i<vertexNum; i++)
for(j=0; j<vertexNum; j++)
edge[i][j]=0;//边数组初始化
for(k=0; k<edgeNum; k++) //给边赋值
cin>>i>>j;
edge[i][j]=1;
edge[j][i]=1;//无向图,若想变成有向图,只需要删除本句
void Mgraph::DFSTraverse(int v)
cout<<vertex[v];//访问顶点
visited[v]=1;//置顶点被访问过
for(int j=0; j<vertexNum; j++)
if(edge[v][j]==1&&visited[j]==0)
DFSTraverse(j);
void Mgraph::BFSTraverse(int v)
int w,j,Q[MaxSize];
int front=-1,rear=-1;
cout<<vertex[v];//访问顶点
visited[v]=1;//置顶点被访问过
Q[++rear]=v;
while(front!=rear)
w=Q[++front];//队头元素出队
for(j=0; j<vertexNum; j++)
if(edge[w][j]==1&&visited[j]==0)
cout<<vertex[j];
visited[j]=1;
Q[++rear]=j;
2.邻接表
顺序存储与链式存储相结合的表示图的一种方法。
思想:图的每个顶点vi,将所有邻接于vi的顶点链成一个单链表,称为顶点vi的边表(有向图则称为出边表)
所有边表的头指针和存储顶点信息的一维数组构成了顶点表。如下图:
如果边有权值的话只需要在边表结构体里加一个变量info表示权值即可。
无向图中顶点i的边表中结点的个数为顶点i的度。
有向图中顶点 i 的出边表中结点的个数为顶点 i 的出度;各顶点的出边表中以顶点 i 为
终点的结点个数为顶点 i 的入度。
时间和空间复杂度O(n+e)
const int MaxSize = 10;
struct EdgeNode//边表结点结构体
int adjvex;//该顶点的邻接点在顶点表中的下标
EdgeNode *next;
;
struct VertexNode//顶点表结点结构体
int vertex;//存放顶点信息
EdgeNode *firstEdge;//指向边表的第一个结点
;
int visited[MaxSize];
class ALGraph
public:
ALGraph(int a[], int n, int e); //构造函数,建立n个顶点e条边的图
~ALGraph();
void DFSTraverse(int v); //深度优先遍历图
void BFSTraverse(int v); //广度优先遍历图
private:
VertexNode adjlist[MaxSize]; //存放顶点表的数组
int vertexNum,edgeNum;//顶点数和边数
;
ALGraph::ALGraph(int a[],int n,int e)
int i,j,k;
EdgeNode *s=NULL;
vertexNum=n;
edgeNum=e;
for(i=0;i<vertexNum;i++)//初始化顶点表
adjlist[i].vertex=a[i];
adjlist[i].firstEdge=NULL;
for(k=0;k<edgeNum;k++)
cin>>i>>j;
s=new EdgeNode;
s->adjvex=j;
s->next=adjlist[i].firstEdge;
adjlist[i].firstEdge=s;//头插法
ALGraph::~ALGraph()
EdgeNode *p=NULL,*q=NULL;
for(int i=0;i<vertexNum;i++)
p=q=adjlist[i].firstEdge;
while(p!=NULL)
p=p->next;
delete q;
q=p;
void ALGraph::DFSTraverse(int v)
int j;
EdgeNode *p=NULL;
cout<<adjlist[v].vertex;
visited[v]=1;
p=adjlist[v].firstEdge; //工作指针p指向顶点v的边表
while(p!=NULL)
j=p->adjvex;
if(visited[j]==0)
DFSTraverse(j);
p=p->next;
void ALGraph::BFSTraverse(int v)
int w,j,Q[MaxSize];
int front=-1,rear=-1;
EdgeNode *p=NULL;
cout<<adjlist[v].vertex;
visited[v]=1;
Q[++rear]=v;
while(front!=rear)
w=Q[++front];
p=adjlist[w].firstEdge;
while(p!=NULL)
j=p->adjvex;
if(visited[j]==0)
cout<<adjlist[j].vertex;
visited[j]=1;
Q[++rear]=j;
p=p->next;
vector实现邻接表
const int MaxSize = 10;
struct edge//边
int from,to,w;//起点,终点,权值
edge(int a,int b,int c)//对边赋值
from=a;
to=b;
w=c;
;
vector<edge>e[MaxSize];//e[i]存第i个结点连接的所有边。
for(int i=1;i<=n;i++)//初始化
e[i].clear();
e[a].push_back(edge(a,b,c));//存边
for(int i=0;i<e[u].size();i++)//检索结点u的所有邻居
...
3.链式前向星
例题
链式前向星存图如下:u表示结点,h[u]用来定位u的第一条边,t[i].to存u的邻居结点,t[i].next定位u的下一个邻居结点。例如:u=7时,h[u]=12,即结点7的第一条边连接的邻居结点存在i=12这个位置,t[12].to=6,表示结点7的第一个邻居是结点6,t[12].next=10,即结点7的第二条边连接的邻居结点存在i=10这个位置里,t[10].to=5,即结点7的第二个邻居结点是5,依次类推,直到t[i].next=0时停止。
const int N=16000;
struct n
int to,next, w;
t[2*N];
int h[2*N],p=1;
void add(int u,int v,int w)
t[p].to=v;
t[p].w=w;
t[p].next=h[u];
h[u]=p++;
//遍历结点i的所有邻居
for(int i=h[u];i;i=t[i].next)
...
三、最小生成树
生成树: 包含图所有顶点的极小连通子图
生成树的代价: 生成树上各边的权值之和称为生成树的代价。
最小生成树: 生成树中代价最小的。
1.prim算法
此方法对点进行贪心操作,设所有结点集合为V,再另设一个集合U用来存最小生成树的结点。从任一点v开始,把离它最近的结点u1加入到集合U中,再从剩下的集合V-U中找到离U中某一结点最近的u2,把加到集合U中,继续上述过程直到U中加满所有结点。如下图,蓝色结点表示已经加入U中的结点,绿色表示正在比较的边。
设数组adjvex[n]存结点,lowcost[n]存每个结点最短边权值,lowcost[v]=0表示顶点v已加入最小生成树中(加入到U中)。
怎么把点加入到集合U中呢?是每一次都把V-U中的所有结点与U中所有结点相连的边都比较一遍吗?你会发现每一次比较都会有重复比较的边,极浪费时间。怎么办呢?
聪明的你一定想到了动态规划,不过这里不用DP数组来实现。我们考虑最初把已知结点v加入到集合U中,lowcost[v]=0,表示v到v的最短边为0,其余结点的最短边在没有比较的情况下都假定最短边是与结点v相连,即adjvex[i]=v,每个结点对应最短边lowcost[i]等于结点i与v的边的权值,这就是初始化。
接下来加点到U中,我们找V-U中所有结点到结点v的边中权值最小的,把对应的结点j1加入集合U中,代码对应就是找到当前lowcost数组中最小的lowcost[j],让其等于0;因为集合U的改变,V-U中到U中结点的最短边可能会改变,集合V-U结点最短边对应的邻接点也可能会变,需要更新两个数组。U中目前已经有结点j1,v,集合V-U中的任何一个结点到集合U
以上是关于图 学习笔记(11.21)的主要内容,如果未能解决你的问题,请参考以下文章
[原创]java WEB学习笔记61:Struts2学习之路--通用标签 property,uri,param,set,push,if-else,itertor,sort,date,a标签等(代码片段