图论:描述有限离散集合中元素间关系

2024-08-25 21:36

本文主要是介绍图论:描述有限离散集合中元素间关系,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

文章目录

    • 图的本质
    • 图论的起源一:七桥问题
      • 七桥问题的图模型
      • 图解七桥问题
      • 代码模拟解析
    • 图论的起源二:最短路径问题
      • 最短路径问题图模型
      • 图解最短路径问题
      • Dijkstra 算法
    • 图论的起源三:电路分析与基尔霍夫定律
      • 电路示意图
      • 电路计算示例
      • 代码模拟电流计算
    • 图论的起源四:地图染色问题
      • 地图染色问题图模型
      • 地图染色模拟
    • 结语

图论作为一种数学理论,旨在研究有限的离散集合中元素之间的关系,其核心在于通过图的结构,即点与线的组合,精确地描述和解析对象间的互动与联系。图的定义不仅是数学抽象,更具有丰富的应用场景。本文将结合计算机科学的视角,从图论的基本概念、实际生活中的应用以及配对问题的解决方案三个方面进行深入探讨,并通过 Python 代码实现相关模型。

在这里插入图片描述

图的本质

在图论中,图(Graph)由一组点(Vertex)和一组边(Edge)构成。我们可以将图表示为 G = ( V , E ) G = (V, E) G=(V,E),其中 V V V 是点的集合, E E E 是边的集合。根据不同特性,图可分为以下几类:

  • 无向图(Undirected Graph):边没有方向,表示两个点之间的双向关系。
  • 有向图(Directed Graph):边有方向,表示从一个点到另一个点的单向关系。
  • 加权图(Weighted Graph):边带有权重,表示元素间关系的强弱、距离、成本等。

图论的起源一:七桥问题

图论的起源可以追溯到柯尼斯堡的七桥问题。在 18 世纪,柯尼斯堡市(今俄罗斯的卡林格勒)有两座岛屿和两岸的桥梁,问题是是否能在不重复走过桥的情况下走遍所有桥(典型的图论问题)。这引发了欧拉提出图的定义和图论的开端。

七桥问题的图模型

我们可以将这些岛屿视为图的顶点(节点),而桥则被视为连接这些顶点的边。形成的图可以描述为:

  • 顶点(岛屿):A,B,C,D
  • 边(桥):AC,AC,AD,BC,BC,BD(总共七座桥)

构建七巧问题图示

我们将使用 Python 的networkxmatplotlib库来构建并可视化柯尼斯堡的七桥问题。

from collections import Counter
import networkx as nx
import matplotlib.pyplot as plt# 设置中文字体
plt.rcParams['font.sans-serif'] = ['Heiti TC']  # 使用黑体# 创建无向图(MultiGraph)
G = nx.MultiGraph()
# 添加所有的边(桥)
edges = [('A', 'C'), ('A', 'C'), ('A', 'D'), ('B', 'C'), ('B', 'C'), ('B', 'D'), ('C', 'D')]
G.add_edges_from(edges)# 绘制图
pos = nx.spring_layout(G)
nx.draw(G, pos, with_labels=True, node_color='lightblue', edge_color='gray', node_size=2000, font_size=15)# 用于显示多条边
edge_labels = Counter(edges)
nx.draw_networkx_edge_labels(G, pos, edge_labels=edge_labels)plt.title("柯尼斯堡七桥问题图示")
plt.show()

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

此处边的数字描述的是岛屿之间桥的数量,而不是权重。这个图模型可以帮助我们分析七桥问题的特性。

图解七桥问题

根据欧拉图的理论,能够在一个图中找到一条经过每条边一次且仅一次的路径(即欧拉路径),需要满足以下条件:

  1. 图是连通的。
  2. 顶点的度数(连接的边的数量)为奇数的顶点数目必须为零或两个。

代码模拟解析

尽管我们知道问题没有解决方案,但我们可以编写代码来分析图的特性,检验每个顶点度数的奇偶性。

