文章目录
前言
本文是一篇图的复习总结文章。文首对定义和术语细致罗列,主体部分使用“抓主干,探细微”的方法即将知识点简要回顾,并将每一个知识点的详细复习嵌入超链接,文末对几道疑难问题进行了讨论。希望这一篇有层次有重点的博客能帮大家和我本人复习起来能够事半功倍。
一、定义及基本术语
1.图的定义
G=(V,E),V为顶点集,E为边集。设图有n个顶点,V={v1,v2,v3,…,vn}
2.图的基本术语
有向图:<v,w>属于E,表示从弧尾v到弧头w的一条弧。
无向图:边(v,w)属于E,
混合图:既有有向边,又有无向边的图
简单图:简单无向图(不存在顶点到自身的边,且任意两个不同的顶点之间没有平行的两条边),简单有向图(不存在顶点到自身的弧,且任意两个不同的顶点之间没有同方向 的两条弧)。简单无向图和简单有向图统称为简单图。
邻接、依附或关联:若无向图有边(v,w),则称顶点v和w相邻或邻接,称(v,w)依附点点v和w,或称与边(v,w)相关联的 两个顶点是v和w;有向图若有弧<v,w>,则称顶点v邻 接到w,w邻接自v,弧<v,w>依附顶点v和w,或称与弧<v,w>相关联的两个顶点是v和w。通常称w是v的邻接点
无向完全图:对简单无向图,图中任意两个不同的定点件都有边。有n个顶点的无向完全图有n(n-1)/2条边
有向完全图:对简单有向图,任意两个顶点间都有方向互为相反的两条弧。有n个顶点的有向完全图有n(n-1)条弧
网或赋权图:无向图或有向图的边或弧上带有一个表示某种物理量的权值
稀疏图、稠密图:边或弧数很少(多)的无向图或有向图
顶点的度、入度、出度:无向图中任意顶点v,与v相关联的边数称为v的度,Degree(v),间记D(v),有n个顶点和e条边的无向图,所有顶点的度之和是边总数的2倍。有向图 中, 以顶点v为弧尾的弧的数目称为v的出度,OD(v),以v为弧头的的弧的数目称为v的入度,ID(v),D(v)=ID(v)+OD(v)为v的度。有n个顶点e条弧的有 向图,所有顶点的入度之和等于出度之和等于边总数e。
子图:G=(V,E),G’=(V’,E’),若V’是V的(真)子集,E’是E的(真)子集,且E’中的边仅与V’中的顶点相关联,则G’是G的(真)子图。
路径、简单路径、回路:无(有)向图G=(V,E),若有顶点序列vs=vi1,vi2,vi3,…,vik=vk,且边(vij-1,vij)(弧<vij-1,vij>)属于E,称vs到vk存在路径vi1,vi2,vi3,…,vik。若vs到 vk路 径上顶点除顶点vs和vk可以相同外,其他顶点都不同,上述路径为简单路径。若vs=vk,则称为回路。
连通和可达:有向图中顶点v到w有路径称v到w是可达的。无向图v到w有路径称v和w是连通的
连通图和强连通图:无向图中任意两个不同顶点都是连通的称它为连通图,否则为非连通图。有向图中任意两个不同顶点都是可达的称之为强连通图或简称连通图,否则为非强 连通图或非连通图
连通分量和强连通分量:无向图的极大连通子图称为连通分量有向图的极大强连通子图称为强连通分量或连通分量。极大指该子图包括了所有连通的顶点以及这些顶点相关联的 所有边。
树和有向树:连通且无回路的的无向图称为无向树,简称树。含n个顶点的树有n-1条边。在忽略弧方向后,连通且无回路的有向树称为有向树。含n个顶点的有向树有n-1条弧。 实际中指的有向树在选定一个顶点作为根节点后,弧的方向都与从根节点指向叶结点的方向一致或全部相反。
生成树、生成森林:由n个顶点构成的连通无向图的任何一个含n个顶点的极小连通子图称为该图的生成树。对于非连通无向图,由连通子图课得到生成子树,非连通无向图的所 有连通分量得到的生成子树构成该树的生成森林
————————————————
二、分类总结
1.无权图
无向图
——搜索
——最小生成树
Prim算法(图论中的一种算法,可在加权连通图里搜索最小生成树。意即由此算法搜索到的边子集所构成的树中,不但包括了连通图里的所有顶点,且其所有边的权值之和亦为最小。)
在我看来,这两种算法最大的区别就是前者对点操作而后者对边操作。
有向图
——拓扑排序
造出拓扑序列的实际意义是:如果按照拓扑序列中的顶点次序,在开始每一项活动时,能够保证它的所有前驱活动都已完成,从而使整个工程顺序进行,不会出现冲突的情况。
——有向图的连通性
2.带权图
——带权图的最小生成树
带权图的最小生成树同样主要使用Prim算法和Kruskal算法。其实就是找到一棵最小生成树,连接所有顶点,权值最小。要注意,最小生成树也不一定是唯一的。这在现实生活中有很大的意义,例如光纤布线等。要实现这个算法需要使用优先级队列。但这里要注意两个问题:
(1)连通n个顶点,需要n-1条边;(2)尽可能选取权值小的边,不能构成回路。
两个算法:
(1)Prim算法
此算法可以称为“加点法”,每次迭代选择代价最小的边对应的点,加入到最小生成树中。算法从某一个顶点s开始,逐渐长大覆盖整个连通网的所有顶点。
图的所有顶点集合为;初始令集合;
在两个集合能够组成的边中,选择一条代价最小的边,加入到最小生成树中,并把并入到集合u中。
重复上述步骤,直到最小生成树有n-1条边或者n个顶点为止。
由于不断向集合u中加点,所以最小代价边必须同步更新;需要建立一个辅助数组closedge,用来维护集合v中每个顶点与集合u中最小代价边信息:
(2)Kruskal算法
此算法可以称为“加边法”,初始最小生成树边数为0,每迭代一次就选择一条满足条件的最小代价边,加入到最小生成树的边集合里。
- 把图中的所有边按代价从小到大排序;
- 把图中的n个顶点看成独立的n棵树组成的森林;
- 按权值从小到大选择边,所选的边连接的两个顶点,应属于两颗不同的树(为什么要有这个规定,是因为如果两个顶点属于不同的树,就不会形成环了),则成为最小生成树的一条边,并将这两颗树合并作为一颗树。
- 重复(3),直到所有顶点都在一颗树内或者有n-1条边为止。
——最短路径
对于带权无向图来说,还有一个很重要的问题:给定两个顶点,如何找到一条路径,令两个顶点之间连通,而且权值最小呢?这里要用到Floyd算法。而如果是带权的有向图,要使用Dijkstra算法。
——AOE网与关键路径
三、疑难问题及解决方案
1.六度空间“六度空间”理论又称作“六度分隔(Six Degrees of Separation)”理论。这个理论可以通俗地阐述为:“你和任何一个陌生人之间所间隔的人不会超过六个,也就是说,最多通过五个人你就能够认识任何一个陌生人。”如图所示:“六度空间”理论虽然得到广泛的认同,并且正在得到越来越多的应用。但是数十年来,试图验证这个理论始终是许多社会学家努力追求的目标。然而由于历史的原因,这样的研究具有太大的局限性和困难。随着当代人的联络主要依赖于电话、短信、微信以及因特网上即时通信等工具,能够体现社交网络关系的一手数据已经逐渐使得“六度空间”理论的验证成为可能。
假如给你一个社交网络图,请你对每个节点计算符合“六度空间”理论的结点占结点总数的百分比。
输入格式:
输入第1行给出两个正整数,分别表示社交网络图的结点数N(1,表示人数)、边数M(≤,表示社交关系数)。随后的M行对应M条边,每行给出一对正整数,分别是该条边直接连通的两个结点的编号(节点从1到N编号)。
输出格式:
对每个结点输出与该结点距离不超过6的结点数占结点总数的百分比,精确到小数点后2位。每个结节点输出一行,格式为“结点编号:(空格)百分比%”。
输入样例:
下面展示一些 内联代码片
。
10 9
1 2
2 3
3 4
4 5
5 6
6 7
7 8
8 9
9 10
输出样例:
1: 70.00%
2: 80.00%
3: 90.00%
4: 100.00%
5: 100.00%
6: 100.00%
7: 100.00%
8: 90.00%
9: 80.00%
10: 70.00%
解题思路:1.对每个结点使用广度优先搜索距离小于6的结点,并统计个数
2.使用层数(level)来表示距离,其中本结点为第0层,距离1的结点为第1层…
#include<stdio.h>
#include<stdlib.h>
#define MAXVEX 10005
void CreateGraph( );
int BFSTraverse(int i);
int G[MAXVEX][MAXVEX],Nv,Ne;
int visited[MAXVEX];
int main()
{
int i,j;
int count;
double b;
CreateGraph();
for( i=1; i<=Nv; i++)
{
count = BFSTraverse(i);
b = 100.0*count/Nv;
printf("%d: %.2f%%\n",i,b);
}
return 0;
}
void CreateGraph()
{
//用邻接矩阵表示图
int i,j;
int v1,v2;
scanf("%d %d",&Nv,&Ne);
for( i=0; i<=Nv; i++)
{
for( j=0; j<=Nv; j++)
{
G[i][j] = 0; //初始化
}
}
for( i=0; i<Ne; i++) //注意这里是读入边
{
scanf("%d %d",&v1,&v2);
G[v1][v2] = 1;
G[v2][v1]= G[v1][v2]; //无向图对称
}
}
int BFSTraverse( int i)
{
int q[MAXVEX]= {0}; //用数组表示队列
int rear=-1,front=-1;
int j;
int temp;
int cnt ;
int level; //当前结点所在的层数
int last; //该层的最后一个结点
int tail; //最后一个进入队列的结点
for( j=0; j<=Nv; j++)
{
visited[j] = 0;
}
visited[i] =1;
cnt = 1;
level = 0; //本结点不算在层数里
last = i;
q[++rear] = i; //入队
while( front<rear ) //判断队列是否为空
{
temp =q[++front]; //出队
for( j=1; j<=Nv; j++)
{
if( G[temp][j] && !visited[j])
{
visited[j] = 1;
q[++rear] = j;
cnt ++;
tail = j;
}
}
if( temp==last)
{
level ++;
last = tail;
}
if( level==6 )
{
break;
}
}
return cnt;
}
2.旅游规划
此题难度较高,可移步此博客获得较详细的解析。