图论(1) 图的基本数据结构和算法
图里面的东西太多,先写一个概要。在后面的文章中将继续逐个探讨和实现。
1,一些概念
顶点
边(无向图) 或 弧(有向图)
完全图,子图,连通图
路径,简单路径(顶点不重复)
生成树(无向图),关键路径,拓扑排序
2,存储结构
非常重要!!!
2种存储方式,邻接矩阵和邻接表
目前我写的都是用邻接表写的,但后来发现大家还是用邻接矩阵比较多,后来问了下baidu的阿海,他说一般都是用邻接矩阵,简单一些,但用过邻接表话,肯定可以秒杀邻接矩阵。恩 ,有时间我也写一下邻接矩阵的实现,,,,
无论哪种方式,顶点一般来说都放在数组里。(当然你也可以放在链表里,这样似乎很麻烦,很少见到这样做的),两种方式的区别在于顶点之间的关系的实现用什么
1)邻接表
这里的表其实是存储边得链表。
将顶点放在一个Node[]数组里,每一个顶点维护一个它的边的链表(想想怎么去实现?),下面是顶点类的定义:
package Graph;
public class VNode {
private Object element; //节点自身信息
private Edge firstEdge; //节点的第一条边
private boolean visited = false; //在遍历时标识是否被访问过
//get,set方法省略
}
每次写一个类似于C里面的结构体类,都把成员变量写为private,结果就需要写很多的get和set方法来访问和设置,这样做符合面向对象封装的要求,但算法的代码就显得比较麻烦,为了简化代码,侧重算法,以后我就都把它们写成public了。这里还是写成了private,get和set方法省略了。
这样Node[]数组里存放的就是上述顶点类型,每一个顶点包含顶点自身存放元素的信息(必备),是否被访问过的信息(遍历时用到),指向第一条边的Edge指针,
要维护顶点的边链表,还需要一个边类:
package Graph;
public class Edge {
private int start,end; //边指向两个节点的位置(如果用数组存放节点,就是下标,如果用链存放下标,就是位置索引)
private Edge next; //边指向的下一条边
private int len; //边的信息(长度)
//省略读写方法
}
我用边得首尾顶点在顶点数组里的下标来标识一条边(这样有向无向图可以一起写),在边里还要维护一个指向下一个边的Edge指针(维护边链表),还有一个边的长度信息(写最小生成树的时候用到,或者其他的加权图)
上述2个顶点和边的定义就可以实现邻接表的存储方式。
2)邻接矩阵
现在顶点的定义只需要放顶点的元素和是否被访问即可,不需要指向第一个边的指针。
我们还是用Node[]放顶点,用一个关系矩阵来存放顶点之间的关系(是否有边连接)
关系矩阵用一个二维数组实现,
M[n][n]---假设有n个顶点的关系矩阵,M[i][j]的值表示顶点i和顶点j的关系
3,基本的算法
1)广度优先搜索
队列实现
2)深度优先搜索
栈实现
3)无向图的最小生成树
2种贪心算法
4)拓扑排序
5)带权有向无环图的关键路径
6)最短路径
总结:
图的存储方式很重要,弄清楚怎么去标表示图,才能做接下来的工作---
邻接表是指每个顶点维护一个跟它相关联的边得链表,而不是跟它相关联的顶点的链表,也就是说需要有2个类,顶点类(要包含指向第一个关联边的引用),边类(要包含指向下一条关联边得指针)。如果只有顶点类的话,那不行,边的长度信息没有地方存储。
这是用邻接表的时候的一个小思考和误区,如果实现成相关联的顶点的链表,那么只能做广搜和深搜,对于加权图有关的算法就无能为力了,这样实现只能表示顶点之间的是否有连接的关系,边的信息不存在。