# 计算每个顶点的度数
degree_dict = {node: G.degree(node) for node in G.nodes()}
for node, degree in degree_dict.items():print(f"顶点 {node}: {degree}{'偶数' if degree % 2 == 0 else '奇数'})")# 根据奇数顶点的数量判断是否存在欧拉路径或回路
odd_degree_count = sum(1 for degree in degree_dict.values() if degree % 2 != 0)if odd_degree_count == 0:print("图中存在欧拉回路。")
elif odd_degree_count == 2:print("图中存在欧拉路径。")
else:print("图中不存在欧拉路径或欧拉回路。")

柯尼斯堡的七桥问题中,每个顶点的度数如下:

  • 顶点 A: 3(奇数)
  • 顶点 C: 5(奇数)
  • 顶点 D: 3(奇数)
  • 顶点 B: 3(奇数)

由于所有顶点的度数都是奇数(> 2 个),因此不存在经过每条边一次的路径。这个结论对应于欧拉定理,即图中不存在欧拉路径或欧拉回路。

运行上述代码后,根据每个顶点的度数可以确认七桥问题没有解决方案。这揭示了图论的基本原理在现实问题中的重要性,说明了一些路径问题的内在限制。

图论的起源二:最短路径问题

最短路径问题是图论中一个经典的问题,其目标是在给定的图中,找到两个节点之间的最短路径。它广泛应用于交通网络、通信网络、物流规划等领域。

在一个有向图 G G G 中,包含一组顶点 V V V 和一组边 E E E,每条边都有一个权重(例如,距离、时间或成本)。给定起点 s s s 和终点 t t t,需要找出从 s s s t t t 的路径,使得路径中所有边的权重之和最小。

最短路径问题图模型

我们可以使用 networkxmatplotlib 来构建和可视化一个有向图,并标识出边的权重。

import networkx as nx
import matplotlib.pyplot as plt# 创建一个有向图
G = nx.DiGraph()# 添加边和权重
edges = [('A', 'B', 3), ('A', 'C', 6), ('B', 'C', 2), ('B', 'D', 1), ('C', 'D', 5)]
G.add_weighted_edges_from(edges)# 绘制图
pos = nx.spring_layout(G)
nx.draw(G, pos, with_labels=True, node_color='lightblue', edge_color='gray', node_size=2000, font_size=15)# 添加边权重标签
edge_labels = nx.get_edge_attributes(G, 'weight')
nx.draw_networkx_edge_labels(G, pos, edge_labels=edge_labels)plt.title("有向图及其边权重")
plt.show()

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图解最短路径问题

这里我们使用 Dijkstra 算法来解决最短路径问题。Dijkstra 算法与广度优先搜索(BFS)相似,但它会考虑边的权重,确保选择当前最小距离的节点进行扩展。

代码模拟解析

以下是使用 Dijkstra 算法计算从节点 1 到节点 4 的最短路径的实现:

import networkx as nx# 定义图
G = nx.DiGraph()
edges = [('A', 'B', 3), ('A', 'C', 6), ('B', 'C', 2), ('B', 'D', 1), ('C', 'D', 5)]
G.add_weighted_edges_from(edges)# 使用 Dijkstra 算法计算最短路径
source = 'A'
target = 'D'
shortest_path = nx.dijkstra_path(G, source, target)
shortest_distance = nx.dijkstra_path_length(G, source, target)print(f'从节点 {source} 到节点 {target} 的最短路径: {shortest_path}, 距离: {shortest_distance}')
# 从节点 A 到节点 D 的最短路径: `['A', 'B', 'D']`, 距离: `4`

使用 nx.dijkstra_path 找到从源节点到目标节点的最短路径,同时使用 nx.dijkstra_path_length 计算路径的总距离。

运行上述代码后,输出将显示从节点 A 到节点 D 的最短路径和距离。这种方法有效且简单,能够用于解决交通、网络等多种实际应用中的最短路径问题。

