9.2.1 图的存储与遍历

2021 继往开来,继续学习数据结构。

9.1一节简述图的基本概念及存储结构,这一节我们深入看一下图的存储,学习图的遍历方法,实现图的存储和遍历。

图的存储

首先说最容易理解的临接矩阵,矩阵需要n平方个元素的存储空间,声明的又是连续的空间地址。由于计算机内存的限制,存储的顶点数目也是有限的。对于简单的问题和场景,可以考虑使用。对于庞大的图结构,可以使用稀疏矩阵表示,但是应用起来就不太容易了。

所以,还是主要了解临接链表方法。虽然实现稍微复杂,但是有他独特的应用价值,无论是扩展性、应用性还是性能,都是优秀的。

邻接链表由数组 + 链表组成,顶点可以保持在数组中,邻接关系则用链表实现,利用链表高效的插入和删除,实现内存的充分利用。但是查找的时候,只能是全部遍历一次了。

邻接链表的存储也可以进行一定程度的优化或变种。A 使用set来保存顶点信息,方便顶点的查找和插入删除,甚至直接用map存储顶点及临接点点对应关系;B list代替链表存储顶点的临接点,对于某些操作比较方便,而且相比矩阵型形式,还是节省空间的。为了查找方便,临接点也可以用set存储。

 

图的遍历基本法

遍历就是从图中某一个顶点出发遍历途中其余顶点,每一个顶点仅被访问一次。

  1. 基本思路
    (1)树有根节点,每个节点延伸方向最多两个。而图的情况是,没有一个初始节点,每个节点邻接方向也不止一个,顺着一个点,极有可能最后又找到自己,形成回路导致死循环。所以要设置一个数组visit[n],n是图中顶点个数,初值为0,当该顶点被遍历后,修改数组元素的值为1
    (2)基于此,图的遍历也是2种遍历方案:深度优先遍历和广度优先遍历。

  2. 深度优先遍历(DFS) 

    深度遍历,一个原则就是,对于当前顶点当我们发现有多个出度时,按照统一标准选择下一个遍历的顶点,比如在链表中,选择第一个,当该顶点已遍历过,选择第二个;当所有临接的点都已遍历,则回退,看当前节点入度的其他出度。直到所有的点都被遍历。

  3. 广度优先遍历(BFS)

    类似树的层次遍历,我们先准备一个队列,先入队一个顶点,弹出队列顶端的1个点访问,并把它连接的顶点入队;重复以上过程,直到队列为空。注意弹出的点才能访问,并且确保该点未被访问。

代码实现

图1-2:有向图

我们根据上图,实践一下图的存储

# 临接链表大法存储图
class node:
    def __init__(self, val):
        self.data = val  # 顶点的id/信息
        #... 其他信息都可以存
        self.adj = None  # 出度顶点链表

class linknode:
    def __init__(self, v, w=1):
        self.val = v
        self.weight = w
        self.next_ = None

class graph:
    def __init__(self, n):
        self.node_num = n
        self.node_map = dict()
    def create(self, edge_lst):
        for a, b in edge_lst:
            # 创建时 a,b点都要加入到顶点map
            if a not in self.node_map.keys():
                nodea = node(a)
                nodea.adj = linknode(b)
                self.node_map[a] = nodea
            else:
                nodea = self.node_map[a]
                adj_link = nodea.adj
                # 遍历链表 添加到在哪儿都行 注意判断head
                if not adj_link:
                    nodea.adj = linknode(b)
                else:
                    next_ = adj_link.next_  # 插入到第二位
                    linkb = linknode(b)
                    adj_link.next_ = linkb
                    linkb.next_ = next_
            if b not in self.node_map.keys():
                nodeb = node(b)
                self.node_map[b] = nodeb
    def add_edge(self, a, b):
        pass
    def del_edge(self, a, b):
        pass

我们输入顶点的数量和所有边,构造图

# 顶点 8
# 边 9
edges = [(5,11),
(7,11),
(11,2),
(11,9),
(11,10),
(8,9),
(3,10),
(7,8),
(3,8)]

# 我们的代码的用法是 根据顶点数量初始化,然后调用create函数 输入所有边的信息
gra1 = graph(8)
gra.create(edges)

下面我们遍历该图,输出顶点和边,看看是否正确

def traverse(graph):
    dq = list()  # 队列
    visit = set()  # 已访问的
    dq.extend(graph.node_map.keys())
    while len(visit) < graph.node_num:
        cur = dq.pop()
        if cur not in visit:
            node = graph.node_map[cur]
            val = node.data
            adj = node.adj
            print("点: ", val)
            if adj is None:
                pass
            else:
                while adj:
                    print(val, "->", adj.val, "(%d)"%adj.weight)
                    adj = adj.next_
            visit.add(cur)

调用方法看结果,打印出了所有的点和边,成功~

traverse(g1)
out:
点:  3
3 -> 10 (1)
3 -> 8 (1)
点:  8
8 -> 9 (1)
点:  10
点:  9
点:  2
点:  7
7 -> 11 (1)
7 -> 8 (1)
点:  11
11 -> 2 (1)
11 -> 10 (1)
11 -> 9 (1)
点:  5
5 -> 11 (1)

总结两句:

图的实现,大致是顶点列表+邻接关系,把点的集合和临接关系的集合存储好就可以,不必拘泥。临接关系用个list就很好,可以不用链表。

对于遍历,我们既然有顶点列表,用类似广度优先的方式遍历各点及边,倒是容易的很。


对于没有顶点列表的情况,往往只给出一个顶点(类似树 只给一个根节点),而且保证所有的点连通的(还有一些其他的限定条件),这时需要DFS、BFS遍历访问所有节点。同时也可以解决其他场景的问题。我们接下来结合LeetCode题目继续学习。

9.2.2 图的遍历LeetCode题目 —— Find the Town Judge & Clone Graph & Keys and Rooms

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值