【问题标题】:Is there a built-in data structure in Python with BST semantics? [closed]Python 中是否有具有 BST 语义的内置数据结构? [关闭]
【发布时间】:2020-06-26 09:19:00
【问题描述】:

在其他语言中,存在具有二叉搜索树语义的内置数据结构,例如 C++ 的std::map

请注意,我使用 C++ 作为示例,但并不是因为我试图在 C++ 本身和 Python 之间进行直接比较。我在这个问题上获得了“基于意见”的密切投票,但没有关于这个问题的“基于意见”

通过二叉搜索树语义,我的意思是:

  • 我可以在亚线性时间内将元素插入到结构中,即 std::map 的 O(logn)
  • 我可以在线性时间内以 排序顺序 遍历所有元素,即 std::map 的 O(n)

Python中有没有二叉搜索树语义的数据结构?

为了说明,下面的程序可以用内置的 Python 数据结构高效地编写吗?

假设我是一家银行的基金经理。银行账户经常被添加到我的基金中。银行账户有一个 ID 和一个值。较小的 ID 意味着银行帐户的创建时间早于具有较大 ID 的帐户。每次将帐户添加到我的基金时,我都想知道我的基金中第 1000 个最早的帐户的 ID 号,以供记录。

以下是 Python 3 和 C++ 中的实现:

from random import randint

dist = lambda: randint(0, 2147483647)

def main():
    my_map = {}

    # fills a map with 1000000 random mappings of type (<int>: <int>)
    for i in range(0, 1000000):
        my_map[dist()] = dist()

    # prints out the 1000th smallest key and its value
    target_key = nth_smallest_key(1000, my_map)
    print("({}: {})".format(target_key, my_map[target_key]))

    # writes a new random mapping to the map
    # then prints out the 1000th smallest key and its value if that key
    # has changed
    # 100000 times
    for i in range(100000):
        my_map[dist()] = dist()

        test_key = nth_smallest_key(1000, my_map)
        if target_key != test_key:
            target_key = test_key
            print("({}: {})".format(target_key, my_map[target_key]))

        # print an indicator every 100 iterations for comparison
        if i % 100 == 0:
            print("iteration: {}".format(i))

def nth_smallest_key(n, m):
    return sorted(m.keys())[n]

if __name__ == "__main__":
    main()
#include <cstdio>
#include <map>
#include <random>
using namespace std;

int nth_smallest_key(int n, map<int, int>& m);

int main() {
    random_device rd;
    mt19937 psrng(rd());
    uniform_int_distribution<> dist(0, 2147483647);

    map<int, int> my_map;

    // fills a map with 1000000 random mappings of type (<int>: <int>)
    for (int i = 0; i < 1000000; i++) {
        my_map[dist(psrng)] = dist(psrng);
    }

    // prints out the 1000th smallest key and its value
    int target_key = nth_smallest_key(1000, my_map);
    printf("(%d: %d)\n", target_key, my_map[target_key]);

    // writes a new random mapping to the map
    // then prints out the 1000th smallest key and its value if that key
    // has changed
    // 100000 times
    for (int i = 0; i <= 100000; i++) {
        my_map[dist(psrng)] = dist(psrng);

        int test_key = nth_smallest_key(1000, my_map);
        if (target_key != test_key) {
            target_key = test_key;
            printf("(%d: %d)\n", target_key, my_map[target_key]);
        }
    }

    return 0;
}

int nth_smallest_key(int n, map<int, int>& m) {
    map<int, int>::iterator curr = m.begin();
    for (int i = 0; i < n; i++) {
        curr = next(curr);
    }
    return curr->first;
}

生成文件:

buildcpp:
        g++ -o main bst_semantics_cpp.cpp

runcpp: buildcpp
        ./main

runpython:
        python3 bst_semantics_python.py

在 C++ 中,这个程序在我的机器上运行大约需要 5 秒

$ time ./main
(2211193: 2021141747)
(2208771: 1079444337)
(2208700: 1187119077)
(2208378: 1447743503)
...
(1996019: 1378401665)
(1989217: 1457497754)
(1989042: 1336229409)

real    0m4.915s
user    0m4.750s
sys     0m0.094s
$ 

但是,在 Python 中,程序在 120 秒后还没有达到 1% 的完成度

$ time make runpython
python3 bst_semantics_python.py
(2158070: 1498305903)
iteration: 0
iteration: 100
iteration: 200
^CTraceback (most recent call last):
  File "bst_semantics_python.py", line 36, in <module>
    main()
  File "bst_semantics_python.py", line 23, in main
    test_key = nth_smallest_key(1000, my_map)
  File "bst_semantics_python.py", line 33, in nth_smallest_key
    return sorted(m.keys())[n]
KeyboardInterrupt
Makefile:8: recipe for target 'runpython' failed
make: *** [runpython] Error 1