Dijkstra 算法

Dijkstra 算法是一种用于计算图中单源最短路径的有效算法,适用于加权图,但不能处理具有负权边的图。下面我们将通过给定的代码代码来逐步分析算法的实现过程。

import heapqclass DijkstraGraph:def __init__(self):self.graph = {}def add_edge(self, u, v, weight):if u not in self.graph:self.graph[u] = []self.graph[u].append((v, weight))  # 添加边 u 到 vif v not in self.graph:self.graph[v] = []self.graph[v].append((u, weight))  # 添加边 v 到 u(无向图)def dijkstra(self, start):min_heap = [(0, start)]  # 使用优先队列存储最小距离visited = set()  # 用于存储已访问的节点distances = {vertex: float('inf') for vertex in self.graph}  # 初始化距离distances[start] = 0  # 起点到自身距离为0while min_heap:current_distance, current_vertex = heapq.heappop(min_heap)  # 取出距离最小的节点if current_vertex in visited:continue  # 如果该节点已访问,跳过visited.add(current_vertex)  # 标记为已访问# 更新邻居节点的距离for neighbor, weight in self.graph[current_vertex]:distance = current_distance + weightif distance < distances[neighbor]:  # 如果发现更短的路径distances[neighbor] = distanceheapq.heappush(min_heap, (distance, neighbor))  # 将邻居加入优先队列return distances  # 返回所有节点的最短距离# 示例
g = DijkstraGraph()
g.add_edge('A', 'B', 3)
g.add_edge('A', 'C', 6)
g.add_edge('B', 'C', 2)
g.add_edge('B', 'D', 1)
g.add_edge('C', 'D', 5)# 输出从 A 到其他节点的最短路径
print(g.dijkstra('A'))
# {'A': 0, 'B': 3, 'C': 5, 'D': 4}

在示例中,我们创建了一个无向图并添加边:

  • 从节点 A 到 B 权重为 3
  • 从 A 到 C 权重为 6
  • 从 B 到 C 权重为 2
  • 从 B 到 D 权重为 1
  • 从 C 到 D 权重为 5

调用 g.dijkstra('A') 返回的结果:

  • 'A': 0:从 A 到 A 的距离为 0
  • 'B': 3:从 A 到 B 的最短距离为 3
  • 'C': 5:从 A 到 C 的最短距离为 5(A → B → C)
  • 'D': 4:从 A 到 D 的最短距离为 4(A → B → D)

通过这个示例例,可清晰看到 Dijkstra 算法如何有效地计算出各节点的最短路径。

数据结构

  • 图的表示:使用字典 self.graph 来表示图,键为节点,值为邻居节点及其权重的列表。
  • 优先队列:使用 heapq 实现优先队列,便于高效地提取当前距离最小的节点。

初始化

  • 所有节点的距离初始化为无穷大,起点的距离设为 0。

主循环

  • 使用最小堆 min_heap,在每次循环中提取出当前距离最小的节点。
  • 如果该节点已经被访问,继续下一个循环;否则标记为已访问。
  • 遍历当前节点的所有邻居,计算新路径的距离,并更新邻居的距离。

时间复杂度

  • Dijkstra 算法的时间复杂度为 O ( ( V + E ) log ⁡ V ) O((V + E) \log V) O((V+E)logV),其中 V V V 是顶点数, E E E 是边数。使用优先队列是主要的性能提升原因。

局限性

  • Dijkstra 算法不能处理负权边,因为它假设一旦找到到某个节点的最短路径,就不会再更新该节点的距离。但如果存在负权边,这个假设就不成立了。

图论的起源三:电路分析与基尔霍夫定律

