图
图是一种非线性结构,比树更复杂。
图也是由一个个点组成的,在图中,我们把每个点叫做顶点(vertex)。
图中的任意一个顶点可以和其他顶点建立连接联系,这种关系叫做边。
一个顶点与其他顶点建立连接关系的总数叫做度,即顶点的边的条数。
很多社交软件都是用图来保存用户之间的关系,以微信和微博来说,微信和微博之间的好友,就是用边来记录的。
而微信和微博分别有单向好友和单向关注,那么又该怎么用边表示呢?其实引入一个“方向”概念就可以了。
这时,度分为入度(in - degree)和出度(out - degree)。
一个顶点的入度就是指向这个顶点的箭头边的总和,出度就是这个顶点指向其他顶点的箭头边的总和。
而如果是qq,那么会有亲密度这种东西,这时我们对边加上“权重”这种概念,每条边的权重代表亲密度即可。
如何存储图?
1.邻接矩阵
邻接矩阵很直观,依赖于一个二维数组,对于无向图,两点顶点有边的话,a[i][j]和a[j][i]都为1,有向图的话,如果从i指向j,那么a[i][j]为1,带权无向图的话,a[i][j]为权重。
其实我们可以看出,每一行就是对应一个顶点,这一行相当于该顶点包含的信息。
邻接矩阵简单,直观,基于数组,所以获取两个顶点的关系时效率很高,而且用邻接矩阵的方式存储图,可以将很多图的运算转换成矩阵之间的运算。
不过,无向图的话,其实整个矩形是沿对角线对称的,所以,我们其实只需要存储上三角或者下三角就可以了,而且,如果我们保存的是稀疏图(sparse matrix),也就是顶点很多,但每个顶点的边不多,那么会浪费更多内存。
邻接链表(Adjacency List)
如果我们不想浪费那么多内存,那么就改成用链表,这时的链表叫邻接表,和散列表相似
图中的每一个顶点都分别为一条链表的头部,每条链表接在头部后面的都是与头部形成连接关系的其他顶点。有向图的话,链表接着的就是指向头部顶点的顶点或者指向头部顶点的其他顶点。
不过链表对缓存不好,虽然内存更省,但查询效率更低。
在基于链表法解决冲突的散列表中,如果链过长,为了提高查找效率,我们可以将链表换成其他更加高效的数据结构,比如平衡二叉查找树等。我们刚刚也讲到,邻接表长得很像散列。所以,我们也可以将邻接表同散列表一样进行“改进升级”。
我们可以将邻接表中的链表改成平衡二叉查找树。实际开发中,我们可以选择用红黑树。这样,我们就可以更加快速地查找两个顶点之间是否存在边了。当然,这里的二叉查找树可以换成其他动态数据结构,比如跳表、散列表等。除此之外,我们还可以将链表改成有序动态数组,可以通过二分查找的方法来快速定位两个顶点之间否是存在边。