【问题标题】:Time complexity of os.walk in PythonPython中os.walk的时间复杂度
【发布时间】:2013-12-11 21:04:04
【问题描述】:

我必须计算算法的时间复杂度,但我在其中调用 os.walk,我不能将其视为单个操作,而是很多。

os.walk 的来源让我感到困惑,因为文件树可能以多种方式排序(一个文件夹中的 1.000.000 个文件或每个文件夹中的一个文件和 1.000.000 个文件夹。

我不是时间复杂性方面的专家,我不能确定我应该只考虑一个操作还是多个操作,所以这让我陷入了困境。不要计算 simlink 标志,我假设它设置为 false 以忽略它们。

PD:我在 Komodo IDE 中找到了 os.walk 的源代码,但我不知道如何找到它们作为 javadocs。

【问题讨论】:

  • 我不知道你所说的 Python 的“javadocs”是什么意思。但是您可以在主仓库中在线找到所有源代码。例如,3.3 的 os.walkhere
  • 您不一定需要知道每个函数需要多少次操作,只要它对您正在调查的变量是不变的即可。如果 N 与您目录中的文件数量完全不相关,那么您可以将walk 视为常数时间。
  • 感谢源链接。我一直在寻找这样的东西,但没有运气。我在 IDE 中找到了它,但这是一个便宜的技巧。

标签: python python-3.x time-complexity


【解决方案1】:

嗯...让我们来看看源代码:)

文档:http://docs.python.org/2/library/os.html#os.walk

def walk(top, topdown=True, onerror=None, followlinks=False):
    islink, join, isdir = path.islink, path.join, path.isdir

    try:
        # Note that listdir and error are globals in this module due
        # to earlier import-*.


        # Should be O(1) since it's probably just reading your filesystem journal
        names = listdir(top)
    except error, err:
        if onerror is not None:
            onerror(err)
        return

    dirs, nondirs = [], []


    # O(n) where n = number of files in the directory
    for name in names:
        if isdir(join(top, name)):
            dirs.append(name)
        else:
            nondirs.append(name)

    if topdown:
        yield top, dirs, nondirs

    # Again O(n), where n = number of directories in the directory
    for name in dirs:
        new_path = join(top, name)
        if followlinks or not islink(new_path):

            # Generator so besides the recursive `walk()` call, no additional cost here.
            for x in walk(new_path, topdown, onerror, followlinks):
                yield x
    if not topdown:
        yield top, dirs, nondirs

由于它是一个生成器,这一切都取决于你在树上走多远,但它看起来像 O(n) 其中n 是给定路径中的文件/目录的总数。

【讨论】:

  • 感谢沃尔夫的快速回复。我想知道在最后几行对 walk() 的递归调用是否不会增加复杂度 O(N*m) 其中 N 是文件总数,m 是根目录下的最小子文件夹数量。毕竟,您是在逐个分支地递归。
  • 在我的n 中,我同时包含文件和目录。递归只会使n 更大,因为它将处理更多文件/目录,但它仍然只会遍历每个文件/目录一次。所以最后你的n 将是路径中所有文件和目录的总数。
【解决方案2】:

评论太长了:在 CPython 中,yield 将其结果传递给直接调用者直接传递给结果的最终消费者。因此,如果您的递归深入到R 级别,则每个级别的yields 链将结果备份到最终消费者的调用堆栈需要O(R) 时间。还需要 O(R) 时间来恢复 R 级别的递归调用以返回到第一个 yield 发生的最低级别。

因此,walk() 编辑的每个结果 yield' 所花费的时间与目录树中第一个结果 yield'ed 的级别成正比。

这是理论上的 ;-) 事实。然而,在实践中,除非递归非常深,否则这几乎没有区别。这是因为yields 链和发电机恢复链是“以 C 速度”发生的。换句话说,它确实花费了O(R) 时间,但常数因子是如此之小,大多数程序都不会注意到这一点。

对于像walk() 这样的递归生成器尤其如此,它几乎从不深度递归。谁拥有嵌套 100 层的目录树?不,我也没有;-)

【讨论】:

    【解决方案3】:

    os.walk(除非您对其进行修剪,或存在符号链接问题)保证将子树中的每个目录仅列出一次。

    因此,如果您假设列出目录与目录中的条目数成线性关系,* 那么如果您的子树中有 N 个目录条目,os.walk 将花费 O(N) 时间。

    或者,如果您希望walk 产生每个值(根、目录名、文件名元组)的时间:如果这 N 个目录条目被拆分到 M 个子目录中,那么 M 次迭代中的每一个都需要摊销 O(N /M) 时间。


    * 真的,这取决于您的操作系统、C 库和文件系统,而不是 Python,而且对于旧文件系统,它可能比 O(N) 更糟糕……但我们忽略这一点。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2018-11-20
      • 2019-01-15
      • 2016-08-20
      • 1970-01-01
      • 2021-08-08
      • 1970-01-01
      • 2019-06-14
      • 1970-01-01
      相关资源
      最近更新 更多