手把手:四色猜想、七桥问题…程序员眼里的图论,了解下?

本文将引导程序员深入理解图论的基础概念,通过探讨四色猜想和七桥问题等经典案例,揭示图论在解决实际问题中的应用。无论你是算法爱好者还是寻求提升编程思维,这篇文章都将为你提供宝贵的洞见。
摘要由CSDN通过智能技术生成

大数据文摘作品

编译:张礼俊、王一丁、xixi、修竹、Apricock、惊蛰、Chloe、龙牧雪


长文预警!本文作者Vardan Grigoryan是一名后端程序员,但他认为图论(应用数学的一个分支)的思维应该成为程序员必备。


本文从七桥问题引入,将会讲到图论在Airbnb房屋查询、推特推送更新时间、Netflix和亚马逊影片/商品个性化推荐、Uber寻找最短路线中的应用,附有大量手把手代码和手绘插图,值得收藏


图论的傻瓜式教程


图论是计算机科学中最重要、最有趣,同时也是最容易被误解的领域之一。理解并使用图论有助于我们成为更好的程序员。图论思维应该成为我们的思维方式之一。


先看一下枯燥的定义……图是一组节点V和边E的集合,包括有序对G =(V,E)……��


在试图研究理论并实现一些算法的同时,我发现自己经常因为这些理论和算法太过艰深而被卡住……


事实上,理解某些东西的最好方法就是应用它。我们将展示图论的各种应用,及其详细的解释。


对于经验丰富的程序员来说,下面的描述可能看起来过于详细,但相信我,作为一个过来人,详细的解释总是优于简洁的定义的。


所以,如果你一直在寻找一个“图论的傻瓜式教程”,你看这篇文章就够了。



免责声明

免责声明1:我不是计算机科学/算法/数据结构(特别是图论方面)的专家。 我没有参与本文谈及的公司的任何项目。本文问题的解决办法并非完美,也有改进的空间。 如果你发现任何问题或不合理的地方,欢迎你发表评论。 如果你在上述某家公司工作或参与相应的软件项目,请提供实际解决方案。请大家耐心阅读,这是一篇很长的文章。


免责声明2:这篇文章在信息表述的风格上与其它文章有所不同,看起来可能图片也与其所在部分的主题不是十分契合,不过耐心的话,相信最终会发现自己对图片有完整的理解。


免责声明3:本文主要是将初级程序员作为受众而编写的。在考虑目标受众的同时,但更专业的人员也将通过阅读本文有所发现。


哥尼斯堡的七桥问题


让我们从经常在图论书中看到的“图论的起源”开始——哥尼斯堡七桥问题


故事发生在东普鲁士的首都哥尼斯堡(今俄罗斯加里宁格勒),普莱格尔河(Pregolya)横贯其中。十八世纪在这条河上建有七座桥,将河中间的两个岛和河岸联结起来。


现在这个地区变成这样啦


题是如何才能不重复,不遗漏地一次走完七座桥,最后回到出发点。他们当时没有互联网,所以就得找些问题来思考消磨时间。以下是18世纪哥尼斯堡七座桥梁的示意图。



你可以尝试一下,用每座桥只通过一次的方式来走遍整个城市。“每座桥”意味着不应该有没有通过的桥。“只有一次”意味着不得多次通过每座桥。如果你对这个问题很熟悉,你会知道无论怎么努力尝试这都是不可能的;再努力最终你还是会放弃。


Leonhard Euler(莱昂哈德·欧拉)


有时,暂时放弃是合理的。欧拉就是这样应对这个问题的。虽然放弃了正面解决问题,但是他选择了去证明“每座桥恰好走过并只走过一遍是不可能的”。让我们假装“像欧拉一样思考”,先来看看“不可能”。



图中有四块区域——两个岛屿和两个河岸,还有七座桥梁。先来看看连接岛屿或河岸的桥梁数量中的模式(我们会使用“陆地”来泛指岛屿和河岸)。


每块区域连接桥梁的数量


只有奇数个的桥梁连接到每块陆地。这就麻烦了。


两座桥与陆地的例子


在上面的插图中很容易看到,如果你通过一座桥进入一片陆地,那你可以通过走第二座桥离开这片陆地。


每当出现第三座桥时,一旦穿过桥梁进入,那就没法走上每座桥且只走一次离开了。如果你试图将这种推理推广到一块陆地上,你可以证明,如果桥梁数量是偶数的话,总有办法离开这块陆地,但如果桥梁数量是奇数,就不能每桥走且只走一次的离开。试着记住这个结论。


如果添加一个新的桥梁呢?通过下面这张图,我们可以观察各个陆地所连接的桥的数量变化及其影响。


注意新出现的桥


现在我们有两个偶数和两个奇数。让我们画一条包括了新桥梁的路线。


Wow


