单程航班问题

57

您将进行一次单程间接飞行旅行,其中包括数十亿一个未知的非常大的数字个转机。

  • 您不会在同一个机场停留两次。
  • 您有每一部分旅程的1张机票。
  • 每张机票都包含srcdst机场。
  • 您拥有的所有机票都是随机排序的。
  • 您忘记了最初的出发机场(第一个src)和目的地(最后一个dst)。

设计一种算法,以最小的大O复杂度重建您的旅程。


尝试解决这个问题,我开始使用两个集合Srcs和Dsts的对称差异

1)在数组Srcs中对所有src键进行排序
2)在数组Dsts中对所有dst键进行排序
3)创建一个联合集,以查找非重复项——它们是您的第一个Src和最后一个Dst
4)现在,有了起点,使用二进制搜索遍历两个数组。

但我认为可能有另一种更有效的方法。


7
亿万次转移...你不能在同一个机场停留两次。我不认为有那么多的机场。 - James McNellis
2
听起来像是作业,但我也不确定... - Nate
4
+1 可以抵消没有解释的负评和关闭投票。(如果您想发脾气:请不要对这个人的问题提出异议) - Ian Boyd
12
  1. 按照出发时间排序机票
  2. 第一张机票的起点是您的出发机场,最后一张机票的到达点是您的到达机场。 ;)
- Nick Johnson
2
如果你忘记了最终目的地,那么你就没有行李(因为它在某个神秘的机场)。考虑到涉及的机票数量很大,你必须将它们藏在行李里。因此,你没有机票,这个问题也就毫无意义了。 - bta
显示剩余7条评论
18个回答

32

构建散列表并将每个机场添加到散列表中。

<key,value> = <airport, count>

如果机场是源或终点,则该机场的计数会增加。因此,对于每个机场,计数将为2(源和目的地各1),但是对于您旅行的起点和终点,计数将为1。

您需要至少查看每张机票一次。因此,时间复杂度为O(n)。


4
你甚至不需要找到出发机场。你可以随意选择一个,建立尽可能多的路径,然后再随意选择另一个机场,以此类推,直到构建完整个旅程。 - Niki Yoshiuchi
4
@blueraja - 你只插入了每个元素一次。构建表格的时间复杂度为O(n),构建行程的时间复杂度也是O(n)。总时间复杂度为O(n + n),即O(n)。 - Niki Yoshiuchi
4
在哈希表中插入元素的时间复杂度为O(1)。对于一个键进行哈希计算的时间复杂度通常为O(k),其中k是键的长度。因此,该算法的时间复杂度为O(nk),但由于机场名称并不长(我们可以选择一个常数c,使得对于所有k属于n,都有c > k且c << n),所以实际上它是O(n)。 - Niki Yoshiuchi
6
Hashtable插入通常被表示为平摊O(1)。 - Lasse V. Karlsen
2
在现实世界中,每个机场都由一个由3个大写字母组成的序列表示,可以打包成15位。因此,32768个短整数的数组以及哈希表都可以使用。我非常确定数组访问是O(1)的。在一个假设的有数十亿个机场的世界里,必须有足够的资源来支持大量的RAM,因此仍然可以使用数组或足够大以具有低碰撞概率的哈希表,因此数据结构访问的答案仍为O(1) - 或者足够接近,算法为O(n)。 - JeremyP
显示剩余13条评论

23

摘要:下面给出了一种单遍算法(即不仅是线性的,而且每张票只会考虑一次,这当然是每张票最理想的访问次数)。我放了这个摘要是因为有许多看起来相等的解决方案,很难发现我为什么还要添加另一个 :)

实际上,我在面试中被问到了这个问题。这个概念非常简单:每张机票都是一个单例列表,具有概念上的两个元素:起点和终点。

