使用广度优先搜索找到最短路径

by Sachin Malhotra

由Sachin Malhotra

Do you know the amount of global air traffic in 2017? Do you know what the rise has been for air traffic over the past several years ? Well, lets look at some statistics.

您知道2017年的全球航空运输量吗? 您知道过去几年空中交通的增长吗? 好吧,让我们看一些统计数据。

According to the International Civil Aviation Organization (ICAO), a record 4.1 billion passengers were carried by the aviation industry on scheduled services in 2017. And, the number of flights rose to 37 million globally in 2017.

根据国际民用航空组织 (ICAO)的数据,2017年航空业按计划提供了创纪录的41亿旅客。2017年,全球航班数量上升至3700万。

That’s a lot of passengers and a lot of flights occupying the air space on a daily basis across the world. Since there are hundreds and thousands of these flights all around the globe, there are bound to be different routes with multiple stops in between from one place to another.

全球每天都有大量乘客和大量航班占用空域。 由于全球有成千上万的此类航班,因此必然会有不同的路线,从一个地方到另一个地方之间会有多个站点。

Every flight has a source and destination of its own and a standard economy seat price associated with it. Let’s leave out the fancy business class tickets and extra leg room and what not!

每个航班都有自己的来源和目的地,以及与此相关的标准经济舱票价。 让我们省去花哨的商务舱机票和额外的腿部空间吧!

In such a scenario, it is too confusing to choose what flight would be the best one if we want to go from one place to another.

在这种情况下,如果我们想从一个地方飞往另一个地方,那么选择哪种航班是最好的航班就太令人困惑了。

Let’s see the number of flight options StudentUniverse (provides discounts for students ?) gives me from Los Angeles to New Delhi.

让我们看看从洛杉矶到新德里的StudentUniverse航班数量 (为学生提供折扣?)。

119 total flights are being offered. Then there appears a pop up on the website saying that there are other websites that might be offering similar flights at even cheaper rates. ?

提供119个航班。 然后,该网站上出现一个弹出窗口,说还有其他网站可能以更低的价格提供类似的航班。 ?

So many websites and uncountable flights for just a single source and destination.

如此之多的网站和无数的航班仅针对一个来源和目的地。

As a developer, if I want to solve this problem, I would build a system to efficiently address the following queries:

作为开发人员,如果我想解决此问题,我将构建一个系统来有效解决以下查询:

  • Total number of destinations reachable (with a max number of stops) from my current location, and also list those destinations.

    从我当前的位置可以到达的目的地总数(最大停靠点数),并列出了这些目的地。

    One should keep their options open when they want to travel ?.

    当他们想旅行时,应该保持自己选择的权利。

  • It is a known fact (IMO ?) that a route with multiple stops tends to be a cheaper alternative to direct flights.

    众所周知的事实(IMO?),具有多个停靠点的航线往往比直接飞行更便宜。

    So, given a source and a destination, we may want to find routes with at least 2 or 3 stops.

    因此,给定一个源和一个目的地,我们可能希望查找至少2或3站的路线。

  • Most importantly: What is the cheapest route from a given source to a given destination?

    最重要的是:从给定来源到给定目的地的最便宜的路线是什么?
  • And…. We’ll come to this one in the end ?.

    和…。 我们最后来这个吗?

As you might guess, there would be potentially thousands of flights as the output of the first two queries. But we can certainly reduce that by providing some other criteria to lessen the output size. For the scope of this article, let us focus on these original queries themselves.

您可能会猜到,前两个查询的输出可能有数千个航班。 但是我们当然可以通过提供其他一些标准来减小输出大小来减少这种情况。 对于本文的范围,让我们专注于这些原始查询本身。

将飞行网络建模为图形 (Modeling the Flight Network as a Graph)

It’s pretty clear from the headline of this article that graphs would be involved somewhere, isn’t it?

从本文的标题可以很明显地看出,图形会涉及到某个地方,不是吗?

Modeling this problem as a graph traversal problem greatly simplifies it and makes the problem much more tractable. So, as a first step, let us define our graph.

将这个问题建模为图形遍历问题可以极大地简化它,并使问题更易于处理。 因此,作为第一步,让我们定义图。

We model the air traffic as a:

我们将空中交通建模为:

  • directed

    指导的
  • possibly cyclic

    可能循环
  • weighted

    加权的
  • forest. G (V, E)

    森林。 G(V,E)

