直观地理解O(1)与O(n)时间复杂度

时间:2017-06-11 15:24:45

标签: arrays algorithm memory time-complexity

我知道O(1)是常数时间,这意味着操作不依赖于输入大小,O(n)是线性时间,这意味着操作随输入大小线性变化。

如果我有一个算法可以简单地直接转到数组索引而不是逐个遍历每个索引来找到所需的算法,那么这将被认为是恒定时间而不是线性时间,对吧?这就是教科书所说的。但是,直观地说,我不明白计算机如何以这种方式工作:计算机是否仍然需要逐个遍历每个索引,从0到(可能)n,以便找到特定的索引给它吗?但那么,这与线性时间算法的作用不同吗?

如果有人愿意花时间澄清这一点,我将不胜感激。

编辑:

我对ElKamina回答的回应阐述了我的困惑如何扩展到硬件:

  

但计算机不会检查它的旅程   指数?例如,如果它试图找到索引3,“我就是索引   0所以我需要地址0 + 3“,”好的,现在我在地址1,所以我必须   前进2“,”好吧,现在我在地址2,所以我必须继续前进   1“,”好的,现在我在索引3“。这些与那些不一样   线性时间算法呢?计算机怎么能不这样做   依次?

6 个答案:

答案 0 :(得分:6)

<强>理论

想象一下,你有一个数组按照发生的顺序存储事件。如果每个事件在计算机的内存中占用相同的空间,您就知道该阵列的开始位置,并且您知道您感兴趣的数字事件,那么您可以预先计算每个事件的位置。 / p>

想象一下,您想要存储记录并通过电话号码键入它们。由于数字很多,您可以计算每个数字的hash。您可能应用的最简单的哈希就是将电话号码视为常规号码,并将其模拟为您希望存储号码的阵列的长度。再次,您可以假设每条记录占用相同的空间量,你知道记录的数量,你知道数组的开始位置,你知道感兴趣的事件的偏移量。通过这些,您可以预先计算每个事件的位置。

如果数组项具有不同的大小,则使用指向实际项的指针填充数组。然后,您的查找有两个阶段:找到合适的数组元素,然后将其跟踪到相关项目。

就像我们可以使用shmancy GPS系统来告诉我们地址在哪里,但是我们仍然需要做那里的驾驶工作,访问内存的问题是不知道项目在哪里,它会得到那里。

回答您的问题

考虑到这一点,你的问题的答案是查找几乎从不是免费的,但也很少 O(N)

磁带内存: O(N)

Magnetic tape

Tape memory要求 O(N)寻找,原因显而易见:您必须假脱机并取消放置磁带以将其定位到所需位置。它很慢。它也便宜又可靠,因此它在长期备份系统中至今仍在使用。考虑到磁带物理特性的Special algorithms可以略微加快对它的操作。

请注意,根据前述内容,磁带的问题并不在于我们不知道我们在哪里找到的东西。问题是让物理媒介到达那里。良好的磁带算法的本质是尽量减少在一组操作中假脱机和未循环的磁带总量。

说到这一点,如果我们有两条较短的磁带,而不是一条长磁带,这会减少点对点的旅行时间。如果我们有四个磁带怎么办?

磁盘内存: O(N),但更小

通过将磁带变成一系列环,硬盘驱动器大大缩短了寻道时间。现在,即使磁盘上有 N 内存空间,也可以通过将驱动器头和磁盘移动到适当的位置来快速访问任何一个内存空间。 (弄清楚如何用大写符号来表达这一点是一个挑战。)

Hard disk image

同样,如果您使用速度更快的磁盘或更小的磁盘,则可以optimize performance

RAM:O(1),但有警告

几乎每个回答这个问题的人都会关注RAM,因为那是程序员最常使用的。看看他们的答案是否有更全面的解释。

但是,简单地说,RAM是上面提出的想法的自然延伸。 RAM保存 N 项目,我们知道我们想要的项目在哪里。然而,这一次没有什么需要机械移动才能让我们到达那个项目。此外,我们看到通过使用更多短磁带或更小,更快的驱动器,我们可以更快地到达我们想要的内存。 RAM将这一想法发挥到了极致。