我们使用其第一个和最后一个元素作为键,在哈希表中对每个这样的列表进行索引,以便我们可以在O(1)时间内找到列表是否以特定元素(机场)开头或结尾。对于每张机票,当我们看到它从另一个列表结束的地方开始时,只需链接这些列表(O(1))。同样,如果它在另一个列表开始的地方结束,那就连接另一个列表。当然,当我们链接两个列表时,我们基本上破坏了这两个列表并得到了一个列表。(N张机票的链将在N-1个这样的链接之后构建)。

需要注意的是,必须保持哈希表键正好是剩余列表的第一个和最后一个元素的不变式。

总体而言,时间复杂度为O(N)。

没错,我当场就回答了这个问题 :)

编辑 忘记添加一个重要的点。每个人都提到两个哈希表,但一个哈希表也可以解决问题,因为算法的不变式包括任何单个城市(如果有两个,我们立即将它们链接在一起,并从哈希表中删除该城市)的起始或结束处最多只能有一个机票列表。从渐近意义上讲,没有区别,只是这种方式更简单。

编辑2 还有一个有趣的事实,与使用每个具有N个条目的2个哈希表的解决方案相比,该解决方案使用最多N/2个条目的一个哈希表(如果我们以1、3、5等顺序查看机票,则会发生这种情况)。因此,这也使用了大约一半的内存,除了更快之外。


1
我喜欢这个。如果你真的有实体票,并且手动解决问题,这似乎也是你要解决问题的方式。 - Instance Hunter
谢谢。希望我的想法即使我懒得写所需的细节,也能通过:)这是一种简单的图像可以使算法立即清晰的东西,但文本描述相当尴尬。 - Dimitris Andreou
2
你是指联合哈希键<src,dst>还是一种具有两个单独哈希函数的多维哈希集?因为我不明白如何使用联合哈希键搜索特定的src和任意的dst。考虑我们想要连接一个航班<src=s,dst=d>,那么我们必须在现有序列上执行两次查找:<src=d,_><_, dst=s>,对吗?你在这里想到了什么样的哈希函数? - bluenote10

11
构建两个哈希表(或 tries),一个以 src 为键,另一个以 dst 为键。随机选择一张车票,并在 src 哈希表中查找其 dst。重复这个过程,直到到达终点(最终目的地)。现在在 dst-keyed 哈希表中查找它的 src。重复这个过程,直到到达起点。
构建哈希表需要 O(n),构建列表需要 O(n),因此整个算法是 O(n)。
编辑:实际上,您只需要构建一个哈希表。假设您构建了以 src 为键的哈希表。随机选择一张车票,像以前一样构造通向最终目的地的列表。然后从尚未添加到列表中的车票中选择另一张随机车票。跟随其目的地,直到到达最初开始的车票。重复此过程,直到构建完整个列表。由于最坏情况下您选择的车票是相反的顺序,因此仍然是 O(n)。
编辑:在我的算法中交换了表名。

我也开始考虑两个哈希表,但实际上一个哈希表就足够了。好主意,点赞。 - Patrick
这是一个三遍算法。一遍就足够了! - Dimitris Andreou

6
基本上它是一个依赖图,其中每个机票代表一个节点,srcdst机场表示有向链接,因此使用拓扑排序确定航班顺序。
编辑:虽然这是一张航空公司机票,并且你知道你实际上制定了一个行程,但仍要按照UTC的出发日期和时间进行排序。
编辑2:假设您拥有的每个机场都使用三个字符代码,您可以使用此处描述的算法(找到仅出现一次的三个数字)通过将所有机场异或在一起来确定两个唯一的机场。
编辑3:这里是一些C ++,用于使用异或方法解决此问题。总体算法如下,假设从机场到整数具有唯一编码(假定为三个字母机场代码或使用纬度和经度将机场位置编码为整数):
首先,将所有机场代码异或在一起。这应该等于初始源机场异或最终目的地机场。由于我们知道初始机场和最终机场是唯一的,因此该值不应为零。由于它不为零,该值中将至少有一个比特设置。该位对应于在一个机场中设置而在另一个机场中未设置的位;称其为指示器位。
接下来,设置两个桶,每个桶都有第一步中异或的值。现在,对于每张机票,根据它是否具有指示器位,将每个机场放入不同的桶中,并将机场代码与桶中的值异或。还要跟踪每个桶中有多少源机场和目的地机场。
处理完所有机票后,选择其中一个桶。发送到该桶中的源机场数量应比发送到该桶中的目的地机场数量多一或少一。如果源机场数少于目的地机场数,则表示初始源机场(唯一的源机场)被发送到了另一个桶中。这意味着当前桶中的值是初始源机场的标识符!反之,如果目的地机场数少于源机场数,则最终目的地机场被发送到了另一个桶中,因此当前桶是最终目的地机场的标识符!
struct ticket
{
    int src;
    int dst;
};

