图及图的存储

图及图的存储

         图(Graph)是由顶点集合和一些顶点间的连线组成的数据结构。通常可以用G(V, E)来表示。其中顶点集合(Vertext Set)和边的集合(Edge Set)分别用V(G)和E(G)表示。V(G)中的元素称为顶点(vertex),用u、v等符号表示。E(G)中的元素称为边(edge),用e等符号表示。
    
    图1(a)所示的图可以表示为G1(V, E)。其中,顶点集合V={1,2,3,4,5,6},边集合E={(1,2), (1,3), (2,3), (2,4), (2,5), (2,6), (3,4), (3,5), (4,5)}。这样的图叫无向图。边(u,v)和(v,u)是同一条边。
    图1(b)所示的图是有向图。有向边<u,v>中u为起点,v为终点。

    如果无向图中任何一对顶点之间都有一条边,这样的图称为完全图。在完全图中,顶点数m和边数n的关系为:m=n×(n-1)/2。
    边的数目相对较少的图称为稀疏图。边的数目相对较多的图称为稠密图。
    
    在无向图中,如果(u,v)是图中的一条无向边,则称顶点u和顶点v互为邻接顶点,或称(u,v)与顶点u和v相关联。
    顶点的度数:一个顶点u的度数是与它相关联的边的数目,记为deg(u)。有向图中,顶点的度数等于该顶点的出度与入度之和。其中,顶点u的出度是以u为起始顶点的有向边(即从顶点u出发的有向边)的数目,记为od(u)。顶点u的入度是以u为终点的有向边(即进入到顶点u的有向边)的数目,记为id(u)。顶点u的度数:deg(u)=od(u) + id(u)。

    定理: 在无向图和有向图中,所有顶点度数总和,等于边数的两倍。

         这是因为,不管是有向图还是无向图,在统计所有顶点的度数之和时,每条边都被统计两次。为了方便起见,我们把度数为偶数的顶点称为偶点,把度数为奇数的顶点称为奇点。有以下推论:
    推论:每个图都有偶数个奇点。


图的连通性

        在无向图中,如果从顶点u到v有路径,则称为顶点u和v是连通的。如果无向图中任意一对顶点都是连通的,则称此图为连通图。相反,如果一个无向图不是连通图,则称为非连通图。
    
    如果一个无向图不是连通的,则其极大连通子图称为连通分量。这里的极大是指子图中包含的顶点个数极大。例如图4所示的无向图就是非连通图。其中顶点1,2,3和5构成一个连通分量,顶点4,6,7和8构成另一个连通分量。
    在有向图中,若对每一对顶点u和v,即存在从u到v的路径,也存在从v到u的路径,则称此图为强连通图。对于非强连通图,其极大强连通子图称为其强连通分量。
    某些图的边具有与它相关的数,称为权值。这些权值可以表示一个顶点到另一个顶点的距离、花费的代价、所需的时间等等。如果一个图,其所有边都具有权值,则称为加权图,或者称为网络(net)。



图的存储结构

        图的存储结构主要有四种:
        ★ 邻接矩阵(使用二维数组存储,不推荐使用)
        ★ 前向星
        ★ 邻接表
        ★ 链式前向星(静态建表)
        在编写程序中,后三种是我们经常采用的图的存储结构。

一、邻接矩阵(不推荐使用)

        在邻接矩阵存储方法中,使用一个二维数组e[n][n]来表示,定义为:
    
    例如图1(a)中,下图表示无向图G1及其邻接矩阵表示。注意:如果图中存在自身环(连接某个顶点自身的边)和重边,多条边的起点一样,终点也一样,也称为平行边的情况,则无法使用邻接矩阵存储。
    

例1:图的存储

        给定n个顶点,m条边,接下来m行,每行三个整数a,b,x,表示边(a,b)的权植为x
样例输入:
5 4
1 3 1
1 4 10
2 3 20
3 5 20

cin >> n >> m;
for(int i=0;i<m;i++){
	cin >> a >> b >> x;
	e[a][b]=e[b][a]=x;
}

二、前向星

        前向星是一种通过存储边的信息的方式存储图的数据结构。它的构造方式非常简单,读入每条边的信息,将边存放在数组中,把数组中的边按照起点顺序排序,前向星就构造完成了。为了查询方便,经常会有一个数组存储起点为Vi的第一条边的位置。

例2:前向星

        给定n个顶点,m条边,接下来m行,每行三个整数a,b,x,表示边(a,b)的权植为x。将边从小到大排序后输出。
样例输入:
8 12
5 8 9
6 1 12
8 3 11
1 2 4
3 1 22
4 3 17
7 4 25
6 5 9
8 7 7
1 6 9
3 2 19
6 7 4
样例输出:
1 2 4
1 6 9
3 1 22
3 2 19
4 3 17
5 8 9
6 1 12
6 5 9
6 7 4
7 4 25
8 3 11
8 7 7

#include <iostream>
#include <string.h>
#include <algorithm>
using namespace std;
int head[10005]; //存储起点为Vi的第一条边的位置
struct note{
    int xx,yy,ww;  //起点,终点,权值
}e[10005];
bool cmp(note a, note b){
    if(a.xx==b.xx && a.yy==b.yy) return a.ww<b.ww;
    if(a.xx==b.xx)  return a.yy<b.yy;
    return a.xx<b.xx;
}
int main(){
     int n, m;
     cin >> n >> m; //n个顶点, m条边 
     for(int i=0; i<m; i++) cin >> e[i].xx >> e[i].yy >> e[i].ww; //读入边 
     sort(e,e+m,cmp); //将边按顶点从小到大排序 
     memset(head, -1, sizeof(head));  //head初始化为-1 
     head[e[0].xx]=0;
     for(int i=1; i<m; i++)
        if(e[i].xx!=e[i-1].xx) head[e[i].xx]=i; //确定起点为Vi的第一条边的位置
     int k;
     for(int i=1;i<=n;i++)
     	for(k=head[i]; e[k].xx==i && k<m; k++) cout << e[k].xx<<" " << e[k].yy<<" "<< e[k].ww << endl;
     return 0;
}