出于实际目的,您可以将RAM视为一个小内存存储库的集合,所有内存存储器都串在一起。您的计算机并不确切知道特定项目在RAM中的位置,只知道它所属的集合。因此它抓住了整个集合,包括数千或数百万字节。它可以像L3缓存一样存储它。

但是该缓存中的特定项目在哪里?再一次,您可以认为计算机并不是真正知道的,它只是抓住了一个保证包含该项目并将其传递给L2缓存的子集。

再次,对于L1缓存。

而且,在这一点上,我们已经从千兆字节(或太字节)的RAM变为3-30千字节。而且,在这个级别,您的计算机(最终)确切地知道该项目的位置并抓住它进行处理。

这种分层行为意味着访问RAM中的相邻项比在RAM中随机访问不同点要快得多。磁带驱动器和硬盘也是如此。

然而,磁带驱动器和硬盘不同,错过所有缓存的最坏情况时间并不依赖于内存量(或者,至少是非常弱依赖的:路径长度,光速和&amp; c)!因此,您可以将其视为内存大小的 O(1)操作。

比较速度

了解这一点,我们可以通过查看Latency Numbers Every Programmer Should Know来讨论访问速度:

Latency numbers

Latency Comparison Numbers
--------------------------
L1 cache reference                           0.5 ns
Branch mispredict                            5   ns
L2 cache reference                           7   ns                      14x L1 cache
Mutex lock/unlock                           25   ns
Main memory reference                      100   ns                      20x L2 cache, 200x L1 cache
Compress 1K bytes with Zippy             3,000   ns        3 us
Send 1K bytes over 1 Gbps network       10,000   ns       10 us
Read 4K randomly from SSD*             150,000   ns      150 us          ~1GB/sec SSD
Read 1 MB sequentially from memory     250,000   ns      250 us
Round trip within same datacenter      500,000   ns      500 us
Read 1 MB sequentially from SSD*     1,000,000   ns    1,000 us    1 ms  ~1GB/sec SSD, 4X memory
Disk seek                           10,000,000   ns   10,000 us   10 ms  20x datacenter roundtrip
Read 1 MB sequentially from disk    20,000,000   ns   20,000 us   20 ms  80x memory, 20X SSD
Send packet CA->Netherlands->CA    150,000,000   ns  150,000 us  150 ms

从更人性的角度来看,这些看起来像:

Minute:

L1 cache reference                  0.5 s         One heart beat (0.5 s)
Branch mispredict                   5 s           Yawn
L2 cache reference                  7 s           Long yawn
Mutex lock/unlock                   25 s          Making a coffee

Hour:

Main memory reference               100 s         Brushing your teeth
Compress 1K bytes with Zippy        50 min        One episode of a TV show (including ad breaks)

Day:

Send 2K bytes over 1 Gbps network   5.5 hr        From lunch to end of work day

Week:

SSD random read                     1.7 days      A normal weekend
Read 1 MB sequentially from memory  2.9 days      A long weekend
Round trip within same datacenter   5.8 days      A medium vacation
Read 1 MB sequentially from SSD    11.6 days      Waiting for almost 2 weeks for a delivery

Year:

Disk seek                           16.5 weeks    A semester in university
Read 1 MB sequentially from disk    7.8 months    Almost producing a new human being
The above 2 together                1 year

Decade:

Send packet CA->Netherlands->CA     4.8 years     Average time it takes to complete a bachelor's degree

答案 1 :(得分:2)

任何时间复杂度计算的基础都是成本模型。成本模型往往过于简单;例如,我们通常会讨论排序算法的时间复杂度,以及我们必须相互比较多少元素。

根据结论索引到数组 O(1)的假设是随机存取存储器;我们可以通过在内存总线的地址线上编码 N 来访问位置 N ,并且该位置的内容返回到数据总线上。如果内存是顺序访问(例如,访问磁带),我们将采用不同的成本模型。

