图论相关概念和链式前向星

基本概念
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条边同起点的下一条边的存储值。
这个最后输出的循环很有意思啊…我好像还没这么用过

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值