基尔霍夫定律主要包括两条基本定律:基尔霍夫电压定律(KVL)和基尔霍夫电流定律(KCL)。这两条定律在电子电路分析中至关重要。

  • 基尔霍夫电压定律 (KVL):在一个闭合回路中,所有电压的代数和等于零。这意味着在回路中电源电压与消耗电压的总和相等。
  • 基尔霍夫电流定律 (KCL):在任何一个节点,流入节点的总电流等于流出节点的总电流。这反映了电流的保守性。

在电路设计与分析中,基尔霍夫定律可用于计算各元件的电压、电流并进行优化,以提高电路的性能。通过将电阻、电压源等元件抽象为图论模型,可以更直观地分析电路性能。

电路示意图

我们将使用 KCLKVL 进行电路分析。以一个简单的电路为例,该电路包含三个电阻(R1、R2、R3)和一个电压源(V)。

首先构建一个简单的电路图,并在节点上标注电流和电压。

import matplotlib.pyplot as plt
import networkx as nx# 创建图
G = nx.Graph()# 添加节点(电阻和电压源)
G.add_node('V', pos=(0, 1))  # 电压源
G.add_node('R1', pos=(0, 0))  # 电阻 R1
G.add_node('R2', pos=(1, 0))  # 电阻 R2
G.add_node('R3', pos=(1, 1))  # 电阻 R3# 添加电流路径
G.add_edges_from([('V', 'R1'), ('R1', 'R2'), ('R2', 'R3'), ('R3', 'V')])# 定义节点位置
pos = nx.get_node_attributes(G, 'pos')# 绘制图
# 使用 'spring' 布局算法,这种算法适合减少边的交叉
pos = nx.spring_layout(G, pos=pos, iterations=50)# 绘制图形
nx.draw(G, pos, with_labels=True, node_size=3000,node_color='lightblue', font_size=14)# 添加标题和隐藏坐标轴
plt.title("电路示意图")
plt.axis('off')  # 不显示坐标轴
plt.show()

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

电路计算示例

假设电路电压为 V = 10 V V = 10V V=10V,电阻值为 R 1 = 2 Ω R1 = 2 \Omega R1= R 2 = 3 Ω R2 = 3 \Omega R2= R 3 = 5 Ω R3 = 5 \Omega R3=。我们可以使用基尔霍夫定律计算通过每个电阻的电流。

在此假设中,电流从电压源流入,各电阻上电压降与电流的关系为:

  • 根据 KVL,电压下降的总和等于电压源。
  • I = V R I = \frac{V}{R} I=RV

代码模拟电流计算

# 定义电压和电阻值
V = 10  # 电压
R1 = 2  # 电阻 R1
R2 = 3  # 电阻 R2
R3 = 5  # 电阻 R3# 通过 Ohm 定律计算每个电阻上电流
I_R1 = V / R1
I_R2 = V / R2
I_R3 = V / R3print(f"电压源: {V}V")
print(f"通过 R1 的电流: {I_R1}A")
print(f"通过 R2 的电流: {I_R2}A")
print(f"通过 R3 的电流: {I_R3}A")# 电压源: 10V
# 通过 R1 的电流: 5.0A
# 通过 R2 的电流: 3.3333333333333335A
# 通过 R3 的电流: 2.0A
  • 电路图:通过使用 networkx 绘制电路示意图,清晰地展示电压源和电阻的位置以及连接关系。
  • 电流计算:根据基尔霍夫电流定律,计算每个电阻的电流。电流通过 Ohm 定律( I = V R I = \frac{V}{R} I=RV)来计算。由于电流是通过不同电阻流动的,可能会根据电路设计有所变化,但在简单的串联电路中,电流是相等的。

这种图形化的方法帮助理解电路内部关系,便于优化和解决实际电气工程问题。

图论的起源四:地图染色问题

地图染色问题是图论中的经典问题,其目标是在图中为节点(代表地图上的区域)着色,使得相邻的节点(代表共享边界的区域)使用不同的颜色。问题的关键在于使用最少的颜色并确保没有两个相邻的区域有相同的颜色。

  • 应用:地图染色问题在实际中广泛应用于地理、图像处理、调度和资源分配等领域。例如,在制定选区时,需要确保相邻的选区不使用相同的颜色以避免冲突。
  • 理论:根据四色定理,在任何平面地图上,最多只需要四种颜色就可以完成染色。

