极客算法

数据结构 - 图Graph

2018-09-07

社交网络是图比较常用的切入话题之一,包括N度人脉,单向关注,好友关系

graph_representation

edge(i, j)表示从点i到点j有一条边

树是一种特殊的图,图成为树的充要条件是:

  1. N个点,N-1条边
  2. 所有点连通

有权重

       (0)
      /   \
  1 /       \ 4
  /           \ 
(1)           (3)
  \           / 
  2 \       / 3
      \   /   
       (2)
# 邻接矩阵
     0   1   2   3
 0 [ 0   1   0   4 ]
 1 [ 1   0   2   0 ]
 2 [ 0   2   0   3 ]
 3 [ 4   0   3   0 ]

# 邻接表
{
  0: [(1, 1), (3, 4)],
  1: [(0, 1), (2, 2)],
  2: [(1, 2), (3, 3)],
  3: [(2, 3), (0, 4)]
}

# 边列表
[
  (0, 1, 1),
  (1, 2, 2),
  (2, 3, 3),
  (3, 0, 4)
]

无权重

       (0)
      /   \
    /       \ 
  /           \ 
(1)           (3)
  \           / 
    \       / 
      \   /   
       (2)
# 邻接矩阵
     0   1   2   3 
 0 [ 0   1   0   1 ]
 1 [ 1   0   1   0 ]
 2 [ 0   1   0   1 ]
 3 [ 1   0   1   0 ]

# 邻接表
{
  0: [1, 3],
  1: [0, 2],
  2: [1, 3],
  3: [2, 0]
}

# 边列表
[
  (0, 1),
  (1, 2),
  (2, 3),
  (3, 0)
]

持久化

内存中的数据结构,类等,通常无法直接用来网络传输,或存储在磁盘上,要通过序列化之后进行

序列化

object_to_string: 将内存中的数据结构,类等转化成字符串(字节组数)的过程

反序列化

string_to_object: 将序列化结果,从新解析成对应数据结构的过程

常见的序列化有xml, json, protobuf, thrift, yaml, plist

设计权衡通常有可读性,压缩率

搜索策略

对于可能有环的图的搜索,一定,一定,一定要去重复,重要的事情说三遍

广度优先BFS

BFS是图的遍历策略,该算法从某一指定节点出发,先搜遍所有的邻居节点,然后再拓展到下一层级,

如下图,以树为例,BFS结果是:

[1]
[2, 3, 4]
[5, 6, 7, 8]
[9, 10, 11, 12]

tree-breadth-first-search

深度优先DFS

DFS图的遍历策略,与BFS策略相反,该算法从某一指定节点出发,先探索尽可能远,然后回溯

回溯是指,先遍历[1, 2, 3, 4], 然后4没有更深的节点,然后把4丢掉,退回[1, 2, 3],再把5填到结果中得到[1, 2, 3, 5],以此类推

如下图:DFS结果是:

[1, 2, 3, 4] pop 4
[1, 2, 3, 5] pop 5, 3
[1, 2, 6] pop 6, 2
[1, 7] pop 7
[1, 8, 9, 10] pop 10
[1, 8, 9, 11] pop 11, 9
[1, 8, 12]

tree-breadth-first-search

代码实现

图的搜索遍历一定要记得记得去重, 由于树没有环这里省略去重复

递归实现

DFS

def __init__(self):
    val = 0
    children = []

def search(node):
    if node is None:
        return
    print(node.val)
    for sub in node.children:
        search(sub)

非递归实现

DFS

nodes_to_visit = [root]

while( len(nodes_to_visit) > 0 ) {
    #从尾部取出
    node = nodes_to_visit.pop()
    print(node.val)
    for sub in node.children:
        nodes_to_visit.append(sub) 
}

BFS

nodes_to_visit = [root]

while( len(nodes_to_visit) > 0 ) {
    #从头部取出
    node = nodes_to_visit.pop(0)
    print(node.val)
    for sub in node.children:
        nodes_to_visit.append(sub) 
}

如果你仔细观察,非递归版本BFS与DFS是非常对称,只是DFS从尾部取出(Stack),BFS从头部取出(Queue)

二分图

把无向图中的节点分成两个阵营A,B。 所有的链接都必须从A阵营连接到B阵营(反之亦然)。这样的图为二分图。

比如异性相亲,2个阵营,每次匹配都只能是异性接触

性质如下:

  1. 图中不能有奇数环,比如三角形,五边形等
  2. 可以二分着色,从任意一点着蓝色,连接的节点不能颜色相同,不会有冲突,孤立节点可以分为任意阵营

着色判断法

from collections import deque
class Solution:
    def isBipartite(self, graph: List[List[int]]) -> bool:
        n = len(graph)
        colored = {}
        q = deque()

        for i in range(n):
            if i not in colored:
                colored[i] = 1
                q.append(i)

            while len(q) > 0:
                cur = q.popleft()
                for node in graph[cur]:
                    if node not in colored:
                        q.append(node)
                        colored[node] = 2 if colored[cur] == 1 else 1
                    elif colored[node] == colored[cur]:
                            return False

        return True 

拓扑排序

拓扑排序是一种对有向无环图(DAG)进行排序的方法,它对图中的所有节点进行线性排序。

比如课程,先参加A,才能参加B课程。 如果有环,那么A,B循环依赖,无法拓扑排序

课程排序

from collections import defaultdict, deque
class Solution:
    def findOrder(self, numCourses: int, prerequisites: List[List[int]]) -> List[int]:
        graph = defaultdict(list)
        indegree = defaultdict(int)
        for x in prerequisites:
            graph[x[1]].append(x[0])
            indegree[x[0]] += 1

        res = []
        q = deque([x for x in range(numCourses) if indegree[x] == 0])
        while len(q) > 0:
            cur = q.popleft()
            res.append(cur)
            for node in graph[cur]:
                indegree[node] -= 1
                if indegree[node] == 0:
                    q.append(node)

        return res if len(res) == numCourses else []

–END–


上一篇 Linux进程管理

评论

内容:
其他: