基本概念
1.图是由顶点V的集合和边E的集合组成的二元组
记为 G=(V,E)
存在一个结点,可能有多个前驱结点和后继结点,链表,栈,队列一个结点只有一个前驱和一个后继,树结构则是一对多的结构,图则是多对多的数据结构
2.无向图:没方向,可以来回走
有向图:点与有向边,必须按照图中箭头的方向走
无权图和带权图
连通图:能连成一片的图(连通:两点之间有路可走)
连通分量:无向图中的极大连通子图
如图中的6和3,8和7,1和2和5和4
如何在一个无向图里找连通分量
先从1作为起点开始搜,它能访问到的所有点都和它在一个连通分量里,再以编号最小的没访问过的点再开始搜
路径:说白了就是从一个点到另一个点的路线,若起点与终点相同叫做环
无向图中顶点的度是指与顶点相连的边的条数
入度:以该点为终点的边
出度:以该点为起点的点
度:入度与出度之和
图的存储
1.邻接矩阵:需开二维数组,需要空间大,只能存一条边,然而实际上,两个点之间不止一条边
邻接表
用链式前向星实现
链式前向星:一个边集数组,把每个边的起点按从小到大的顺序排序,记录下以某个点为起点的所有边在数组中
的起始位置和边的数量
建立如下的结构体:
struct node
{
int w;//权值
int to;
int next;
}edge[1000];
edge[i].to 表示第i条边的终点,edge[i].next表示和第i条边有相同起点的下一条边的存储位置
另外还有一个数组head[],它是用来表示以i为起点的第一条边存储的位置,这里的第一条边存储的位置其实是在以i为起点的所有边的最后输入的那个编号.head[u]是指编号,u是指顶点
核心代码:
void add(int u,int v,int w)//起点,终点,权值
{
edge[cnt].w=w;//第cnt条边的权值
edge[cnt].to=v;//第cnt条边的终点
edge[cnt].next=head[u]; //edge[cnt].next表示与第cnt条边同起点的下一条边的存储值
head[u]=cnt++;// head[u]表示以u为起点的最后一条边的储存位置,cnt为按顺序的编号
}
head[u]通过计数器cnt的不断更新来记录以u为起点的边的编号
若输入
6
1 2 3
2 3 5
3 4 6
1 3 8
4 1 9
1 5 6
edge[1].to = 2; edge[1].next = -1; head[1] = 1;
edge[2].to = 3; edge[2].next = -1; head[2] = 2;
edge[3].to = 4; edge[3],next = -1; head[3] = 3;
edge[4].to = 3; edge[4].next = 1; head[1] = 4;
edge[5].to = 1; edge[5].next = -1; head[4] = 5;
edge[6].to = 5; edge[6].next = 4; head[1] = 6;
代码如下:
/*链式前向星的原理:是一个边集数组,把每个边的起点按从小到大的顺序排序
如果起点一样,就按照终点从小到大排序,记录下以某个点为起点的所有边在数组中
的起始位置和边的数量*/
#include<bits/stdc++.h>
using namespace std;
int head[1000];
int cnt;
struct node
{
int w;
int to;
int next;
}edge[1000];
void add(int u,int v,int w)//起点,终点,权值
{
edge[cnt].w=w;//第cnt条边的权值
edge[cnt].to=v;//第cnt条边的终点
edge[cnt].next=head[u]; //edge[cnt].next表示与第cnt条边同起点的下一条边的存储值
head[u]=cnt++;// head[u]表示以u为起点的最后一条边的储存位置,cnt为按顺序的编号
}
int main()
{
memset(head,-1,sizeof(head));
int n,a,b,c;
cin>>n;
cnt=1;
int start;
while(n--)
{
cin>>a>>b>>c;
add(a,b,c);
}
cin>>start;
for(int i=head[start];i!=-1;i=edge[i].next)//倒序遍历,head[u]存的值以u为起点的所有边中编号最大的那个
//而把这个当作顶点i的第一条起始边的位置.
cout<<start<<"->"<<edge[i].to<<" "<<edge[i].w<<endl;
}
模拟:当start=1时,循环变为 for(i = head[1];i!= 0;i = edge[1].next) 输出第六条边的终点和权值,即为1 5 6同时i = edge[6].next = 4(相当于一个链表将第四条边牵出来),输出第四条边的终点和权值即为1 3 8,这是i = edge[4].next = head[1] = 1,又将第一条边牵出来,输出第一条边的终点和权值,然后i=0,循环结束,其他类似。
在找到1号顶点的第一条边之后,剩下的边都可以在next数组中依次找到
我一开始很不理解edge[].next的作用,真的是想了很久,下面对此做一个理解:
以起点为1的举例,读入第一条边后,u=1,v=2,w=3,此时edge[1].next=-1,head[u]=1,然后接着读入第二条边,cnt变为3,读入第三条边cnt变为4,读入第四条边,u=1,v=3,w=8,注意!顶点是1的又来一条!此时edge[4].next=head[1]=1,head[1]=4,所以可以看出next作用就是记录的上一条和它顶点一样的边的编号,由于是类似于头插,所以可以说成是第cnt条边同起点的下一条边的存储值。
这个最后输出的循环很有意思啊…我好像还没这么用过