地图染色问题图模型

我们将使用 networkxmatplotlib 绘制一个图以表示一个简单的地图,然后用贪心算法为这个图着色。

首先,我们创建一个简单的图来表示地图。

import networkx as nx
import matplotlib.pyplot as plt# 创建图
G = nx.Graph()# 添加节点(区域)
regions = ['A', 'B', 'C', 'D', 'E']
G.add_nodes_from(regions)# 添加边(相邻区域)
edges = [('A', 'B'), ('A', 'C'), ('B', 'C'), ('B', 'D'), ('C', 'D'), ('D', 'E')]
G.add_edges_from(edges)# 绘制图
pos = nx.spring_layout(G)
nx.draw(G, pos, with_labels=True, node_color='lightgray', edge_color='gray', node_size=2000)
plt.title("地图图示")
plt.show()

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

地图染色模拟

接下来,我们使用贪心算法为这个图染色。

def greedy_coloring(graph):color_map = {}# 获取所有节点nodes = list(graph.nodes)for node in nodes:# 获取相邻节点的颜色neighbor_colors = {color_map.get(neighbor) for neighbor in graph.neighbors(node)}# 找到第一个可用颜色color = 0while color in neighbor_colors:color += 1color_map[node] = colorreturn color_map# 应用贪心算法进行染色
coloring = greedy_coloring(G)# 打印染色结果
print("染色结果:", coloring)# 绘制结果
color_values = [coloring[node] for node in G.nodes]
nx.draw(G, pos, with_labels=True, node_color=color_values, edge_color='gray', node_size=2000, cmap=plt.cm.rainbow)
plt.title("地图染色结果")
plt.show()
# 染色结果: {'A': 0, 'B': 1, 'C': 2, 'D': 0, 'E': 1}

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图的构建

我们创建一个无向图来表示区域(节点)及其相邻关系(边)。每个区域通过边连接。

贪心染色算法

  • 遍历每个节点,并找到所有相邻节点的颜色。
  • 选择最小的可用颜色来为当前节点上色。
  • 利用集合操作避免冲突,确保没有相邻节点使用同一颜色。

通过运行上述代码,我们可以获得每个区域的染色情况,并可以验证相邻区域的颜色是不同的。该模型展示了简单的贪心算法如何有效解决地图染色问题。

这种方法可以扩展到更复杂的地图或使用更多颜色进行优化,进一步探讨图论中的染色理论对于实际应用的与可能的改进。

结语

通过对图论的基本概念的探讨与来源追溯,我们在计算机科学的视角下,对图论的基本概念、实际应用进行了深入分析,结合 Python 代码实现加深对图论理论的理解。

图论经过了上百年的发展,已经成为解决数据问题和计算机科学问题的重要工具。图论的核心是对图的理解与应用,而图的本质就是对离散的、有限集合中各个元素之间关系的描述。通过图的结构,我们可以更好地理解和解决现实生活中的问题。


PS:感谢每一位志同道合者的阅读,欢迎关注、点赞、评论!


  • 上一篇:布隆过滤器:大数据量下的快速存在性判断
  • 专栏:「数智通识」

这篇关于图论:描述有限离散集合中元素间关系的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1106712

相关文章

CSS3中使用flex和grid实现等高元素布局的示例代码

《CSS3中使用flex和grid实现等高元素布局的示例代码》:本文主要介绍了使用CSS3中的Flexbox和Grid布局实现等高元素布局的方法,通过简单的两列实现、每行放置3列以及全部代码的展示,展示了这两种布局方式的实现细节和效果,详细内容请阅读本文,希望能对你有所帮助... 过往的实现方法是使用浮动加

C#比较两个List集合内容是否相同的几种方法