int get_airport_bucket_index(
    int airport_code, 
    int discriminating_bit)
{
    return (airport_code & discriminating_bit)==discriminating_bit ? 1 : 0;
}

void find_trip_endpoints(const ticket *tickets, size_t ticket_count, int *out_src, int *out_dst)
{
    int xor_residual= 0;

    for (const ticket *current_ticket= tickets, *end_ticket= tickets + ticket_count; current_ticket!=end_ticket; ++current_ticket)
    {
        xor_residual^= current_ticket->src;
        xor_residual^= current_ticket->dst;
    }

    // now xor_residual will be equal to the starting airport xor ending airport
    // since starting airport!=ending airport, they have at least one bit that is not in common
    // 

    int discriminating_bit= xor_residual & (-xor_residual);

    assert(discriminating_bit!=0);

    int airport_codes[2]= { xor_residual, xor_residual };
    int src_count[2]= { 0, 0 };
    int dst_count[2]= { 0, 0 };

    for (const ticket *current_ticket= tickets, *end_ticket= tickets + ticket_count; current_ticket!=end_ticket; ++current_ticket)
    {
        int src_index= get_airport_bucket_index(current_ticket->src, discriminating_bit);

        airport_codes[src_index]^= current_ticket->src;
        src_count[src_index]+= 1;

        int dst_index= get_airport_bucket_index(current_ticket->dst, discriminating_bit);
        airport_codes[dst_index]^= current_ticket->dst;
        dst_count[dst_index]+= 1;
    }

    assert((airport_codes[0]^airport_codes[1])==xor_residual);
    assert(abs(src_count[0]-dst_count[0])==1); // all airports with the bit set/unset will be accounted for as well as either the source or destination
    assert(abs(src_count[1]-dst_count[1])==1);
    assert((src_count[0]-dst_count[0])==-(src_count[1]-dst_count[1]));

    int src_index= src_count[0]-dst_count[0]<0 ? 0 : 1; 
    // if src < dst, that means we put more dst into the source bucket than dst, which means the initial source went into the other bucket, which means it should be equal to this bucket!

    assert(get_airport_bucket_index(airport_codes[src_index], discriminating_bit)!=src_index);

    *out_src= airport_codes[src_index];
    *out_dst= airport_codes[!src_index];

    return;
}

int main()
{
    ticket test0[]= { { 1, 2 } };
    ticket test1[]= { { 1, 2 }, { 2, 3 } };
    ticket test2[]= { { 1, 2 }, { 2, 3 }, { 3, 4 } };
    ticket test3[]= { { 2, 3 }, { 3, 4 }, { 1, 2 } };
    ticket test4[]= { { 2, 1 }, { 3, 2 }, { 4, 3 } };
    ticket test5[]= { { 1, 3 }, { 3, 5 }, { 5, 2 } };

    int initial_src, final_dst;

    find_trip_endpoints(test0, sizeof(test0)/sizeof(*test0), &initial_src, &final_dst);
    assert(initial_src==1);
    assert(final_dst==2);

    find_trip_endpoints(test1, sizeof(test1)/sizeof(*test1), &initial_src, &final_dst);
    assert(initial_src==1);
    assert(final_dst==3);

    find_trip_endpoints(test2, sizeof(test2)/sizeof(*test2), &initial_src, &final_dst);
    assert(initial_src==1);
    assert(final_dst==4);

    find_trip_endpoints(test3, sizeof(test3)/sizeof(*test3), &initial_src, &final_dst);
    assert(initial_src==1);
    assert(final_dst==4);

    find_trip_endpoints(test4, sizeof(test4)/sizeof(*test4), &initial_src, &final_dst);
    assert(initial_src==4);
    assert(final_dst==1);

    find_trip_endpoints(test5, sizeof(test5)/sizeof(*test5), &initial_src, &final_dst);
    assert(initial_src==1);
    assert(final_dst==2);

    return 0;
}