答案 2 :(得分:1)

我们假设存储器是&#34;随机存取存储器&#34; (也称为RAM),而不是磁带或磁盘存储器。在RAM中,您可以在固定时间内访问任何地址。有关其工作原理的更多信息,请参阅相应的wiki文章。

此外,数组的元素按顺序存储。假设我们想要在Java中存储占用4个字节的整数。如果我们想查找第k个元素,我们将直接查看内存中的start + 4 * k位置。

您也可以通过其他方式实现数组。例如,您可以使用链表实现数组,在这种情况下,访问元素需要花费O(n)时间。但这并不是通常如何实现数组的。

答案 3 :(得分:1)

想象一下计算机内存就像桶一样,说你有10个桶。 如果有人告诉你从第8号桶中取出一些东西,你就不会先把手伸进1号到7号桶。你只需将手直接放入桶8即可。

阵列以相同的方式工作,在大多数语言中映射到某种形式的内存布局。所以例如如果你有一个10的字节数组,那将是10个连续的字节。 其他类型的大小可能会有所不同,具体取决于内容是值类型/结构,还是其中数组由指针组成的引用类型。

答案 4 :(得分:1)

这里没有人详细解释为什么(IMO),你可以在O(1)时间内详细地访问它,所以我会尝试:

在我做之前作为一个说明,这可能会使计算机中硬件的复杂程度变得微不足道,但希望它能够沿着正确的道路前进。您将在计算机组织课程中介绍这一点,该课程涉及硬件的内容。

当你有电路时,通过计算机的电压传播速度非常快,返回的结果取决于时钟的脉冲。以此图为例:

https://upload.wikimedia.org/wikipedia/commons/3/3d/Square_array_of_mosfet_cells_read.png

以下是缺少您可以从教科书或课程(或在线)中正确学习的部分,但遗漏这些细节仍然应该为您提供足够的高级概述,以便大致了解其工作原理: / p>

您作为位发送的地址将位于图像的左侧,并根据您发送的地址大小,电压将被正确发送到具有所需数据的正确存储单元。当电池接收到电压后,它会将值发回到底部(这也基本上是即时的),现在您已经读取了存储在存储器中的值。因为你想要的数据已经到了。由于电压传输速度有多快,由于电路中电压变化的速度,几乎可以立即获得结果。这意味着它不依赖于遍历它之前的元素,因为你可以去它,这是RAM背后的想法。瓶颈来自带锁存器的时钟脉冲,当您参加计算机组织课程时,您将看到我们的工作以及我们为什么这样做。

这就是我们认为在O(1)时间内可行的原因。

现在,操作系统和计算机组织课程将向您展示所有关于它如何连接到底层,为什么它的方式比我写的更复杂(甚至可能不是是那么准确了),但希望能让你直截了当地知道为什么我们可以在不断的时间内做到这一点。

由于复杂符号隐藏了引擎盖下的常量(从上面我们可以假设它是恒定时间去内存中的任何偏移量),因此我们可以跳转到任何数组偏移量在O(1)时间从高层次的角度来看 - 这是复杂性分析旨在为我们做的事情 - 与之相比。这也是为什么我们不需要遍历内存中的每个元素来获取我们想要的位置,正如你所说的那样是O(n)。

答案 5 :(得分:0)

假设您所讨论的数据结构是一个向量/数组,您可以通过递增用于迭代它的任何内容来轻松地达到索引'x'。

假设你有一个结构“A”的向量,其中A占用20字节,假设你想要得到索引28,你知道向量从内存位置'x'开始,而不是你只需要去x + 20字节那是你的元素。

对于像列表这样的数据结构,查找时间将为O(n),因为它不是连续分配的,你必须从指针跳转到指针。

使用二叉树,其O(log2(n))......等

所以这里的答案是它取决于你的结构。我建议你阅读一些关于基础数据结构的书籍,这些书籍可能会帮助你更好地理解你正在使用的各种概念。