图论—简化图数据结构和遍历算法

图论

图形数据结构和遍历算法变得简单

计算机软件中的图形与高中的条形图略有不同。 当然,它们仍然是关系的映射,只是以不同的方式表示 。 图实际上可以帮助解决很多问题。 它们可以用于解决社交网络中的问题,例如。 查找朋友之间的关系或在GPS导航中查找从您的房屋到最近的购物中心的最佳路线。 例如,在机器人技术和AI中经常使用图形,有时用于保持允许机器人进入的所有可能状态(这样它们就不会摔坏东西或穿过墙壁移动)。 它们非常适合安排问题,例如何时安排交通流量(可以通过图形着色解决)。 清单还在继续,他们解决了许多现实世界中的问题

图形表示

了解图形的基本概念非常重要,即使它可能很无聊,但从长远来看,它可以使您获得十倍的回报。 所以我将从头开始。

什么是图? 好吧,上面有一点点,对吧? 哦,是的..您以某种方式将这些点连接在一起...瞧,您有一个图形!

这些要点是什么? 它们称为顶点节点 。 我将使用节点术语来保持一致性。

这些线叫什么? 它们称为

好的,很酷,因此我们得到的是图形 。.只是节点和边以某种方式连接! 也许看起来像这样:

其他有助于定义图形的属性:

  • 有向- 边缘仅指向单向,因此有向图的一个示例可能是仅包含可以向下行驶的单向街道的路线图。
  • 无方向- 边缘指向两个方向,您可以上下移动。
  • 加权-加权图具有沿特定边缘移动的某种成本 。 例如。 30分钟即可到达Y街。
  • 未加权-与边缘无关的成本
  • 非循环的-您将永远不会两次遇到相同的节点。
  • 已连接-图中的所有节点都以某种方式连接。 如果该图是一个物理模型,则可以将其拾取,并且由于它是连接图,因此不会崩溃。
  • 断开连接-该图由子图组成,或者是二部图

我们如何用代码表示这一点? 我的答案- 很多方式 。 它实际上取决于有关如何表示图形的问题 。 这是我的两个必去之地。

邻接矩阵

邻接矩阵用于表示有限图。 重要的是要注意,如果您的问题是处理连续空间,那么有更好的选择来表示图形。 那么,邻接矩阵是什么? 它本质上只是一个带有1和0的矩阵。 它可以是稀疏的(大多数填充为0),并且每一行和关联的列都是图的一个节点 ,它可能看起来像这样:

这是一个N * N或“ N by N”矩阵。 如您所见,数字0…至9在列上,然后是0…9在行上。 这涵盖了图中节点关系的每种组合。 例如,我们要将节点号“ 0”与节点号“ 9”连接起来,然后查找第0行并找到第9列,并在矩阵中写一个“ 1”以表示存在一条 0至9。

所以现在我们有了一个有向边,看起来像0-> 9的关系。如果我们想要一个无向的关系,那么我们必须查询第9行和第0列,然后在矩阵中写一个“ 1”以表示存在一个边在9和0之间。现在,无向边看起来像是0 <-> 9,使其成为双向的(在两个方向上都起作用)。

但是为什么要使用邻接矩阵? 真该死的查找速度 。 可以通过简单地查询矩阵的索引来直接查询该图以查看它是否在两个节点之间有连接。 matrix [i] [j],其中i是行,j是列。 这在O(1)恒定时间执行,因为您不会浪费时间在整个矩阵中搜索所需的内容。

不可能都很好吗? 不是。 如果您有空间意识,则邻接矩阵会占用大量空间。 确切地说是O(V2),其中V是图中的节点数。 这仅仅是因为它存储了节点之间边缘的所有可能组合。 即使它们为0(表示2个节点之间没有边)。 查找必须搜索图中所有节点的相邻节点还需要花费额外的时间。 不是很有效。

如果图中的节点数量相对较小并且您需要快速访问时间,那么我会选择一个邻接矩阵。

邻接表

这些是我的最爱。 邻接表用于表示有限图。 它们本质上只是所有节点列表 。 但是列表中的每个节点都维护一个指向其所有相邻节点的列表的指针。 像这样的东西

使用Hashtable可以轻松实现这一点! 如果你还没有看过我的职位上哈希表,检查它的详细信息,他们的引擎盖下是如何工作的。 现在……我们如何使用哈希表来实现呢? 简单。 一种方法是使用面向对象的方法,并将图形的节点表示为对象。 然后将每个Node对象作为键插入到Hashtable中,并将相邻Node对象的列表作为值插入。 (邻居列表可以为空)。