real    2m2.040s
user    1m59.063s
sys     0m0.375s
$ 

要高效地编写这个程序,你需要一个具有二叉搜索树语义的数据结构。

Python中有这样的数据结构吗?你能用内置的 Python 数据结构高效地编写这个程序吗?

【问题讨论】:

  • 我的猜测是 C++ 键是排序的,所以你总是很快得到最小的。这是真的吗?
  • std::map 实现为二叉搜索树,可以按排序顺序遍历
  • 有些库提供排序字典,例如:grantjenks.com/docs/sortedcontainers
  • 部分问题是smallest_key 是您正在对整个数组进行排序以获取最小的元素,而您可以使用min(my_map)
  • sorted 调用强制 Python 遍历未排序键的列表并对其进行排序。一个更快的实现应该是min(my_map.keys())

标签: python data-structures binary-search-tree


【解决方案1】:

首先,我不会说这是一个苹果对苹果的比较。我不能说你的 c++ 是否是惯用的,所以我不会在那里发表评论。我可以说python不是。我在这里看到的最大问题是重新定义较小键的代码可以以更快的方式完成:

# this is the second for loop, not the first
for i in range(100000):
    k, v = dist(), dist()
    my_map[k] = v

    # this avoids any new iteration through min or sorted
    min_key = min_key if min_key <= k else k

这避免了对您已经使用min迭代的数据结构进行不必要的迭代。

你还有一个额外的if检查打印在python代码的底部,而不是在你的c++代码的底部。

我看到它使用 python 3.6 在不科学的 4 秒内运行,没有其他优化。

为什么要避免排序?不是更快吗?

是的,sorted 在技术上更快,用大 O 表示法,但这还不是全部。使用sorted,您还需要为创建新的数据结构并填充它而付费,这似乎会导致纸上的任何收益在实践中消失:

python -m timeit -s "from random import randint; x = [randint(0, 100000) for i in range(10000)]" "min(x)"
2000 loops, best of 5: 126 usec per loop

python -m timeit -s "from random import randint; x = [randint(0, 100000) for i in range(10000)]" "sorted(x)"
200 loops, best of 5: 953 usec per loop

您可能会指出,此测试有些随机,重复测试可能会产生不同的结果,但我可以说,min 在查找数据集合中的最小元素时始终明显更快。

实际问题

为什么 python 没有与 c++ 的map 类似的映射?可能是因为这是 python 而不是 c++。听起来很刻薄,在图书馆等方面做得很好的人要么已经实现了它,而我只是不知道(似乎有道理,所以对此持保留态度),或者它只是不值得时间。如果速度真的那么重要,而且你对 c++ 有很好的掌握,那么我不确定转向 python 会给你带来什么。

编辑:

通读 cmets,Unholy Sheep 建议使用 sortedcontainers 库,该库似乎满足排序字典(地图)的要求

【讨论】:

  • 我已更新我的问题以更清楚地说明问题。事实上,Python 没有任何具有二叉搜索树语义的内置数据结构,这不仅是在实践中使用它的一大缺点,而且意味着“Python 程序员”不知道他们可以有效地执行这些类型的操作.我的问题是 Python 标准库中是否有类似 sortedcontainers 的东西。似乎答案是否定的。
  • Python 不需要在标准库中包含 everything。它是一种通用语言,因此包含所有内容会造成严重的膨胀,因为 python 比 c++ 大一点。此外,无论不包括什么,您通常都可以 pip install ,所以我不会称之为缺点。你确定 python 程序员不知道如何编写高效的 BST 代码吗?因为我优化了你的python 没有“二叉搜索树语义”,它的运行速度几乎一样快。恕我直言,但您似乎已经决定了语言/图书馆/等的偏好
  • 这就像python虽然在数据科学中很流行,但它没有内置的数据框或多维数组库。为什么?已经存在一些很棒的,很容易 pip 安装,如果它们还不够,那种会调用 numpypandas 不足的程序员将是一个足够好的程序员,他们可以自己编写。 TL;DR:pip install &lt;library&gt; 不应被视为缺点,IMO
  • 我喜欢 Python 作为一门语言,这就是为什么我很失望在某些无法安装库的情况下无法使用它(我相信您可以想到某些情况)其中“使用库”不是“允许的”)。此外,您的答案目前并未优化问题中的代码,该代码已被编辑。
  • 没有看到你的编辑,所以在那儿承认。 真的没有遇到任何不允许图书馆的情况。但是,我的意见是基于我的经验,其他人有其他经验,因此意见不同
【解决方案2】:

Python 没有内置数据结构,无法有效地表达和实现此类程序。

【讨论】:

    猜你喜欢
    • 2010-10-14
    • 2018-10-22
    • 1970-01-01
    • 2013-01-18
    • 2015-08-20
    • 2010-09-05
    • 2013-01-18
    • 1970-01-01
    相关资源
    最近更新 更多