title: 多叉树treelib&区间树intervaltree
subtitle: 多叉树相关以及区间树三方库介绍
date: 2021-03-04
author: NSX
catalog: true
tags:
- 多叉树treelib
- 区间树intervaltree


  • treelib 库是一个 Python 的第三方库。这个库实现了一些多叉树相关的常用方法。
  • intervaltree库是一个可变的、自平衡的区间树。查询可以按点、按范围重叠或按范围包含。

    treelib 库

用于构建多叉树

  1. #创建一棵多叉树
  2. #show(): 将多叉树按树形结构展示输出
  3. from treelib import Tree, Node
  4. tree = Tree()
  5. tree.show()
  6. print(tree.identifier)
  7. #添加节点到多叉树中
  8. tree.create_node(tag='Node-5', identifier='node-5', data=5)
  9. tree.create_node(tag='Node-10', identifier='node-10', parent='node-5', data=10)
  10. tree.create_node('Node-15', 'node-15', 'node-10', 15)
  11. tree.show()
  12. node = Node(tag='Node-A', identifier='node-A', data='A')
  13. tree.add_node(node, parent='node-5')
  14. tree.show()
  15. """
  16. Node-5
  17. └── Node-10
  18. └── Node-15
  19. Node-5
  20. ├── Node-10
  21. │ └── Node-15
  22. ├── Node-A
  23. """
  24. #节点的属性和方法
  25. print(node)
  26. print('node id: ', node.identifier)
  27. print('node tag:', node.tag)
  28. print('node data:', node.data)
  29. print('node is leaf: ', node.is_leaf())
  30. print('node is root: ', node.is_root())
  31. #多叉树中的节点个数
  32. print('tree len: ', len(tree))
  33. print('tree size:', tree.size())
  34. #多叉树的深度和叶子节点
  35. print('tree depth:', tree.depth())
  36. print('node-20 depth:', tree.depth(node='node-20'))
  37. print('node-20 level:', tree.level('node-20'))
  38. print('tree leaves:', tree.leaves()) #返回多叉树的所有叶节点
  39. print(tree.paths_to_leaves()) #返回根节点到每个叶节点的路径上的所有节点id
  40. #返回多叉树中的节点
  41. print('tree nodes:', tree.nodes)
  42. print(tree.all_nodes())
  43. for node in tree.all_nodes_itr():
  44. print(node)
  45. #多叉树转换成字典和保存到文件中
  46. print(tree.to_dict())
  47. print(tree.to_json())
  48. tree.to_graphviz()
  49. tree.save2file('demo_tree.tree')

应用:通过多叉树解决嵌套实体的问题

  1. class Nodex(object):
  2. def __init__(self, tmp):
  3. self.tmp = tmp
  4. def cross_judge(item_a, item_b):
  5. """判断实体之间是否存在交集
  6. """
  7. a1, b1 = item_a["char_offset"], item_a["char_offset"] + item_a["char_length"]
  8. a2, b2 = item_b["char_offset"], item_b["char_offset"] + item_b["char_length"]
  9. if max(a1, a2) < min(b1, b2):
  10. return True
  11. else:
  12. return False
  13. def get_slot_dic_from_tree(result_tmp):
  14. """通过多叉树输出嵌套实体(非包含关系)的排列组合结果
  15. result_tmp: 实体相关信息 ↑
  16. """
  17. tree1 = Tree()
  18. tree1.create_node("Root", "root", data=Nodex(-1)) # 根节点
  19. root2leave = []
  20. if result_tmp:
  21. result_tmp.sort(key=lambda x: x["char_offset"])
  22. # 第一个new_node直接加入tree
  23. tree1.create_node("Child0", "child0", parent="root", data=Nodex(result_tmp[0]))
  24. for i in range(1, len(result_tmp)):
  25. parent_idt_list = []
  26. # 每个new_node与所有的叶子结点进行比较
  27. for j, leaf in enumerate(tree1.leaves()):
  28. idt = leaf.identifier # 叶子结点标识符
  29. parent_idt = tree1.parent(idt).identifier # 父结点标识符
  30. node_a = Node(
  31. tag="Child" + str(i) + "_" + str(j),
  32. identifier="Child" + str(i) + "_" + str(j),
  33. data=Nodex(result_tmp[i]),
  34. )
  35. # 1、如果存在嵌套(冲突),则父节点增加新分支
  36. if cross_judge(result_tmp[i], leaf.data.tmp):
  37. # 针对相同的new_node,父节点仅可增加一个新分支
  38. if parent_idt not in parent_idt_list:
  39. tree1.add_node(node_a, parent=parent_idt)
  40. parent_idt_list.append(parent_idt)
  41. # 2、否则,当前节点加深
  42. else:
  43. tree1.add_node(node_a, parent=idt)
  44. # 最后返回【根节点→叶子结点】的所有路径,即所有不存在嵌套实体的排列组合结果
  45. for path in tree1.paths_to_leaves():
  46. item = [tree1.get_node(idt).data.tmp for idt in path[1:]]
  47. root2leave.append(item)
  48. return root2leave

图示解决过程:
NER核心处理逻辑.drawio.png

intervaltree库

判断多个区间是否有重叠:利用intervaltree构建区间树,也即构建一个二叉排序树,树的叶节点是每个区间。同时,树的查询效率和树的深度是有关系的,所以构建一个相对平衡的二叉排序树也能进一步提高效率,比如红黑树。算法导论上介绍了这种数据结构,节点是区间的红黑树——区间树

  1. >>> from intervaltree import Interval, IntervalTree
  2. >>> tree = IntervalTree()
  3. >>> iv = Interval(4, 7)
  4. >>> tree.add(iv)
  5. >>> target_iv = Interval(5, 8)
  6. >>> tree.overlaps(target_iv)
  7. True
  8. >>> target_iv_2 = Interval(7, 10)
  9. >>> tree.overlaps(target_iv_2)
  10. False

应用:如“今天8点到10点, 8个人”提取时间和人数时,idx=text.find(x)只能查找第一个时间“8”出现位置的索引,导致人数“8”的位置被覆盖了。解决:在发现实体区间有重叠时,循环执行 idx=text.find(x, idx+1),直到-1跳出;

  1. # 寻找最适合的索引
  2. interval_tree = IntervalTree() # 用于判断实体区间的重叠情况!
  3. char_offset = text.find(x.upper()) # first
  4. while char_offset!=-1:
  5. target_iv = Interval(char_offset, char_offset+len(x))
  6. is_overlaps = bool(interval_tree.overlaps(target_iv))
  7. if is_overlaps: # 有重叠,则继续找,直到-1跳出
  8. char_offset = text.find(x.upper(), char_offset+1)
  9. else: # 无重叠,直接跳出
  10. interval_tree.add(target_iv)
  11. break
  12. if char_offset==-1:
  13. char_offset = text.find(x.upper()) # first

参考

Python treelib库创建多叉树的用法介绍

Python多叉树的构造及取出节点数据(treelib)的方法

treelib官方文档

判断多个区间是否有重叠

区间重叠计算及IntervalTree初识