Directed because every flight will have a designated source and a destination. These carry a lot of meaning.

定向,因为每个航班都会有一个指定的来源和目的地。 这些具有很多意义。

Cyclic because it is very possible to follow a bunch of flights starting from a given location and ending back at the same location.

循环的,因为很有可能跟随一排从给定位置开始并返回相同位置的航班。

Weighted because every flight has a cost associated with it which would be the economy class flight ticket for this article.

加权是因为每个航班都有与之相关的成本,这将是本文的经济舱机票。

And finally, a forest because we might have multiple connected components. It is not necessary that all the cities in the world have some sort of flight network between them. So, the graph can be disconnected, and hence a forest.

最后是森林,因为我们可能有多个连接的组件。 世界上所有城市之间不一定都具有某种飞行网络。 因此,该图可以断开连接,从而断开连接。

The vertices, V, would be the locations all over the world wherever there are working airports.

顶点V将是世界上任何有工作机场的地点。

The edges, E, would be representative of all the flights constituting the air traffic. An edge from u -->; v simply means you have a directed flight from the location / node u to v .

边缘E将代表构成空中交通的所有航班。 你的优势u --> ; v只是意味着你有一个从位置/不定向飞行d欧盟t OV。

Now that we have an idea about how to model the flight network as a graph, let us move on and solve the first common query that a user might have.

现在,我们有了一个关于如何将飞行网络建模为图形的想法,让我们继续解决用户可能遇到的第一个常见查询。

可到达的目的地总数 (Total Number of Destinations Reachable)

Who doesn’t like to travel?

谁不喜欢旅行?

As someone who likes to explore different places, you would want to know what all destinations are reachable from your local airport. Again, there would be additional criteria here to reduce the results of this query. But to keep things simple, we will simply try and find all the locations reachable from our local airport.

作为一个喜欢探索不同地方的人,您可能想知道从当地机场可以到达的所有目的地。 同样,这里还会有其他标准来减少此查询的结果。 但是为了简单起见,我们将尝试查找可从我们本地机场到达的所有位置。

Now that we have a well defined graph, we can apply traversal algorithms to process it.

现在我们有了一个定义明确的图,我们可以应用遍历算法来对其进行处理。

Starting off from a given point, we can use either Breadth First Search (BFS) or Depth First Search (DFS) to explore the graph or the locations reachable from the starting location within a maximum number of stops. Since this article is all about the breadth first search algorithm, let’s look at how we can use the famous BFS to accomplish this task.

从给定点开始,我们可以使用广度优先搜索 (BFS)或深度优先搜索 (DFS)来浏览图形或在最大停靠点数内从起始位置可到达的位置 由于本文都是关于广度优先搜索算法的,因此让我们看一下如何使用著名的BFS来完成此任务。

We will initialize the BFS queue with the given location as the starting point. We then perform the breadth first traversal, and keep going until the queue is empty or until the maximum number of stops have been exhausted.

我们将以给定位置为起点初始化BFS队列。 然后,我们执行广度优先遍历,并继续进行直到队列为空或直到停止的最大次数为止。

Note: If you are not familiar with the breadth first search or depth first search, I would recommend going through this article before continuing.

注意:如果您不熟悉广度优先搜索或深度优先搜索,建议继续阅读本文

Let’s look at the code to initialize our graph data structure. We also need to look at how the BFS algorithm would end up giving us all the destinations reachable from a given source.

让我们看一下初始化图形数据结构的代码。 我们还需要查看BFS算法最终将如何为我们提供从给定源可到达的所有目的地。

Now that we have a good idea about how the graph is to be initialized, let’s look at the code for the BFS algorithm.

现在我们对如何初始化图形有了一个好主意,让我们看一下BFS算法的代码。

Performing bfs on the city of Los Angeles would give us the following destinations which are reachable:

在洛杉矶市执行bfs将为我们提供以下可达的目的地:

{'Chicago', 'France', 'Ireland', 'Italy', 'Japan', 'New Delhi', 'Norway'}

That was simple, wasn’t it?

那很简单,不是吗?

We will look at how we can limit the BFS to a maximum number of stops later on in the article.

在后面的文章中,我们将研究如何将BFS限制为最大停止次数。

In case we have a humongous flight network, which we would have in a production scenario, then we would not ideally want to explore all the reachable destinations from a given starting point.