《C#比较两个List集合内容是否相同的几种方法》本文详细介绍了在C#中比较两个List集合内容是否相同的方法,包括非自定义类和自定义类的元素比较,对于非自定义类,可以使用SequenceEqual、... 目录 一、非自定义类的元素比较1. 使用 SequenceEqual 方法(顺序和内容都相等)2.

MYSQL关联关系查询方式

《MYSQL关联关系查询方式》文章详细介绍了MySQL中如何使用内连接和左外连接进行表的关联查询,并展示了如何选择列和使用别名,文章还提供了一些关于查询优化的建议,并鼓励读者参考和支持脚本之家... 目录mysql关联关系查询关联关系查询这个查询做了以下几件事MySQL自关联查询总结MYSQL关联关系查询

在MyBatis的XML映射文件中<trim>元素所有场景下的完整使用示例代码

《在MyBatis的XML映射文件中<trim>元素所有场景下的完整使用示例代码》在MyBatis的XML映射文件中,trim元素用于动态添加SQL语句的一部分,处理前缀、后缀及多余的逗号或连接符,示... 在MyBATis的XML映射文件中,<trim>元素用于动态地添加SQL语句的一部分,例如SET或W

基于Redis有序集合实现滑动窗口限流的步骤

《基于Redis有序集合实现滑动窗口限流的步骤》滑动窗口算法是一种基于时间窗口的限流算法,通过动态地滑动窗口,可以动态调整限流的速率,Redis有序集合可以用来实现滑动窗口限流,本文介绍基于Redis... 滑动窗口算法是一种基于时间窗口的限流算法,它将时间划分为若干个固定大小的窗口,每个窗口内记录了该时间

uva 11178 计算集合模板题

题意: 求三角形行三个角三等分点射线交出的内三角形坐标。 代码: #include <iostream>#include <cstdio>#include <cstdlib>#include <algorithm>#include <cstring>#include <cmath>#include <stack>#include <vector>#include <

POJ1269 判断2条直线的位置关系

题目大意:给两个点能够确定一条直线,题目给出两条直线(由4个点确定),要求判断出这两条直线的关系:平行,同线,相交。如果相交还要求出交点坐标。 解题思路: 先判断两条直线p1p2, q1q2是否共线, 如果不是,再判断 直线 是否平行, 如果还不是, 则两直线相交。  判断共线:  p1p2q1 共线 且 p1p2q2 共线 ,共线用叉乘为 0  来判断,  判断 平行:  p1p

pip-tools:打造可重复、可控的 Python 开发环境,解决依赖关系,让代码更稳定

在 Python 开发中,管理依赖关系是一项繁琐且容易出错的任务。手动更新依赖版本、处理冲突、确保一致性等等,都可能让开发者感到头疼。而 pip-tools 为开发者提供了一套稳定可靠的解决方案。 什么是 pip-tools? pip-tools 是一组命令行工具,旨在简化 Python 依赖关系的管理,确保项目环境的稳定性和可重复性。它主要包含两个核心工具:pip-compile 和 pip

遮罩,在指定元素上进行遮罩

废话不多说,直接上代码: ps:依赖 jquer.js 1.首先,定义一个 Overlay.js  代码如下: /*遮罩 Overlay js 对象*/function Overlay(options){//{targetId:'',viewHtml:'',viewWidth:'',viewHeight:''}try{this.state=false;//遮罩状态 true 激活,f

学习记录:js算法(二十八):删除排序链表中的重复元素、删除排序链表中的重复元素II

文章目录 删除排序链表中的重复元素我的思路解法一:循环解法二:递归 网上思路 删除排序链表中的重复元素 II我的思路网上思路 总结 删除排序链表中的重复元素 给定一个已排序的链表的头 head , 删除所有重复的元素,使每个元素只出现一次 。返回 已排序的链表 。 图一 图二 示例 1:(图一)输入:head = [1,1,2]输出:[1,2]示例 2:(图