不完全准确-它实际上是一个链表(我想这是一个DAG)。问题在于你没有指针,只有随机顺序的元素名称。 - Niki Yoshiuchi
@Niki,你仍然可以对其进行拓扑排序以获得正确的顺序。或者你可以按照航班到达或离开时间进行排序。 - MSN
@Niki,这些票是边缘(src->dst)和节点(trip)。鉴于它们形成了一个DAG,您必须执行拓扑排序以确定它们的顺序。这里提出的所有解决方案都是拓扑排序的具体实现。是的,您需要整个图来进行拓扑排序。就像对于任何潜在的解决方案一样,因为在最坏的情况下,您处理的最后两张票是起始和结束票。 - MSN
你是正确的。我最初的评论措辞不当 - 我的意思是,它并不像仅仅进行拓扑排序那样简单,因为你不能在O(1)时间内查找一条边而不先执行其他操作。你需要先建立一个邻接表,然后再进行拓扑排序。如果我的话不太清晰,对此我很抱歉。 - Niki Yoshiuchi
按照不存在的数据“日期/时间”排序会导致数万亿级别的问题。 - Greg Domjan
显示剩余3条评论

4
创建两个数据结构:
Route
{
  start
  end
  list of flights where flight[n].dest = flight[n+1].src
}

List of Routes

然后:

foreach (flight in random set)
{
  added to route = false;
  foreach (route in list of routes)
  {
    if (flight.src = route.end)
    {
      if (!added_to_route)
      {
        add flight to end of route
        added to route = true
      }
      else
      {
        merge routes
        next flight
      }
    }
    if (flight.dest = route.start)
    {
      if (!added_to_route)
      {
        add flight to start of route
        added to route = true
      }
      else
      {
        merge routes
        next flight
      }
    }
  }
  if (!added to route)
  {
    create route
  }
}

3

输入两个哈希表:

to_end = src -> des; to_beg = des -> src

选择任意一个机场作为起点S。

while(to_end[S] != null)
   S = to_end[S];

S现在是你的最终目的地。使用另一张地图重复此步骤以找到你的起点。

如果没有适当的检查,这可能会感觉像O(N),前提是你有一个不错的哈希表实现。


2
你只需将这个票据链传递下去,最终你就能找到目的地。 - George Godik
1
这也是我想出来的,建立每个字典应该是O(n),查找起点应该是O(n)(这种方式不需要查找终点),重构行程也是O(n)。因此,基本上是4 * O(n),这基本上意味着O(n)。 - Lasse V. Karlsen
经过重新阅读,实际上并不需要找到结尾,我只是发现它被遗忘了。 - wowest

3
哈希表对于大规模的数据集(例如原问题中的数十亿)不适用;任何使用过它们的人都知道,它们只适用于小型数据集。相反,您可以使用二叉搜索树,这将为您提供O(n log n)的复杂度。
最简单的方法是进行两次遍历:第一次将它们全部添加到树中,以src为索引。第二次遍历树并将节点收集到数组中。
我们能做得更好吗?如果我们真的想的话,我们可以在一次遍历中完成。将每个机票表示为链表上的一个节点。最初,每个节点的next指针都为空值。对于每张机票,将其src和dest输入索引。如果发生冲突,那意味着我们已经有了相邻的机票;连接节点并从索引中删除匹配项。完成后,您只需要进行一次遍历,就会获得一个空索引和按顺序排列的所有机票的链表。
这种方法显着更快:只需要一次遍历而不是两次,并且存储空间显着较小(最坏情况:n/2;最好情况:1;典型情况:sqrt(n)),足以让您可能实际上使用哈希表而不是二叉搜索树。