如果我们有一个庞大的飞行网络(在生产场景中会拥有该网络),那么我们理想上就不希望从给定的起点探索所有可到达的目的地。

This is a use case if the flight network is very small or pertains only to a few regions in the United States.

如果飞行网络非常小或仅涉及美国的几个地区,则这是一个用例。

But, for a large network, a more realistic use case would be to find all the flight routes with multiple stops. Let us look at this problem in some more detail and see how we can solve it.

但是,对于大型网络,更现实的用例是找到所有具有多个停靠点的飞行路线。 让我们更详细地研究这个问题,看看如何解决。

多站路线 (Routes with multiple stops)

It is a well known fact that more often than not, for a given source and destination, a multi stop trip is cheaper than a direct, non-stop flight.

众所周知的事实是,对于给定的来源和目的地,多站旅行通常比直​​接的,不间断的航班便宜。

A lot of times we prefer the direct flight to avoid the layovers. Also because the multi-stop flights do tend to take a lot of time — which we don’t have.

很多时候,我们更喜欢直接飞行以避免中途停留。 同样是因为多站飞行确实会花费很多时间,而我们却没有。

However, if you don’t have any deadlines approaching and you want to save some bucks (and are comfortable with the multi-stop route that a lot of airlines suggest), then you might actually benefit a lot from something like this.

但是,如果您没有任何截止日期,并且想节省一些钱(并且对许多航空公司建议的多站路线感到满意),那么您实际上可能会从这样的事情中受益匪浅。

Also, you might get to pass through some of the most beautiful locations in the world with some of the most advanced airports which you can enjoy. So, that’s enough motivation as it is.

另外,您可能会穿越世界上一些最美丽的地方,并享受一些最先进的机场。 因此,这就是足够的动机。

In terms of the graph model that we have been talking about, given a source and a destination, we need to come up with routes with 2 or more stops for a given source and destination.

就我们一直在讨论的图模型而言,给定一个源和一个目标,对于给定的源和目标,我们需要拿出具有2个或更多停靠点的路线。

As an end user, we might not want to see flights in this order for this query:

作为最终用户,我们可能不希望以此顺序查看此查询的排期:

[A, C, D, B], 2 stops, $X[A, E, D, C, F, W, G, T, B], 8 stops, $M[A, R, E, G, B], 3 stops, $K[A, Z, X, C, V, B, N, S, D, F, G, H, B, 11 stops, $P

I know. Nobody in their right minds would want to go for a flight route with 11 stops. But the point I’m trying to make is that an end user would want symmetry. Meaning that they would want to see all the flights with 2 stops first, then all the flights with 3 stops and so on till maybe a max of, say, 5 stops.

我知道。 在他们的正确思维中,没有人愿意选择11个站的飞行路线。 但是我要说明的是最终用户想要对称。 这意味着他们希望先查看所有2个停靠点的航班,然后再查看所有3个停靠点的航班,依此类推,直到最多可以看到5个停靠点。

But, all the flight routes with the same number of stops in between should be displayed together. That is a requirement we need to satisfy.

但是,所有之间停靠点数量相同的飞行路线应一起显示。 这是我们需要满足的要求。

Let’s look at how we can solve this. So, given the graph of flight networks, a source S and a destination D, we have to perform a level order traversal and report flight routes from S -->; D with at least 2 and at most 5 stops in between. This means we have to do a level order traversal until a depth of 7 from the start node S .

让我们看看如何解决这个问题。 因此,给定飞行网络,源S和目的地D的图形,我们必须执行层级遍历并从S -->报告飞行路线; D之间至少有2个,最多5个停靠点。 这意味着我们必须要办出水平序遍历,直到7从一开始没有深度d (E S)。

Have a look at the code for solving this problem:

看一下解决这个问题的代码:

This might not be the best way to go about solving this problem at scale — the largest memory constraint would be due to the nodes currently present in the queue.

这可能不是大规模解决此问题的最佳方法-最大的内存限制将归因于队列中当前存在的节点。

Since every node or location can have thousands of flights to other destinations in the world, the queue could be humongous if we store actual flight data like this. This is just to demonstrate one of the use cases of the breadth first search algorithm.

由于每个节点或位置可以有成千上万的飞往世界其他目的地的航班,因此如果我们这样存储实际的航班数据,则队列可能会非常庞大​​。 这只是为了演示广度优先搜索算法的用例之一。

Now, let us just focus on the traversal and look at the way it is done. The traversal algorithm is simple as it is. However, the entire space complexity of the level order traversal comes from the elements in the queue and the size of each element.

现在,让我们仅关注遍历并查看其完成方式。 遍历算法很简单。 但是,级别顺序遍历的整个空间复杂性来自队列中的元素以及每个元素的大小。

There are multiple ways to implement the algorithm. Also, each of them varies in terms of maximum memory consumed at any given time by the elements in the queue.

有多种实现算法的方法。 同样,它们每个在队列中的元素在任何给定时间消耗的最大内存方面都各不相同。

We want to see the maximum memory consumed by the queue at any point in time during the level order traversal. Before that, let’s construct a random flight network with random prices.

我们想要查看在级别顺序遍历期间的任何时间队列所消耗的最大内存。 在此之前,让我们构建一个具有随机价格的随机航班网络。

Now let us look at the implementation of level order traversal.

现在让我们看一下级别顺序遍历的实现。

This above is the easiest and most straightforward implementation of the level order traversal algorithm.

以上是级别顺序遍历算法的最简单,最直接的实现。

With every node we add to the queue, we also store the level information and we push a tuple of (node, level) into the queue. So every time we pop an element from the queue, we have the level information attached with the node itself.

对于添加到队列中的每个节点,我们还存储级别信息,并将(node, level)元组推入队列。 因此,每当我们从队列中弹出一个元素时,我们都会在节点本身上附加级别信息。

The level information for our use case would mean the number of stops from the source to this location in the flight route.

我们用例的级别信息将表示从源到飞行路线中此位置的停留次数。

It turns out that we can do better as far as memory consumption of the program is concerned. Let us look at a slightly better approach to doing level order traversal.

事实证明,就程序的内存消耗而言,我们可以做得更好。 让我们看一个更好的方法来进行级别顺序遍历。

The idea here is that we don’t store any additional information with the nodes being pushed into the queue. We use a None object to mark the end of a given level. We don’t know the size of any level before hand except for the first level, which just has our source node.

这里的想法是,当节点被推入队列时,我们不存储任何其他信息。 我们使用None对象标记给定级别的结束。 除了第一个级别(我们只有source节点)以外,我们不知道任何级别的大小。

So, we start the queue with [source, None] and we keep popping elements. Every time we encounter a None element, we know that a level has ended and a new one has started. We push another None to mark the end of this new level.

因此,我们从[source, None]开始队列,并继续弹出元素。 每次遇到None元素时,我们都知道一个级别已经结束,并且新级别已经开始。 我们推另一个None来标记此新级别的结束。

Consider a very simple graph here, and then we will dry run this through the graph.

在这里考虑一个非常简单的图形,然后我们将在图形中进行试运行。

**************************************************** LEVEL 0 beginslevel = 0, queue = [A, None]level = 0, pop, A, push, B, C, queue = [None, B, C]pop None ******************************************* LEVEL 1 beginspush Nonelevel = 1, queue = [B, C, None]level = 1, pop, B, push, C, D, F, queue = [C, None, C, D, F]level = 1, pop, C, push, D, D (lol!), queue = [None, C, D, F, D, D]pop None ******************************************* LEVEL 2 beginspush Nonelevel = 2, queue = [C, D, F, D, D, None] .... and so on

I hope this sums up the algorithm pretty well. This certainly is a neat trick to do level order traversal, keep track of the levels, and not encounter too much of a memory concern. This certainly reduces the memory footprint of the code.

我希望这能很好地总结算法。 当然,这是进行级别顺序遍历,跟踪级别并且不会遇到太多内存问题的巧妙技巧。 这无疑减少了代码的内存占用。

Don’t get complacent now thinking this is a great improvement.

现在不要自满,认为这是一个很大的进步。

It is, but you should be asking two questions:

是的,但是您应该问两个问题:

  1. How big of an improvement is this?

    这有多大的改善?
  2. Can we do better?

    我们可以做得更好吗?

I will answer both of these questions now starting with the second question. The answer to that is Yes!

我将从第二个问题开始回答这两个问题。 答案是肯定的!

We can do one better here and completely do away with the need for the None in the queue. The motivation for this approach comes from the previous approach itself.

我们可以在这里做得更好,并且完全不需要队列中的None 。 这种方法的动机来自先前的方法本身。

If you look closely at the dry run above, you can see that every time we pop a None, one level is finished and the other one is ready for processing. The important thing is that an entire next level exists in the queue at the time of popping of a None . We can make use of this idea of considering the queue size into the traversal logic.

如果仔细看一下上面的空运行,您会发现每次我们弹出None ,一个关卡就完成了,而另一关卡就可以进行处理了。 重要的是,在弹出None时,队列中存在整个下一个级别。 我们可以利用将队列大小纳入遍历逻辑的想法。

Here is the pseudo code for this improved algorithm:

这是此改进算法的伪代码:

queue = Queue()queue.push(S)level = 0while queue is not empty {      size = queue.size()      // size represents the number of elements in the current level      for i in 1..size {          element = queue.pop()          // Process element here          // Perform a series of queue.push() operations here
level += 1

And here is the code for the same.

这是相同的代码。

The pseudo code is self explanatory. We essentially do away with the need for an extra None element per level and instead make use of the queue’s size to change levels. This would also lead to improvement over the last method, but how much?

伪代码是不言自明的。 从本质上讲,我们不需要在每个级别上都增加一个None元素,而是利用队列的大小来更改级别。 这也将导致对最后一种方法的改进,但是要多少呢?

Have a look at the following Jupyter Notebook to see the memory difference between the three methods.

看看下面的Jupyter Notebook,看看这三种方法之间的内存差异。

  • We track the maximum size of the queue at any time by considering the sum of sizes of individual queue elements.

    我们通过考虑各个队列元素的大小总和来随时跟踪队列的最大大小。
  • According to Python’s documentation, sys.getsizeof returns the object’s pointer or reference’s size in bytes. So, we saved almost 4.4Kb space (20224 — 15800 bytes) by switching to the None method from the original level order traversal method. This is just the memory savings for this random example, and we went only until the 5th level in the traversal.

    根据Python的文档, sys.getsizeof返回对象的指针或引用的大小(以字节为单位)。 因此,通过从原始级别顺序遍历方法切换到None方法,我们节省了近4.4Kb的空间(20224 — 15800 bytes) 。 这只是这个随机示例的内存节省,而我们只进行到遍历的第5级。

  • The final method only gives an improvement of 16 bytes over the None method. This is because we got rid of just 4 None objects which were being used to mark the 4 levels (apart from the first one) that we processed. Each pointer’s size (pointer to an object) is 4 bytes in Python on a 32 bit system.

    最终方法仅比None方法改善了16个字节。 这是因为我们仅删除了4个None对象,这些对象用于标记我们处理的4个级别(除第一个级别之外)。 在32位系统上,每个指针的大小(指向对象的指针)为4字节。

Now that we have all these interesting multi-path routes from our source to our destination and highly efficient level order traversal algorithms to solve it, we can look at a more lucrative problem to solve using our very own BFS.

现在,我们已经拥有了从源头到目的地的所有这些有趣的多路径路线,以及可以解决该问题的高效层级遍历算法,现在我们可以来看一个使用自己的BFS解决的更有利可图的问题。

What’s the cheapest flight route from my source to a given destination? This is something everybody would be instantly interested in. I mean who doesn’t want to save some bucks?

从我的来源到给定目的地的最便宜的航线是多少? 这是每个人都会立即感兴趣的东西。我的意思是谁不想节省一些钱?

从给定源到目标的最短路径 (Shortest Path from a given source to destination)

There’s not much description to give for the problem statement. We just need to find the shortest path and make the end user happy.

问题说明没有太多说明。 我们只需要找到最短的路径并使最终用户满意即可。

Algorithmically, given a weighted directed graph, we need to find the shortest path from source to destination. Shortest or cheapest would be one and the same thing from the point of the view of the algorithm.

在算法上,给定加权有向图,我们需要找到从源到目标的最短路径。 从算法的角度来看,最短或最便宜将是一回事。

We will not go into describing a possible BFS solution to this problem because such a solution would be intractable. Let us look at the graph below to understand why that is the case.

我们将不介绍可能的BFS解决方案,因为这样的解决方案很棘手。 让我们看下面的图表,了解为什么会这样。

We say that BFS is the algorithm to use if we want to find the shortest path in an undirected, unweighted graph. The claim for BFS is that the first time a node is discovered during the traversal, that distance from the source would give us the shortest path.

我们说BFS是要在无向,无权图中找到最短路径的算法 BFS的主张是,在遍历期间首次发现节点时,与源的距离将为我们提供最短的路径。

The same cannot be said for a weighted graph. Consider the graph above. If say we were to find the shortest path from the node A to B in the undirected version of the graph, then the shortest path would be the direct link between A and B. So, the shortest path would be of length 1 and BFS would correctly find this for us.

加权图不能说相同。 考虑上图。 如果说要在图的无向版本中找到从节点AB的最短路径,则最短路径将是A和B之间的直接链接。因此,最短路径的长度为1而BFS为为我们正确找到这个。

However, we are dealing with a weighted graph here. So, the first discovery of a node during traversal does not guarantee the shortest path for that node. For example, in the diagram above, the node B would be discovered initially because it is the neighbor of A and the cost associated with this path (an edge in this case) would be 25 . But, this is not the shortest path. The shortest path is A --> M --> E --> B of length 10.

但是,我们在这里处理加权图。 因此,遍历期间节点的首次发现并不能保证该节点的最短路径。 例如,在上图中,将首先发现节点B ,因为它是A的邻居,并且与此路径关联的成本(在这种情况下为边)为25 。 但是,这不是最短的路径。 的最短路径是A --> M --> E - > B○ f长度10。

Breadth first search has no way of knowing if a particular discovery of a node would give us the shortest path to that node. And so, the only possible way for BFS (or DFS) to find the shortest path in a weighted graph is to search the entire graph and keep recording the minimum distance from source to the destination vertex.

广度优先搜索无法知道某个节点的特定发现是否会为我们提供到该节点的最短路径。 因此,BFS(或DFS)在加权图中找到最短路径的唯一可能方法是搜索整个图并保持记录从源到目标顶点的最小距离。

This solution is not feasible for a huge network like our flight network that would have potentially thousands of nodes.

这种解决方案对于像我们的飞行网络这样的庞大网络是不可行的,因为它可能具有数千个节点。

We won’t go into the details of how we can solve this. That is out of scope for this article.

我们不会详细介绍如何解决此问题。 这超出了本文的范围。

What if I told you that BFS is just the right algorithm to find the shortest path in a weighted graph with a slight constraint ?

如果我告诉您BFS只是在带有轻微约束的加权图中找到最短路径的正确算法,该怎么办?

约束最短路径 (Constrained Shortest Paths)

Since the graph we would have for the flight network is humongous, we know that exploring it completely is not really a possibility.

由于我们对于飞行网络所拥有的图表是巨大的,因此我们知道完全探索它是不可能的。

Consider the problem of shortest paths from the customer’s perspective. When you want to book a flight, these are the following options you ideally consider:

从客户的角度考虑最短路径的问题。 当您要预订航班时,以下是您理想的选择:

  • It shouldn’t be too long a flight.

    飞行不应该太长。
  • It should be under your budget (Very Important).

    它应该在您的预算之内(非常重要)。
  • It may have multiple stops but not more than K where K can vary from person to person.

    它可能有多个停靠点,但最多不超过K ,其中K可能因人而异。

  • Finally we have personal preferences which involve things like lounge access, food quality, layover locations, and average leg room.

    最后,我们有个人喜好,其中包括使用休息室,食物质量,中途停留的地点和平均腿部空间。

The important point to consider here is the third one above: it may have multiple stops, but not more than K where K can vary from person to person.

这里要考虑的重要一点是上面的第三点:它可能有多个停靠点,但最多不超过K ,其中K可能因人而异。

A customer wants the cheapest flight route, but they also don’t want say 20 stops in between their source and destination. A customer might be okay with a maximum of 3 stops, or in extreme cases maybe even 4 — but not more than that.

客户希望获得最便宜的航班路线,但也不想说源头和目的地之间有20个停靠点。 一个客户最多可以停3次,或者在极端情况下甚至可以停4次,但还可以。

We would want an application that would find out the cheapest flight route with at most K stops for a given source and destination.

我们希望有一个应用程序能够找出给定源和目的地的最便宜的航班路线(最多K个停靠站)

This question from LeetCode has been the primary motivation for me to write this article. I strongly recommend going through the question once and not only relying on the snapshot above.

LeetCode提出的这个问题一直是我撰写本文的主要动机。 我强烈建议您一次回答问题,而不仅要依赖上面的快照。

“Why would BFS work here?” one might ask. “This is also a weighted graph and the same reason for the failure of BFS that we discussed in the previous section should apply here.” NO!

“为什么BFS在这里工作?” 一个人可能会问。 “这也是加权图,我们在上一节中讨论的BFS失败的相同原因也应适用于此。” 没有!

The number of levels that the search would go to is limited by the value K in the question or in the description provided at the start of section. So, essentially, we would be trying to find the shortest path, but we won’t have to explore the entire graph as such. We will just go up to the level K.

搜索将进入的级别数受问题或本节开头提供的描述中的值K限制。 因此,从本质上讲,我们将尝试找到最短的路径,但不必像这样探索整个图。 我们将升至K级。

From a real life scenario, the value of K would be under 5 for any sane traveler ?.

从现实生活的情况来看,任何一个理智的旅行者?的K值都将低于5。

Let us look at the pseudo-code for the algorithm:

让我们看一下该算法的伪代码:

def bfs(source, destination, K):      min_cost = dictionary representing min cost under K stops for each node reachable from source.
set min_cost of source to be 0
Q = queue()      Q.push(source)      stops = 0      while Q is not empty {
size = Q.size           for i in range 1..size {                 element = Q.pop()
if element == destination then continue
for neighbor in adjacency list of element {                        if stops == K and neighbor != destination        then continue
if min_cost of neighbor improves, update and add back to the queue.                }           }               stops ++       }

This again is level order traversal and the approach being used here is the one that makes use of the queue’s size at every level. Let us look at a commented version of the code to solve this problem.

这又是级别顺序遍历,这里使用的方法是在每个级别上利用队列大小的方法。 让我们看一下代码的注释版本以解决此问题。

Essentially, we keep track of the minimum distance of every node from the given source. The minimum distance for the source would be 0 and +inf for all others initially.

本质上,我们跟踪每个节点到给定源的最小距离。 最初,所有其他光源的最小距离为0和+ inf。

Whenever we encounter a node, we check if the current minimum path length can be improved or not. If it can be improved, that means that we have found an alternate path from source to this vertex with cheaper cost — a cheaper flight route until this vertex. We queue this vertex again so that locations and nodes reachable from this vertex on are updated (may or may not be) as well.

每当遇到节点时,我们都会检查是否可以改善当前的最小路径长度。 如果可以改进,则意味着我们找到了一条从源到该顶点的替代路径,且成本更低-直到该顶点为止的飞行路线都更便宜。 我们再次对该顶点进行排队,以便从该顶点开始可到达的位置和节点也被更新(可能会也可能不会)。

The key thing is this:

关键是:

# No need to update the minimum cost if we have already exhausted our K stops. if stops == K and neighbor != dst:    continue

So we just popped the a node represented by element in the code and neighbor can either be a destination or a random other node. If we have already exhausted our K stops with the element being the Kth stop, then we shouldn’t process and update (possibly) the minimum distance for neighbor. This would violate our maximum K stops condition in that case.

因此,我们只是在代码中弹出了一个由element表示的节点, neighbor可以是目标节点,也可以是随机的其他节点。 如果我们已经用尽了我们的K停靠点,而element是第Kth停靠点,那么我们就不应该处理和更新(可能) neighbor的最小距离。 在这种情况下,这将违反我们的最大K停止条件。

As it turns out, I solved this problem originally using Dynamic Programming and it took around 165ms to run on the LeetCode platform. I ran using BFS and it was blazing fast with just 45ms to execute. Motivation enough to write this article for you guys.

事实证明,我最初使用动态编程解决了这个问题,并且在LeetCode平台上运行大约需要165ms。 我使用BFS进行运行,执行速度仅为45ms,非常快。 足以为你们写这篇文章的动机。

I hope you were able to derive some benefit from this article on Breadth First Search and some of its applications. The major focus was to showcase its application to shortest paths in a weighted graph under some constraints ?.

我希望您能够从有关“广度优先搜索”的本文及其一些应用程序中受益。 主要重点是在某些约束条件下展示其在加权图中最短路径的应用。

翻译自: https://www.freecodecamp.org/news/exploring-the-applications-and-limits-of-breadth-first-search-to-the-shortest-paths-in-a-weighted-1e7b28b3307/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值