C++关于图论的入门常识(一)

这篇文章咩,主要给大家讲一下图论的有关知识和基本算法?(后面有很多东西我会一一给大家讲解)

目录

1.图

2.有向图和无向图

3.图的表示

好了,闲话少说,让我们切入正题


1.图

那么,首先我们要解决的问题就是,图是什么   ?????

一张简单的“图”

没错这的确是一张简单的图,因为我画的太简单了。

但是我们还是能从这张草率的图里获取信息的是吧。

那,那些圆圆的圈圈叫啥子呢?            答案是:顶点。

那连接源的线段你总该能猜到叫啥了吧……就是边啊。

那现在我们就要引进一个概念了,我们学过的几何,不管是平面的还是立体的,是不是都有“边长”这个概念呢?

那么在C++的图论里,我们把边唱这个概念定义为边权,即两顶点之间的距离(注意我说的是“距离”,这个概念大家都接触过吧,就是两点之间的最小值,换言之,两顶点之间的边权是唯一的)

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------

2.有向图和无向图

在了解了c++图论中这些基本的元素以后,我们来了解一下有向图和无向图这两个概念。

首先,举个栗子:

现在,学校放假了,只要你出去旅游,假期要多长有多长,而你是个非常热爱旅游的小宝宝你利用手机上的旅游软件规划自己的旅游路径,直到自己喜欢的地方都去了一遍,然而生活还是摆在眼前的,你没有充裕的资金来安排这次旅行,为了自己的旅行体验,只能寻找最便宜的路径,假定你灰常喜欢Aus(好吧其实是我灰常喜欢,就当你因为我讲得太优秀请我去好了),想把Aus的东海岸转一个遍,那最少需要多少资金?

 

这显然就是一个有向图的问题,我们就以这个题为例题,来考虑一下有向图的相关问题。

我们此行的目的地——Australia

 

我们来看一下…一个城市中的交通网:

好整齐呦额滴娘嘞(不吹不捧)

我突然觉得我们的资金连转完这一个city都不够用捏= =||    

好了,这就是我们真正要用到的东西,我把它简化了一下。

那么我们来看,这些城市之间肯定有来有回,不会有去到一个城市以后发现没有回来的路这种情况,所以这就是一个有向图,事实上,只要在一个图中,所有的边都是有向的,那么不管是不是有来回,它都是一个有向图,而像上图一样,所有边都没有方向,这就是一个无向图。

那么,有向图中的边就叫做有向边;无向图中的边就叫做无向边。

有向边可以用有序偶数对<Vi,Vj>来表示,表意为从Vi到Vj的有向线段,即这条边从Vi指向Vj。

无向边就好理解多了,可以用无序偶数对(Vi,Vj)来表示,表意为Vi和Vj之间有一条边。

那么这两个数对,一个叫做有序偶数对,一个叫做无序偶数对,看起来好像差不多,它们有什么区别呢?

我们来看这样一个图解:

这个图解左边是有向边的表示法,右边是无向边的表示法,如果我们将有向边的表示法改为<Vi,Vj>,那么图中的Vi和Vj就应该颠倒位置,但是,如果我们将无向边的表示法改为(Vj,Vi),这依然表示Vi和Vj之间有一条无向边,如果你非要找点不同的话……

可以理解成这个样子:Vj和Vi之间有一条无向边(但这本质上不是一样的吗……)

顺便提一下,我们表示图,一般可以采用这种方式:G1={Vi,Vj} E1={<Vi,Vj>}
有向图和无向图就这么讲完了,然后是图的表示。

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------

3.图的表示

热爱学习的你回到家以后对导航软件的无所不能表示惊奇,向蒟蒻我索取一些地图的基本工作原理,第一个当然就是把图表示出来啦!

在程序设计中,图的表示肯定没有我们看到的那么直观,那么图在程序设计中是怎么表示的呢?

图的表示分两种方法:邻接表和邻接矩阵。

我们先来看邻接矩阵。

邻接矩阵,顾名思义,既然是矩阵,肯定是用二维数组来存储图,邻接矩阵就是使用|V|*|V|的二维数组来存图,g[i][j]用来表示顶点i和顶点j的关系。

在无向图中,由于边没有方向,理论上只需要知道“两顶点之间有没有边相连”这样的信息,因此表示无向图时只需要用01来表示两点之间是否有边相连就可以,在实际运用中可以直接表示边的权值,需要对g[i][j]和g[j][i]都赋值。

在有向图中,由于两顶点之间的边是有方向的,就需要同时表示出g[i][j]和g[j][i]的值,注意,在有向图中两顶点之间的两边权值不一定相同,两顶点之间不同方向的边不仅仅是方向相反,权值也可能不同。

邻接矩阵的好处是可以在常数时间内判断两点之间是否有边存在(而且好写,一个二维数组有什么不好写的),但是需要花费O(|V|²)的空间。

 

然后我们来看邻接表。

首先我们需要想一个问题,既然邻接矩阵有这么多优点,为什么还要有邻接表呢?肯定是邻接矩阵有比较大的缺陷是吧!那么,具体是什么呢?

我们来想,现在给你一棵树,树只有|V|-1条边,而我们开了大小为|V|*|V|的二维数组来表示树上顶点之间的关系,也就是说,我们的数组中大部分元素都是0,这就浪费了大量的空间,在|V|达到1,000,000时,即使g的每个元素只需要1个字节的空间,整个数组也需要1TB才能存下,远远超出了我们做题的空间限制,显然是不行的。

然后我们再考虑一个问题,如果在无向图中,两点之间有重边或者一个点有自环,在带全图中就无法表示出来,这也可莪能直接导致我们程序错误,所以我们要引入邻接表,那么它有什么优点呢?我们往下看。

首先,邻接表可以存下所有的边;其次,邻接表就像对数据进行离散化一样,可以节省大量的空间,这是怎么实现的呢?

邻接表是通过把“从顶点0出发有到顶点2,4,5的边”这样的信息保存在链表中来存图的。这样只需要O(|V|+|E|)的空间,而且可以比较方便的表示重边和自环。

vector<int> G[MAX_V];
/*
边上有属性的情况
struct edge{int to,cost;};
vector<edge> G[MAX_V];
*/
int main(){
    int V,E;
    scanf("%d%d",&V,&E);
    for(int i=0;i<E;i++){
        int s,t;
        scanf("%d%d",&s,&t);
           G[s].push_back(t);//从s向t连边,如果是无向图,还需要从t向s连边
    }
    //加图的操作
    return 0;
}//第一种
struct vertex{
   vector<vertex*> edge;
    //加点的属性 
};
vertex G[MAX_V];
int main(){
    int V,E;
    scanf("%d%d",&V,&E);
    for(int i=0;i<E;i++){
       G[s].edge.push_back(&G[t]);//G[t].edge.push_back(&G[s]); 
    }
    //图的操作
    return 0;
}//第二种

在带权图等边有附加属性的图中,将便用结构体或者类来表示就可以很方便的存储了。邻接表虽然在边数稀少时只需要占用少量内存,但是和邻接矩阵相比实现较为复杂。而且,在邻接表中查询两点间是否有边需要遍历一遍链表才能知道。

好啦,这次就讲到这里,这些都是进行图论学习必备的一些常识,下面我会给大家讲解图的搜索以及最短路问题,我们以后再见。

  • 10
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

oblivion_Zzz

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值