好吧..为什么这些好? 邻接表是很好的图形表示,因为可以在恒定的时间 O(1)中检索节点的所有邻居。 我们如何将其转化为现实问题? 嗯...

也许在社交网络中找到某人“ X”的所有朋友。 考虑这个问题……可以用一个Person对象替换一个Node对象,并且该Person对象的属性可以是名称,dob,age等。然后我们可以为人员“ X”查找哈希表,然后返回一个所有Person对象的列表,也就是他们社交圈中的所有朋友。

图搜索

我们已经简要介绍了如何实际表示图形。 现在,如果我们想使用该图怎么办? 时间到了我的朋友。 我们可以搜索答案。

这个主题非常广泛,搜索方法很多。 我将介绍一些流行的方法,希望也能介绍一些更高级的搜索方法。 需要注意的一件事是,我们总是希望以某种目标状态告终,因此要找到“目标节点”。 但是,要做到这一点,我们必须从某个“初始节点”开始,并经过一些路径才能达到目标。 在图世界中,“初始”节点也可以称为“根”节点。 我将交替使用“ root”和“ initial”。

DFS

DFS或“深度优先搜索”是一种用于搜索图形的方法。 它从根节点开始搜索,选择一个相邻节点,然后开始探索,直到所有连接的节点都被探索为止。 您可以将其视为沿着一棵树下去,探索所有子节点,直到它撞到树的底部。 然后返回,并选择下一个相邻节点,并重复向下扩展树的过程。 但是,如果图形中有数百万个节点并且正在寻找目标节点该怎么办? 嗯,这可能是个问题,尤其是在分支因子很大的情况下。 但是,如果目标节点在树中很深,它也可以很好地工作。 很难说,通常取决于问题。

实施DFS? 使用堆栈! DFS的伪代码如下:

DFS(Graph, root, goal): let S be a stack S.push(root) while S is not empty node = S.pop() if node is the goal: return node if node is not labeled as discovered: label node as discovered for all edges from node to neighbour in Graph.adjacentEdges(node) do S.push(neighbour)

BFS

BFS或“广度优先搜索”是用于搜索图形的另一种方法。 在大多数情况下,这是使用DFS的首选,因为它可以按“级别”浏览图形。 本质上,它的搜索更为均匀。 如果目标节点稍微靠近根节点,那么BFS可能比DFS更好。 BFS如何做事? 它从根节点开始搜索,扩展第一级中的所有邻居(那些直接连接到根的邻居)。 然后,它选择一个邻居,并扩展其第一级中的所有节点。 之后,它返回并选择下一个连接的节点/邻居,并在其第一级扩展其所有节点。 重复该过程,直到最终找到目标节点。

实施BFS? 使用队列! BFS的伪代码如下:

BFS(Graph, root, goal): create empty set S create empty queue Q root.parent = null add root to S Q.enqueue(root) while Q is not empty: current = Q.dequeue() if current is the goal: return current for each node n that is neighbour of current: if n is not in S: add n to S n.parent = current Q.enqueue(n)

DFS和BFS都盲目地搜索目标节点,因此被分类为“不知情”搜索方法。 要补充的另一件事是,通过视觉表示来理解这些算法的工作方式非常有帮助。 这样,您可以了解算法的工作原理,从而更轻松地在代码中表示。 请查看以下两个链接:

Youtube剪辑展示DFS Youtube剪辑展示BFS

…好吧,现在的问题是..我们能比BFS或DFS做得更好吗? 如果有一种方法可以“通知”我们的搜索,并且扩展到达目标节点的最佳路径中的节点,那该怎么办...

A *搜索

A *搜索是Dijkstra算法的扩展,该算法用于获取两个节点之间的最短路径。 是什么使A *与众不同? 它使用启发式方法来指导其在目标节点方向上的搜索。 为了使我们的A *搜索能够按预期工作,那么我们必须具有可以接受的启发式方法。 这意味着永远不要高估到达目标节点的成本,如下所示-h(n)≤h *(n),其中h *(n)是从n开始达到目标的成本。

