字典树的创建以及增删改查方法详解(Python实现)

《自然语言处理入门》-2.4字典树 学习笔记

字典树的定义

参考博客:https://blog.csdn.net/weixin_39778570/article/details/81990417

这里还有一个映射的概念,图中能表示一个个字符串的集合,除了集合的功能之外,字典树也可以实现映射,只需将相应的值悬挂在键的终点节点上即可,后续的代码实现也是关于字典树的映射的。

字典树节点和树的Python代码实现

class Node(object):
    def __init__(self, value):
        # 该节点的子节点,是一个字典,键是单字char,而值是一个Node
        self._children = {}
        # 该节点对应的词,实现映射,如果不是一个词语的的结尾,那么value值始终为None,若是用字典树实现映射,那么词语结尾的value值就是映射值
        self._value = value

    def _add_child(self, char, value, overwrite=False):
        # char是一个字符,首先检查该字符对应的节点是否存在
        child = self._children.get(char)
        print("Node方法中的char:", char)
        print("Node方法中的value:", value)
        print("Node方法中的child:", child)
        print("------------------")
        # 如果该节点不存在,那么插入该节点
        if child is None:
            # 这里的value仍然是None,但是用Node封装了一下作为子节点的值了,因此下次取值的时候就不会为None了
            child = Node(value)
            self._children[char] = child
            print("生成的children:", self._children)
            print("children中单字char对应的值(节点)中的value值:", self._children[char]._value)
            print("=====================")
        elif overwrite:
            child._value = value
        return child


class Trie(Node):
    """
    Trie类继承了Node类,因此可以使用父类的方法
    重载python的方法,使得可以像dict一样操作字典树
    """
    def __init__(self):
        super().__init__(Node)

    def __contains__(self, key):
        return self[key] is not None

    def __getitem__(self, key):
        state = self
        for char in key:
            state = state._children.get(char)
            if state is None:
                return None
        return state._value

    def __setitem__(self, key, value):
        state = self
        for i, char in enumerate(key):
            # 如果没有到对应中文的边的结尾,那么_add_child方法中对应的value都是None值
            if i < len(key) - 1:
                state = state._add_child(char, None, False)
            else:
                state = state._add_child(char, value, True)

字典树的增加

测试代码:

if __name__ == '__main__':
    trie = Trie()
    # 增 对应于setitem方法
    # 这里的中文中的每个字符都是节点中_add_child方法中的char,
    trie['自然'] = 'nature'
    trie['自然人'] = 'human'

测试结果如下:

Node方法中的char: 自
Node方法中的value: None
Node方法中的child: None
------------------
生成的children: {'自': <__main__.Node object at 0x000002ED749F3668>}
children中单字char对应的值(节点)中的value值: None
=====================
Node方法中的char: 然
Node方法中的value: nature
Node方法中的child: None
------------------
生成的children: {'然': <__main__.Node object at 0x000002ED749F36D8>}
children中单字char对应的值(节点)中的value值: nature
=====================
Node方法中的char: 自
Node方法中的value: None
Node方法中的child: <__main__.Node object at 0x000002ED749F3668>
------------------
Node方法中的char: 然
Node方法中的value: None
Node方法中的child: <__main__.Node object at 0x000002ED749F36D8>
------------------
Node方法中的char: 人
Node方法中的value: human
Node方法中的child: None
------------------
生成的children: {'人': <__main__.Node object at 0x000002ED749F3710>}
children中单字char对应的值(节点)中的value值: human
=====================

首先观察第一行代码:

trie['自然'] = 'nature'

代码使用了类似Python中dict的方式来增加节点,这行代码对应的是Trie类中的__setitem__方法

for i, char in enumerate(key):
    # 如果没有到对应中文的边的结尾,那么_add_child方法中对应的value都是None值
    if i < len(key) - 1:
        state = state._add_child(char, None, False)
    else:
        state = state._add_child(char, value, True)

enumerate遍历了“自然”这个字符串的每个单字,在未到达字符串的结尾时,映射值为None,而当

i = len(key) - 1:

时,就将整个中文字符串对应的值作为value传入,调用了父类的_add_child方法来实现增加

对于Node类_add_child方法而言,新增一个节点的操作首先需要判断是否存在这个节点,不存在这个节点,分为两种情况:

1.要插入的节点不是字符串的末尾,也就没有了映射,那么新建节点的键是字符char,而值是一个value为None的节点,尽管value值为None,但是再次访问到这个节点时,child = self._children.get(char)获取到的不为空(这一点十分重要)

2.要插入的节点在字符串的末尾,此时有了一个value的映射值,那么新建节点的键是字符char,而值是一个value为映射值的节点

如果存在这个节点(且overwrite为False),那么直接返回该子节点即可。

 

字典树的查找

测试代码:

assert '自然' in trie

因为重写了__getitem__方法,所以只需采取in的写法就可以了

 

字典树的修改

测试代码:

    # 改
    trie['自然语言'] = 'language'
    trie['自然语言'] = 'human language'
    assert trie['自然语言'] == 'human language'

测试结果:

Node方法中的char: 自
Node方法中的value: None
Node方法中的child: None
------------------
生成的children: {'自': <__main__.Node object at 0x000001D07D5935F8>}
children中单字char对应的值(节点)中的value值: None
=====================
Node方法中的char: 然
Node方法中的value: None
Node方法中的child: None
------------------
生成的children: {'然': <__main__.Node object at 0x000001D07D593668>}
children中单字char对应的值(节点)中的value值: None
=====================
Node方法中的char: 语
Node方法中的value: None
Node方法中的child: None
------------------
生成的children: {'语': <__main__.Node object at 0x000001D07D5936A0>}
children中单字char对应的值(节点)中的value值: None
=====================
Node方法中的char: 言
Node方法中的value: language
Node方法中的child: None
------------------
生成的children: {'言': <__main__.Node object at 0x000001D07D5936D8>}
children中单字char对应的值(节点)中的value值: language
=====================
Node方法中的char: 自
Node方法中的value: None
Node方法中的child: <__main__.Node object at 0x000001D07D5935F8>
------------------
Node方法中的char: 然
Node方法中的value: None
Node方法中的child: <__main__.Node object at 0x000001D07D593668>
------------------
Node方法中的char: 语
Node方法中的value: None
Node方法中的child: <__main__.Node object at 0x000001D07D5936A0>
------------------
Node方法中的char: 言
Node方法中的value: human language
Node方法中的child: <__main__.Node object at 0x000001D07D5936D8>
------------------

同样用的是__setitem__方法,代码的逻辑跟增加的步骤大致相同,不过是进入了Node类_add_child方法的elif分支

输出    assert trie['自然语言'] == 'human language'并没有报错,说明修改成功

 

字典树的删除

测试代码:

    # 删 对应于setitem方法,不过设置对应的值为None
    trie['自然语言'] = 'language'
    trie['自然语言'] = None
    assert '自然语言' not in trie

测试结果:

Node方法中的char: 自
Node方法中的value: None
Node方法中的child: None
------------------
生成的children: {'自': <__main__.Node object at 0x0000025C2C3D3630>}
children中单字char对应的值(节点)中的value值: None
=====================
Node方法中的char: 然
Node方法中的value: None
Node方法中的child: None
------------------
生成的children: {'然': <__main__.Node object at 0x0000025C2C3D36A0>}
children中单字char对应的值(节点)中的value值: None
=====================
Node方法中的char: 语
Node方法中的value: None
Node方法中的child: None
------------------
生成的children: {'语': <__main__.Node object at 0x0000025C2C3D36D8>}
children中单字char对应的值(节点)中的value值: None
=====================
Node方法中的char: 言
Node方法中的value: language
Node方法中的child: None
------------------
生成的children: {'言': <__main__.Node object at 0x0000025C2C3D3710>}
children中单字char对应的值(节点)中的value值: language
=====================
Node方法中的char: 自
Node方法中的value: None
Node方法中的child: <__main__.Node object at 0x0000025C2C3D3630>
------------------
Node方法中的char: 然
Node方法中的value: None
Node方法中的child: <__main__.Node object at 0x0000025C2C3D36A0>
------------------
Node方法中的char: 语
Node方法中的value: None
Node方法中的child: <__main__.Node object at 0x0000025C2C3D36D8>
------------------
Node方法中的char: 言
Node方法中的value: None
Node方法中的child: <__main__.Node object at 0x0000025C2C3D3710>
------------------

输出只需要将最后的映射值置为None即可,复用了__setitem__方法。

 

 

 

 

  • 4
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值