一些引用会很好(sqrt(n)从哪里来?为什么哈希表只适用于“小”集合?“小”有多小?)除此之外,非常好的答案,特别是作为第一篇文章。-> +1 - meriton
当然:首先,让我们看看为什么这个存储比其他提议要小。每次我们进行匹配时,都会从存储中删除两个条目。我们可以同时拥有的最多未匹配条目是n/2-下一个需要匹配“某些东西”。最坏情况=n/2。最好的情况是每个条目都匹配了某些东西,所以我们在1张票和没有票之间波动。通常在这些极端之间。我们的存储越大,下一个条目与其中的“某些东西”匹配的可能性就越大;因此,随着存储变得越来越大,它变小的可能性也越大。没有足够的空间来详细说明,但请考虑sqrt(n)。 - SRobertJames
我理解最好和最坏情况分析。我怀疑的是平均情况分析,因为我的直觉告诉我它是线性的。尝试证明:假设商店中有i个段落。如果在所有段落中随机选择一个新段落插入,则平均而言,它与2 * i / n个节点相邻。也就是说,E[new_size] = E[old_size] + 1 - 2 * old_size / n。也就是说,这个迭代的固定点s满足s = s + 1 - 2s/n,等价于s = n/2。是的,我做了许多简化假设,但我怀疑任何一个都不会渐近相关。 - meriton
"2 * i / n" 不正确,应该是 "2 * i / (n - d)" 其中 d 是到目前为止删除的配对数。至于哈希 - 为了高效,不能有太多冲突。随着 n 的增长,大块发生的可能性会增加;避免冲突所需的空间大约是 n^2 级别。请参阅 http://theory.cs.uiuc.edu/~jeffe/teaching/algorithms/notes/07-hashing.pdf 。最后,如果我们需要使用磁盘,则忘记哈希 - 只有树才是可行的。 - SRobertJames
1
sqrt(n) 确实不正确;其实是 n/6。不要将其视为一个顺序过程;相反,问自己,如果我拿走了 n 的 x 部分,那么我手里有多少期望的数量?没有匹配项时是 xn,但是如果放弃匹配项,则为 (x)(x-1)n。在 x 从0到1范围内积分,我们得到 n/6。 - SRobertJames
我认为你的空间复杂度可能有点问题。当然,你的哈希表大小会减半,但是你的部分旅行数据结构将比票据对象大一倍。你的平均内存使用率会降低,但最坏情况下仍然很糟糕。 - Jason

2
这是一个单路径状态机矩阵的简单案例。 很抱歉伪代码采用C#风格,但使用对象更容易表达思想。
首先,构建一个转换矩阵。 请阅读我对转换矩阵的描述(不要关心FSM答案,只需了解转换矩阵的说明),链接为:如何测试大型状态机?
然而,您所描述的限制使情况变得简单,并且成为了一个简单的单路径状态机。这是可能的最简单的状态机,具有完整的覆盖范围。
对于5个机场的简单情况, 垂直节点=源/入口点, 水平节点=目标/出口点。
   A1 A2 A3 A4 A5
A1        x
A2  x
A3           x
A4              x
A5     x

请注意,对于每一行和每一列,都不应该有超过一个转换。

要获取机器的路径,您需要将矩阵排序为

   A1 A2 A3 A4 A5
A2  x
A1        x
A3           x
A4              x
A5     x

或者将其排序为一个对角方阵 - 有序对的特征向量。
   A1 A2 A3 A4 A5
A2  x
A5     x
A1        x
A3           x
A4              x

有序对列表为:

a2:a1,a5:a2,a1:a3,a3:a4,a4:a5。

或者更正式地表示为:

<a2,a1>, <a5,a2>, <a1,a3>, <a3,a4>, <a4,a5>.

嗯...有序对?在Lisp中闻到了递归的味道?

<a2,<a1,<a3,<a4,a5>>>>

