图搜索BFS算法及存储优化

图搜索BFS算法及存储优化

1 实验目的

  • 理解图的存储方式,包括邻接矩阵和邻接表,并能够选择适当的存储方式。
  • 学习并实现图的广度优先搜索算法(BFS)。
  • 通过实验测试,比较不同数据规模下的BFS算法的性能差异。

2 实验要求

  • 根据给定的数据选择合适的存储方式(邻接矩阵和邻接表中的一种)进行图的存储。
  • 对图进行广度优先遍历(BFS)。
  • 对数据集1进行实验:
    • 使用给定的数据集data.txt,将其看作无向图。
    • 根据图的特征(节点数较少而边比较密集),选择合适的存储方式。
    • 以节点A为起始顶点进行广度优先遍历,输出遍历过程。
  • 对数据集2进行实验:
    • 使用给定的数据集twitter_small。
    • 选择一种合适的存储方式存储数据。
    • 输出BFS的遍历时间。
  • 编写实验报告,包括实验内容、实验目的、算法设计思路、源码及注释、算法测试结果、实验过程中的困难和收获。
  • 注意:对于数据集2的实验,可以根据实际情况选择邻接矩阵或邻接表作为存储方式,并记录BFS的遍历时间。

3 实验内容

3.1 宽度优先搜索(BFS)

宽度优先搜索BFS算法的具体步骤如下:

  • 初始化:
    • 创建一个队列,用于存储待访问的节点。
    • 创建一个标记数组,用于标记节点是否已经访问过。
    • 将起始节点加入队列,并将起始节点标记为已访问。
  • 进入循环,直到队列为空:
    • 从队列中取出队首节点。
    • 输出或处理该节点。
  • 遍历当前节点的邻居节点:
    • 对于邻接矩阵中的行或列,检查与当前节点相连的节点。
    • 如果某个节点与当前节点相连且未被访问过,则将该节点加入队列,并将其标记为已访问。
  • 重复步骤2和步骤3,直到队列为空。

以下是伪代码表示的邻接矩阵的BFS算法过程

BFS(graph, start_node):
    queue = new Queue()
    visited = new array of size graph.size initialized with false
    queue.enqueue(start_node)
    visited[start_node] = true
    
    while queue is not empty:
        current_node = queue.dequeue()
        print(current_node)
        
        for each neighbor in graph[current_node]:
            if neighbor is not visited:
                queue.enqueue(neighbor)
                visited[neighbor] = true


3.2 邻接矩阵存储

对于数据集1,该数据集构成的图的边比较稠密,所以我们采用邻接矩阵来存储该图结构,存储及BFS过程如下:

# 使用邻接矩阵表示图
class GraphMatrix:
    def __init__(self, size):
        # 初始化邻接矩阵,所有元素设为0
        self.adjacencyMatrix = [[0] * size for _ in range(size)]
        self.nodes = []

    # 添加双向边
    def add_edge_matrix(self, from_, to):
        self.adjacencyMatrix[from_][to] = 1
        self.adjacencyMatrix[to][from_] = 1

    # 添加节点
    def add_node(self, node):
        self.nodes.append(node)
        
    # 广度优先搜索
    def bfs_matrix(self, start_node):
        # 初始化访问标记数组
        visited = [False] * len(self.adjacencyMatrix)
        # BFS队列
        queue = []

        # 标记起始节点为已访问并入队
        visited[start_node] = True
        queue.append(start_node)

        while queue:
            current_node = queue.pop(0)
            print(self.nodes[current_node], end=' ')

            # 遍历邻接矩阵中的每个节点
            for i in range(len(self.adjacencyMatrix)):
                if self.adjacencyMatrix[current_node][i] == 1 and not visited[i]:
                    visited[i] = True
                    queue.append(i)

3.3 邻接表存储

对于数据集2,图的节点数较多而边的数量相对较少,邻接表的存储方式更节省空间。而且对于大数据集来说,邻接表在存储稀疏图时比邻接矩阵更高效,在进行图的遍历操作时具有较高的效率,插入和删除节点或边的操作上具有更好的动态性能。

建立邻接表并存储图结构及BFS过程如下::