桥梁的数量是偶数还是奇数至关重要。现在的问题是:我们知道了桥梁数量是否就能够断定问题可不可解?为了解决问题桥的数量必须是偶数吗?欧拉找到了一种方法证明这个问题。而且,更有趣的是,具有奇数个桥梁连接的陆地数量也很重要。


欧拉开始将陆地和桥梁“转换”成我们所知道的图。以下是代表哥尼斯堡七桥的图的样子(请注意,我们“暂时”增加的桥并不在图中):




需要特别注意的一点是对问题的概括和抽象。无论何时你解决一个具体的问题,最重要的是归纳类似问题的解决方案。在这个例子中,欧拉的任务是归纳桥梁问题,以便能在未来推广到类似问题上,比如说世界上所有的桥梁问题。可视化还有助于以不同视角来看问题。下面的每张图都表示前面描述的桥梁问题:



所以说图形化可以很好地描绘问题,但我们需要的是如何使用图来解决哥尼斯堡问题。观察从圆圈中出来的线的数量。从专业角度而言,我们将称之为“节点”(V),以及连接它们的“边”(E)。V代表节点(vertex),E代表边(edge)。



下一个重要的概念就是所谓的节点自由度,即入射(连接)到节点的边的数量。在上面的例子中,与陆地连结的桥的数目可以视为图的节点自由度。



欧拉证明了,一次并仅有一次穿过每条边(桥)来遍历图(陆地)严格依赖于节点(陆地)自由度。由这些边组成的路径叫做欧拉路径。欧拉路径的长度是边的数量。


有限无向图G(V,E)的欧拉路径是一条使G的每条边出现并且只出现一次的路径。如果G有一条欧拉路径,那么就可以称之为欧拉图。


定理:一个有限无向连通图是一个欧拉图,当且仅当只有两个节点有奇数自由度或者所有节点都有偶数自由度。在后一种情况下,曲线图的每条欧拉路径都是一条闭环,前者则不是。



左图正好有两个节点具有奇数自由度,右图则是所有节点都是奇数自由度


首先,让我们澄清上述定义和定理中的新术语。


有限图是具有有限数量的边和节点的图。


图可以是有向图也可以是无向图,这是图的有趣特性之一。举个非常流行的关于有向图和无向图的例子,Facebook vs. Twitter。Facebook的友谊关系可以很容易地表示为无向图,因为如果Alice是Bob的朋友,那么Bob也必须是Alice的朋友。 没有方向,都是彼此的朋友。




还要注意标记为“Patrick”的节点,它有点特别,因为没有任何连接的边。但“Patrick”点仍然是图的一部分,但在这种情况下,我们会说这个图是不连通的,它是非连通图(与“John”,“Ashot”和“Beth”相同,因为它们与图的其他部分分开)。在连通图中没有不可达的节点,每对节点之间必须有一条路径。


与Facebook的例子相对,如果Alice在Twitter上关注Bob,那不需要Bob回粉Alice。所以一个“关注”关系必须有一个方向来指示是谁关注谁,图表示中就是哪个节点(用户)有一个有向边(关注)到另一个节点。



现在,知道什么是有限连通无向图后,让我们回到欧拉图:



为什么我们首先讨论了哥尼斯堡桥问题和欧拉图呢?通过思考实际问题和上述解决方案,我们触及了图理论的基本概念(节点,边,有向,无向),避免了只有枯燥的理论。然而我们还未完全解决欧拉图和上述问题。我们现在应该转向图的计算机表示,因为这对我们程序员来说是最重要的。通过在计算机程序中表示图,我们能设计一个标记图路径的算法,从而确定它是否是欧拉路径。


图表示法:介绍


这是一项非常乏味的任务,要有耐心。记得数组和链表之间的竞争吗?如果你需要对元素进行快速访问,请使用数组;如果你需要对元素进行快速插入/删除等修改,请使用列表。


我不相信你会在“如何表示列表”的问题上有困惑。但是就图的表示而言,实际上却有很多麻烦,因为首先需要你决定的是用什么来表示一个图。相信我,你不会喜欢这个过程的。邻接表,邻接矩阵,还是边表?抛个硬币来决定?


决定了用什么表示图了吗?我们将从一棵树开始。你应该至少看过一次二叉树(以下不是二叉搜索树)。



仅仅因为它由顶点和边组成,它就是一个图。你也许还记得最常见的一棵二叉树(至少在教科书中)的样子。


对于已经熟悉“树”的人来说,这段文字可能看起来太过细致了,但我还是要详细解释以确保我们的理解一致(请注意,我们仍在使用伪代码)。


BinTreeNode<Apple>* root = new BinTreeNode<Apple>("Green");

root->left = new BinTreeNode<Apple>("Yellow");
root->right = new BinTreeNode<Apple>("Yellow 2");

BinTreeNode<Apple>* yellow_2 = root->right;

yellow_2->left = new BinTreeNode<Apple>("Almost red");
yellow_2->right = new BinTreeNode<Apple>("Red");


如果你不熟悉树,请仔细阅读上面的伪代码,然后按照此插图中的步骤操作:


颜色只是为了有好的视觉表现


虽然二叉树是一个简单的节点“集合”,每个节点都有左右子节点,但二叉搜索树却由于应用了一个允许快速键查找的简单规则显得更加有用。


二叉搜索树(BST)对各节点排序。你可以自由地使用任何你想要的规则来实现你的二叉树(尽管可能根据规则改变它的名字,例如最小堆或最大堆),BST一般满足二分搜索属性(这也是二叉搜索树名字的来源),即“每个节点的值必须大于其左侧子树中的任何值,并且小于其右侧子树中的任何值”。


关于“大于”的阐述有个非常有趣的评论。这对于BST的性质也至关重要。当将“大于”更改为“大于或等于”,插入新节点后,BST可以保留重复的值,否则只将保留一个具有相同值的节点。你可以在网上找到关于二叉搜索树非常好的文章,我们不会提供二叉搜索树的完整实现,但为了保持一致性,我们还是会举例说明一个简单的二叉搜索树。



Airbnb房屋查询实例:二叉搜索树


树是一种非常实用的数据结构,你可能在项目开始的时候没有打算应用树这种结构,但是不经意间就用到了。让我们来看一个构想的但非常有价值的例子,关于为什么要首先使用二叉搜索树。


二叉搜索树中这个名字中包括了“搜索”,所以基本上所有需要快速查找的东西都应该放进二叉搜索树中。应该不代表必须,在编程中最重要的事情就是要牢记用合适的工具解决问题。在许多案例中用复杂度为O(N)的简单链表查找比复杂度为O(logN)的二叉搜索树查找更好。


通常我们会使用一个库实现BST,最常用的是C++里面的std::set或是std::map,然而在这个教程中,我们完全可以重新造轮子,重写一个BST库(BST几乎可以通过任何通用的编程语言库实现,你可以找到你喜欢的语言的相应文档)。


下面是我们将要解决的问题:



Airbnb房屋搜索截图


如何用一些过滤器尽可能快地根据一些查询语句搜索住房是一个困难的问题;当我们考虑到Airbnb储存了上百万的房源信息后这个问题会变得更困难。


所以当用户搜索住房时,他们有机会接触到数据库中大概四百万个房源记录。当然,网站主页上只能显示有限个排名靠前的住房列表,并且用户几乎不会好奇地去浏览住房列表里百万量级的住房信息。我没有任何关于Airbnb的分析,但是我们可以用一个在编程中叫做“假设”的强大工具,所以我们假设一个用户最多浏览一千套房源。


这里最重要的因素是实时用户数量,因为这会导致数据结构、数据库选择的不同和项目结构的不同。可以很明显的看出,如果只有100个用户,我们一点不必担心,但如果一个国家的实时用户数量远超过百万量级的阙值,我们必须要十分明智地做每一个决定。


是的,“每一个”决定都需要明智的决策。这就是为什么公司在服务方面力求卓越而雇佣最好的员工。Google, Facebook, Airbnb, Netflix, Amazon, Twitter还有许多其他的公司,他们需要处理大量的数据,做出正确的选择以通过每秒数以百万的数据来服务数以百万的实时用户,这一切从招聘好的工程师开始。这就是为什么我们程序员在面试中要与数据结构、算法和解决问题的逻辑作斗争,因为他们所需要工程师需要具有以最快最有效的方式解决大规模问题的能力。


现在案例是,一个用户访问Airbnb主页并且希望找到最合适的房源。我们如何解决这个问题?(注意这是一个后端问题,所以我们不需关心前端或者网络阻塞或者多个http链接一个http或者Amazon EC2链接主集群等等)。


首先,我们已经熟悉了一种强大的编程工具(是假设而不是抽象),让我们从假设我们所处理的数据都能放在内存中。你也可以假设我们的内存已经足够大。多大的内存是足够的呢?这是另外一个好问题。储存真实的数据需要多大的内存,如果我们正在处理四百万单位个数据(假设),而且我们或许知道每个单位的大小,那么我们就可以轻易的算出需要的内存大小,例如,4M*一个单位的大小。让我们来考虑一个“home”对象和他的特征,


事实上,让我们考虑至少一个在解决问题时需要处理的特征(一个“home”就是一个单位)。我们把它表示为C++语言结构的伪代码,你可以很轻松的将它转换成MongoDB架构或者其它你想要的形式,我们只是讨论特征名字和类型(想象为节约空间所使用的二元位域或位集)。


// feel free to reorganize this struct to avoid redundant space
// usage because of aligning factor
// Remark 1: some of the properties could be expressed as enums,
// bitset is chosen for as multi-value enum holder.
// Remark 2: for most of the count values the maximum is 16
// Remark 3: price value considered as integer,
// int considered as 4 byte.
// Remark 4: neighborhoods property omitted
// Remark 5: to avoid spatial queries, we're
// using only country code and city name, at this point won't consider
// the actual coordinates (latitude and longitude)
struct Airb

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值