You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于前缀树(Trie)实现Python目录结构生成的性能优化问询

高效实现前缀树(Trie)的目录拆分方案

首先,我得说你的原生实现思路是对的,但性能瓶颈在于每次计算前缀频率都要遍历所有文件名,这在文件数量多的时候会非常慢。先给你一个优化后的原生实现,不用第三方库就能大幅提升性能,然后再讲如何用datrie或pytrie这类高效库来实现。

优化后的原生实现(性能大幅提升)

核心是先一次性统计所有前缀的出现次数,而不是每次计算都遍历所有文件。这样时间复杂度从O(n²)降到了O(所有文件名的总长度),效果立竿见影:

from collections import defaultdict

def count_prefixes(words):
    """一次性统计所有可能前缀的出现次数"""
    prefix_counts = defaultdict(int)
    for word in words:
        for i in range(1, len(word) + 1):
            prefix = word[:i]
            prefix_counts[prefix] += 1
    return prefix_counts

def make_trie_optimized(words, max_freq):
    prefix_counts = count_prefixes(words)
    root = dict()
    for word in words:
        current_dict = root
        for i in range(len(word)):
            letter = word[i]
            current_prefix = word[:i+1]
            if prefix_counts[current_prefix] > max_freq:
                # 前缀对应的文件数超过阈值,继续拆分目录
                current_dict = current_dict.setdefault(letter, {})
            else:
                # 达到阈值,标记为终止节点,停止处理当前文件
                current_dict[letter] = "end"
                break
    return root

# 原路径查询函数可以直接复用
def get_path(image_id, trie):
    result = ""
    current_dict = trie
    for i in range(len(image_id)):
        letter = image_id[i]
        if letter in current_dict:
            result += letter + "/"
            if current_dict[letter] == "end":
                break
            current_dict = current_dict[letter]
    return result

# 测试示例
filenames = ["111", "112", "1341", "2213", "2131", "22222", "11111"]
trie = make_trie_optimized(filenames, 2)
print("优化后的Trie结构:", trie)
for fname in filenames:
    print(f"{fname} -> {get_path(fname, trie)}")

用datrie实现高效Trie

datrie是基于双数组的Trie实现,内存占用小、查询速度极快。它需要先定义字符集(这里是数字),我们可以用它存储每个前缀的状态(是否需要继续拆分):

import datrie
from collections import defaultdict

def build_datrie(words, max_freq):
    # 先统计所有前缀的出现次数
    prefix_counts = defaultdict(int)
    for word in words:
        for i in range(1, len(word)+1):
            prefix = word[:i]
            prefix_counts[prefix] += 1
    
    # 定义字符集:0-9数字
    charset = '0123456789'
    # 创建Trie,值为bool:True=继续拆分,False=终止
    trie = datrie.Trie(charset)
    
    for word in words:
        current_prefix = ""
        for c in word:
            current_prefix += c
            count = prefix_counts[current_prefix]
            if count > max_freq:
                # 需要继续拆分,标记为True(如果还没存的话)
                if current_prefix not in trie:
                    trie[current_prefix] = True
            else:
                # 达到阈值,标记为终止,停止处理当前文件
                if current_prefix not in trie:
                    trie[current_prefix] = False
                break
        # 处理那些整个文件名都需要作为终止节点的情况
        if len(current_prefix) == len(word) and current_prefix not in trie:
            trie[current_prefix] = False
    return trie

def get_path_datrie(image_id, trie):
    path = ""
    current_prefix = ""
    for c in image_id:
        current_prefix += c
        path += c + "/"
        # 遇到终止节点就停止
        if current_prefix in trie and not trie[current_prefix]:
            break
    return path

# 测试
filenames = ["111", "112", "1341", "2213", "2131", "22222", "11111"]
trie = build_datrie(filenames, 2)
for fname in filenames:
    print(f"{fname} -> {get_path_datrie(fname, trie)}")

用pytrie实现

pytrie的SortedPrefixTrie支持便捷的前缀查询,我们可以直接把前缀的计数存在Trie里,查询时逐步判断:

from pytrie import SortedPrefixTrie
from collections import defaultdict

def build_pytrie(words, max_freq):
    prefix_counts = defaultdict(int)
    for word in words:
        for i in range(1, len(word)+1):
            prefix = word[:i]
            prefix_counts[prefix] += 1
    
    # 创建PrefixTrie,存储每个前缀的计数
    trie = SortedPrefixTrie()
    for prefix, count in prefix_counts.items():
        trie[prefix] = count
    return trie

def get_path_pytrie(image_id, trie, max_freq):
    path = ""
    current_prefix = ""
    for c in image_id:
        current_prefix += c
        count = trie[current_prefix]
        path += c + "/"
        if count <= max_freq:
            break
    return path

# 测试
filenames = ["111", "112", "1341", "2213", "2131", "22222", "11111"]
trie = build_pytrie(filenames, 2)
for fname in filenames:
    print(f"{fname} -> {get_path_pytrie(fname, trie, 2)}")

关键逻辑说明

不管用哪种实现,核心逻辑都是一致的:

  • 统计所有前缀的出现次数:即有多少个文件名以该前缀开头
  • 构建Trie时的终止判断:如果当前前缀对应的文件数≤阈值(这里是2),就标记为终止节点,不再继续拆分目录
  • 路径查询:遍历文件名的每个字符,直到遇到终止节点,拼接出目录路径

这样就能保证每个最终目录下的文件数不超过2,同时性能比原生实现提升很多。

内容的提问来源于stack exchange,提问作者user1981275

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.14 09:18:02