三、邻接表

        邻接表是一种链式的存储结构。对于图G中的每个顶点Vi,所有邻接于Vi顶点Vj链成一个单链表,这个单链表称为顶点Vi的邻接表。
        邻接表中每个表节点有三个属性:其一,邻接点序号to,用以存放与顶点Vi 相邻接的顶点vj的序号j,其二,边上的权值我,其三,为指针next,用来将邻接表的所有节点链在一起。另外,为每个顶点Vi的邻接表设置一个具有两个属性的表头节点:一个是顶点序号from,另一个是指向其邻接表的指针first,它是指向Vi的邻接表的第一个节点的指针。建立一个Vnode的数组就可以访问每个顶点的邻接表了。
        上面的例题2中,利用邻接表存储如下:

#include <iostream>
#include <string.h>
#include <stdio.h>
using namespace std;
const int maxn=100005;
struct  enode{ //边 
    int to;
    int w;
    enode *next;
};
struct vnode{ //顶点 
    int from;
    enode *first;
};
vnode adjilist[maxn];
int main(){
    int n,m,j,i,w;
    while(cin >> n >> m){
        for(int ii=1; ii<=m; ii++){
            cin >> i >> j >> w;
            enode *p=new enode();
            p->to=j;
            p->w=w;
            p->next=adjilist[i].first;
            adjilist[i].first = p;
        }
        for(int i=1; i<=n; i++){
            for(enode *k=adjilist[i].first; k!=NULL; k=k->next) cout << i << " "<< k->to << ' ' << k->w << endl;
        }
    }
    return 0;
}

        另外,还可以只用到一个结构体,接着结构体数组定义为结构体指针数组,代码如下:

#include <iostream>
#include <stdio.h>
#include <string.h>
using namespace std;
struct enode{
    int to;
    int w;
    edgenode *next;
}*N[10005];
int main(){
    int i,j,w,n;
    while(~scanf("%d",&n)){
        for(int kk=0; kk<n; kk++){
            cin >> i >> j >> w;
            edgenode *p=new edgenode();
            p->to=j;
            p->w=w;
            p->next=N[i];
            N[i]=p;
        }
        for(int i=1; i<=n; i++)
            for(edgenode *k=N[i]; k!=NULL; k=k->next) cout << i << " " << k ->to << " " << k->w << endl;
    }
    return 0;
}



四、链式前向星(静态建表)(熟记)

        数组模拟链表的主要方式是记录下一个节点在数组中的那个位置。head数组存储描述vi边信息的链的起点在edge数组的位置。构造链式前向星就是将新加入的节点链在对应链的最开始并修改head数组的对应位置的值。
        链式前向星的建图效率非常高,读入结束建图就结束,时间复杂度为O(m),空间上使用了两个数组,所以空间复杂度为O(m+n)。
        缺点:不能用起点和终点来确定是否有边存在。。。。

#include <iostream>
#include <istring.h>
#include <itdio.h>
#include <iiostream>
#include <ialgorithm>
using namespace std;
const int N=10005;
int head[N];
int ip;
struct enode{
    int yy;
    int ww;
    int next;
}e[N];
void add(int u,int v,int w){
    e[ip].yy=v, e[ip].ww=w, e[ip].next=head[u], head[u]=ip++;
}
int main(){
    int n,m,x,y,w,k=0;
	scanf("%d%d",&n,&m);
    memset(head,-1,sizeof(head)); //head所有初始值为-1
    ip=0;
    for(int k=0; k<m; k++){
        cin >> x >> y >> w;
        add(x,y,w);  //add(y,x,w);若是无向图就加上这句话
    }
    for(int i=1;i<=n;i++)
        for(int k=head[i]; k!=-1; k=e[k].next) cout << i << ' ' << e[k].yy << ' ' << e[k].ww << endl;
    return 0;
}

程序说明:
        假如在上面的程序中,读入如下数据:

7 10
5 6 8
6 7 7
2 5 5
3 5 6
3 6 4
4 6 10
4 7 5
1 2 2
3 1 3
1 4 3
在读入数据中,从顶点3出发的边共有3条,依次为(3,5)、(3,6)和(3,1)。
读入边(3,5)时(说明:第4次读入),e[3].yy=5,表示编号为3的边的终点为5。
	       e[3].ww=6,表示编号为3的边的权值为6。
	       e[3].next=head[u]=head[3]=-1(初始为-1),表示编号为3的边只有一条,没有上一条边。
	      对应的head[3]=3,表示以顶点3为起点的第一条边的编号为3。
读入边(3,6)时(说明:第5次读入),e[4].yy=6,表示编号为3的边的终点为6。
	       e[4].ww=4,表示编号为3的边的权值为4。
	       e[4].next=head[u]=head[3]=3,表示编号为4的上一条边的编号为3。
	      对应的head[3]=4,表示以顶点3为起点的第一条边的编号为4。
读入边(3,1)时(说明:第8次读入),e[7].yy=1,表示编号为7的边的终点为1。
	       e[7].ww=3,表示编号为3的边的权值为3。
	       e[7].next=head[u]=head[3]=4,表示编号为7的上一条边的编号为4。
	      对应的head[3]=7,表示以顶点3为起点的第一条边的编号为7。
最终,head[3]=7。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值