这个机器有两种模式:

  1. 旅行计划 - 你不知道有多少个机场,需要一个通用的旅行计划来规划未知数量的机场。
  2. 旅行重建 - 你拥有一次过去旅行的所有收费站票据,但它们都堆在你的手套箱/行李袋里。

我猜你的问题是关于旅行重建的。所以,你可以从那堆票中随机挑选一张接一张地查看。

我们假设票堆的大小是无限的。

     tak mnx cda 
bom    0
daj       0
phi           0

当数值为0时,表示未排序的票。我们将未排序的票定义为其目的地与另一张票的出发地不匹配的票。

接下来的票发现mnx(dst) = kul(src)匹配。

     tak mnx cda kul
bom    0
daj       1
phi           0
mnx               0

当您选择下一个机票时,有可能连接两个连续的机场。如果发生这种情况,您将从这两个节点创建一个群集节点:

<bom,tak>, <daj,<mnx,kul>>

矩阵被简化,

     tak cda kul
bom    0
daj          L1
phi       0

在哪里

L1 = <daj,<mnx,kul>>

这是主列表的子列表。

继续挑选下一个随机票。

     tak cda kul svn xml phi
bom    0
daj          L1
phi       0
olm               0
jdk                   0
klm                       0

将现有的目标地址匹配到新的源地址
或将现有的源地址匹配到新的目标地址:

     tak cda kul svn xml
bom    0
daj          L1
olm               0
jdk                   0
klm      L2


<bom,tak>, <daj,<mnx,kul>>, <<klm,phi>, cda>

上述拓扑练习仅用于视觉理解。以下是算法解决方案。
概念是将有序对聚类成子列表,以减轻我们将用于存储票据的哈希结构的负担。逐渐地,会有越来越多的伪票(由匹配的票合并而成),每个伪票都包含一个不断增长的有序目的地子列表。最后,将剩下一个单一的伪票,其中包含其子列表中完整的行程向量。
正如您所看到的,这可能最好使用Lisp完成。
然而,作为链表和映射的练习...
创建以下结构:
class Ticket:MapEntry<src, Vector<dst> >{
  src, dst
  Vector<dst> dstVec; // sublist of mergers

  //constructor
  Ticket(src,dst){
    this.src=src;
    this.dst=dst;
    this.dstVec.append(dst);
  }
}

class TicketHash<x>{
  x -> TicketMapEntry;

  void add(Ticket t){
    super.put(t.x, t);
  }
}

因此,有效地说,
TicketHash<src>{
  src -> TicketMapEntry;

  void add(Ticket t){
    super.put(t.src, t);
  }
}

TicketHash<dst>{
  dst -> TicketMapEntry;

  void add(Ticket t){
    super.put(t.dst, t);
  }
}    

TicketHash<dst> mapbyDst = hash of map entries(dst->Ticket), key=dst
TicketHash<src> mapbySrc = hash of map entries(src->Ticket), key=src

当从堆中随机选取一张票时,

void pickTicket(Ticket t){
  // does t.dst exist in mapbyDst?
  // i.e. attempt to match src of next ticket to dst of an existent ticket.
  Ticket zt = dstExists(t);

  // check if the merged ticket also matches the other end.
  if(zt!=null)
    t = zt;

  // attempt to match dst of next ticket to src of an existent ticket.
  if (srcExists(t)!=null) return;

  // otherwise if unmatched either way, add the new ticket
  else {
    // Add t.dst to list of existing dst
    mapbyDst.add(t); 
    mapbySrc.add(t);
  }
}

检查目标是否存在:

Ticket dstExists(Ticket t){
  // find existing ticket whose dst matches t.src
  Ticket zt = mapbyDst.getEntry(t.src);

  if (zt==null) return false; //no match

  // an ordered pair is matched...

  //Merge new ticket into existent ticket
  //retain existent ticket and discard new ticket.
  Ticket xt = mapbySrc.getEntry(t.src);

  //append sublist of new ticket to sublist of existent ticket
  xt.srcVec.join(t.srcVec); // join the two linked lists.

  // remove the matched dst ticket from mapbyDst
  mapbyDst.remove(zt);
  // replace it with the merged ticket from mapbySrc
  mapbyDst.add(zt);

  return zt;
}