# 使用邻接表表示图
class GraphAdjacency:
    def __init__(self):
        self.adjacency_list = {}

    # 添加边到邻接表
    def add_edge_adjacency(self, from_, to):
        if from_ not in self.adjacency_list:
            self.adjacency_list[from_] = set()
        self.adjacency_list[from_].add(to)

    # BFS搜索并返回访问的节点数
    def BFS_adjacency(self, start_node):
        visited = set()
        queue = []
        queue.append(start_node)
        visited.add(start_node)
        cnt = 0

        while queue:
            current_node = queue.pop(0)
            cnt += 1
            if current_node in self.adjacency_list:
                for neighbor in self.adjacency_list[current_node]:
                    if neighbor not in visited:
                        queue.append(neighbor)
                        visited.add(neighbor)
        return cnt

在实验过程中我们发现,任意选取一个点可能无法遍历到有向图的所有节点,因为有的图可能只有入度没有出度,我们经过对比实验发现并找到一个结点,验证了有向图是连通的,因为我们找到了一个点,从这个点开始广度优先遍历可以一次性遍历到全部的81306个结点。这个点的编号是54226675。

4 实验结果

  • 运行数据集1的函数
# 数据集1 使用邻接矩阵存储 节点少边多 稠密图
def graph_matrix_test():
    graph = GraphMatrix(9)  # 初始化邻接矩阵
    with open('data.txt', 'r') as file:
        for line in file:
            from_, to = line[0], line[2]
            if from_ not in graph.nodes:
                graph.add_node(from_)
            if to not in graph.nodes:
                graph.add_node(to)
            graph.add_edge_matrix(graph.nodes.index(from_), graph.nodes.index(to))
    start_node = graph.nodes.index('A')
    print("GraphMatrix BFS Traversal:")
    graph.bfs_matrix(start_node)
    print()
    
  • 运行数据集2的函数
# 数据集2 使用邻接表存储 节点多 边少 稀疏图
def graph_adjacency_test():
    from_, to = 0, 0
    max_node_num = 81306
    graph = GraphAdjacency()
    start_time = time.time()
    with open('twitter_small.txt', 'r') as file:
        for line in file:
            from_, to = map(int, line.split())
            graph.add_edge_adjacency(from_, to)
    duration_file = (time.time() - start_time) * 1000
    print(f"读取文件执行时间:{duration_file} 毫秒")
    start_node = 54226675
    start_time = time.time()
    cnt = graph.BFS_adjacency(start_node)
    print(f"The number of nodes visited by BFS: {cnt}")
    duration_bfs = (time.time() - start_time) * 1000
    print(f"宽度优先搜索执行时间:{duration_bfs} 毫秒")


  • 实验结果

在这里插入图片描述

5 实验总结

通过本次实验,我获得了以下收获:

  • 图的存储方式选择:学会根据图的特征和需求选择适当的存储方式。对于节点较少而边比较密集的图,邻接矩阵可能更合适;对于稀疏图,邻接表则更适合。
  • 广度优先搜索算法(BFS):掌握了BFS算法的实现和应用。该算法可以按照广度优先的顺序遍历图中的节点,对于寻找最短路径等问题很有用。
  • 数据集处理能力:通过处理不同规模的数据集,我提升了对大规模数据的处理能力。了解了如何选择合适的存储方式,并记录了算法执行时间以评估性能。
  • 在遍历数据集2的过程中,我学到了应该通过大量实验去寻找遍历的起始节点,尽可能完全遍历到图的所有节点,有向图的遍历相对与无向图的处理稍微复杂些
    合。
  • 广度优先搜索算法(BFS):掌握了BFS算法的实现和应用。该算法可以按照广度优先的顺序遍历图中的节点,对于寻找最短路径等问题很有用。
  • 数据集处理能力:通过处理不同规模的数据集,我提升了对大规模数据的处理能力。了解了如何选择合适的存储方式,并记录了算法执行时间以评估性能。
  • 在遍历数据集2的过程中,我学到了应该通过大量实验去寻找遍历的起始节点,尽可能完全遍历到图的所有节点,有向图的遍历相对与无向图的处理稍微复杂些
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值