它将成本函数应用于当前节点的每个相邻节点。 如果我们要开始A *搜索,则当前节点将是初始开始节点。 然后它将成本函数应用于其每个相邻节点。 A *中的成本函数表示为f(n)= g(n)+ h(n),其中f(n)是到达目标节点的总估计成本,g(n)是获得的估计成本h(n)是我们的启发式算法,用于估算从n到达目标节点的成本。

实施A *搜索? 使用优先队列! A *的伪代码如下:

A*(Graph, root, goal): initial = root create empty PriorityQueue Q Q.add(initial) While Q is not empty: current = Q.remove() label current as visited if current is the goal: return current for each node n that is neighbour of current: g(n) = g(current) + cost(n) if(n) = g(n) + h(n) Q.add(n, f(n)) set node to be child of current While Q is not empty: current = Q.remove() label current as visited if current is the goal: return current for each node n that is neighbour of current: g(n) = g(current) + cost(n) if(n) = g(n) + h(n) Q.add(n, f(n)) set node to be child of current

当我们从Priority Queue中调用remove()时,我们假设它删除具有最小f值的节点,该是使用成本函数f(n)计算的。 您可能想知道这种启发式方法从何而来? 请记住,它是用来计算f(n)的。 要注意的一件事是,如果h(n)= 0,则f(n)仅为g(n),这只是到达节点的估计成本。 然后,该算法恢复为Dijkstra的算法,从而保证了最短的路径。 如果h(n)是到达目标节点的确切成本,则A *将永远不会扩展节点,而不是到达目标节点的最佳路径。 使算法运行起来非常快。

因此,为了使A * 最优 ,其启发式h(n)绝不能高估达到目标节点的成本,并且……要保持一致

什么是一致的启发式? 如果从当前节点到相邻节点的成本加上相邻节点的h值小于或等于当前节点的h值,则启发式方法是一致的。 成本(当前,邻居)+ h(邻居)≤h(n)WTF是什么意思? 无论您从哪个节点开始,该解决方案都将始终提供最短的路径

如果启发式失败,会发生什么? (它高估了目标的成本)。 好吧……那么您的算法将错误判断实际上值得扩展的节点。 并没有表现最佳。

我们也可以使用A *在连续空间中进行搜索,在连续空间中,图形的数量是无休止的。 可以将图形离散化,并使用A *在离散化版本中进行搜索以找到目标节点(或状态)。

加起来

图可以以多种方式构造并用于解决许多问题。 考虑使用图形时,一个好的做法是对问题进行建模,并尝试使其适应图形方案。 合适吗? 如果是,那就太棒了! 使用图表。 有许多技术可用于构建您的解决方案,并且几乎每种编程语言在其标准库中都有可用的数据结构。 关于图,总是有很多东西要学习,我希望这篇文章能激发您对图的兴趣!

最初发布于 zeroequalsfalse.press

From: https://hackernoon.com/graph-theory-graph-data-structures-and-traversal-algorithms-made-easy-28c7154c9662

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
算法数据结构它们分别涵盖了以下主要内容: 数据结构(Data Structures): 逻辑结构:描述数据元素之间的逻辑关系,如线性结构(如数组、链表)、树形结构(如二叉树、堆、B树)、结构(有向、无向等)以及集合和队列等抽象数据类型。 存储结构(物理结构):描述数据在计算机中如何具体存储。例如,数组的连续存储,链表的动态分配节点,树和的邻接矩阵或邻接表表示等。 基本操作:针对每种数据结构,定义了一系列基本的操作,包括但不限于插入、删除、查找、更新、遍历等,并分析这些操作的时间复杂度和空间复杂度。 算法算法设计:研究如何将解决问题的步骤形式化为一系列指令,使得计算机可以执行以求解问题。 算法特性:包括输入、输出、有穷性、确定性和可行性。即一个有效的算法必须能在有限步骤内结束,并且对于给定的输入产生唯一的确定输出。 算法分类:排序算法(如冒泡排序、快速排序、归并排序),查找算法(如顺序查找、二分查找、哈希查找),图论算法(如Dijkstra最短路径算法、Floyd-Warshall算法、Prim最小生成树算法),动态规划,贪心算法,回溯法,分支限界法等。 算法分析:通过数学方法分析算法的时间复杂度(运行时间随数据规模增长的速度)和空间复杂度(所需内存大小)来评估其效率。 学习算法数据结构不仅有助于理解程序的内部工作原理,更能帮助开发人员编写出高效、稳定和易于维护的软件系统。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值