Ticket srcExists(Ticket t){
  // find existing ticket whose dst matches t.src
  Ticket zt = mapbySrc.getEntry(t.dst);

  if (zt==null) return false; //no match

  // an ordered pair is matched...

  //Merge new ticket into existent ticket
  //retain existent ticket and discard new ticket.
  Ticket xt = mapbyDst.getEntry(t.dst);

  //append sublist of new ticket to sublist of existent ticket
  xt.srcVec.join(t.srcVec); // join the two linked lists.

  // remove the matched dst ticket from mapbyDst
  mapbySrc.remove(zt);
  // replace it with the merged ticket from mapbySrc
  mapbySrc.add(zt);

  return zt;
}

检查是否存在src:

Ticket srcExists(Ticket t){
  // find existing ticket whose src matches t.dst
  Ticket zt = mapbySrc.getEntry(t.dst);

  if (zt == null) return null;

  // if an ordered pair is matched

  // remove the dst from mapbyDst
  mapbySrc.remove(zt);

  //Merge new ticket into existent ticket
  //reinsert existent ticket and discard new ticket.
  mapbySrc.getEntry(zt);

  //append sublist of new ticket to sublist of existent ticket
  zt.srcVec.append(t.srcVec);
  return zt;
}

我感觉以上内容有很多错别字,但概念应该是正确的。如果发现任何错误,请请求帮助进行更正。


2
每个机场都是一个节点。每张机票都是一条边。可以使用邻接矩阵来表示图形,这可以作为位字段来压缩边缘。你的起点将是没有路径的节点(它的列为空)。一旦你知道了这一点,你只需要跟随存在的路径即可。
或者,您可以构建一个按机场索引的结构。对于每张机票,您查找它的源和目的地。如果任一方未找到,则需要将新机场添加到列表中。当找到每个机场时,您将出发机场的出口指针设置为指向目的地,并将目的地的到达指针设置为指向出发机场。当您用完机票时,必须遍历整个列表以确定谁没有路径。
另一种方法是拥有一个可变长度的小行程列表,当您遇到每张机票时,将其连接在一起。每次添加机票时,您会看到现有小行程的末端是否与您的机票的源或目标匹配。如果不是,则当前机票成为自己的小行程并添加到列表中。如果是,则新机票将附加到与之匹配的现有行程的末端,可能会将两个现有小行程拼接在一起,在这种情况下,它将缩短小行程列表。

0
我写了一个小的Python程序,使用了两个哈希表,一个用于计数,另一个用于源到目标的映射。 复杂度取决于字典的实现。如果字典具有O(1)的复杂度,则复杂度为O(n),如果字典具有类似STL map的O(lg(n))的复杂度,则复杂度为O(n lg(n))。
import random
# actual journey: a-> b -> c -> ... g -> h
journey = [('a','b'), ('b','c'), ('c','d'), ('d','e'), ('e','f'), ('f','g'), ('g','h')]
#shuffle the journey.
random.shuffle(journey)
print("shffled journey : ", journey )
# Hashmap to get the count of each place
map_count = {}
# Hashmap to find the route, contains src to dst mapping
map_route = {}

# fill the hashtable
for j in journey:
    source = j[0]; dest = j[1]
    map_route[source] = dest
    i = map_count.get(source, 0)
    map_count[ source ] = i+1
    i = map_count.get(dest, 0)
    map_count[ dest ] = i+1

start = ''
# find the start point: the map entry with count = 1 and 
# key exists in map_route.
for (key,val) in map_count.items():
    if map_count[key] == 1 and map_route.has_key(key):
        start = key
        break

print("journey started at : %s" % start)
route = [] # the route
n = len(journey)  # number of cities.
while n:
    route.append( (start, map_route[start]) )
    start = map_route[start]
    n -= 1

print(" Route : " , route )

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接