树和树林的实现,不懂数据结构的人也能看懂

树常用来表达一对多的关系,比如企业里从总经理到部门到员工的关系,军队中从军、师、团到营、连、排、班的关系,XML文档中各种文档元素之间的组成关系等等都可以用树表示。下图给出了树的一个示例:

 树的一个例子

树由结点构成,有一个唯一的结点称为根,包括根在内每个结点可以有0个、1个或者多个子结点,子结点又可以有自己的子结点,依此类推构成了一个树根在上的倒过来的树形结构。除了根之外,每个结点都有且仅有一个父结点。没有子结点的结点称为叶子。除了根和叶子之外,其他结点称为中间结点。

树以及树所构成的集合树林上的操作主要有:

  1. 在一个指定结点下构建一个新的子结点
  2. 删除一个结点
  3. 获取所有结点
  4. 获取或者更改一个结点所关联的值
  5. 获取一个结点的度(即子结点的数目)
  6. 判断一个结点是否在树上,是不是根结点
  7. 获取或者更改一个结点的父结点
  8. 获取一个结点所在树的根结点、所有子结点、所有子孙结点或者祖先结点
  9. 获取所有的根结点

根据面向对象方法,下面代码创建了一个Forest类。该类的核心数据结构是一个字典型的成员变量.nodes,用来保存每个结点的键和结点的映射关系。结点是_Node类型的对象,每个结点中封装了结点的键(.key)、值(.value)、指向父结点的指针(.parent)和所有子结点的列表(.children)。当新增一个结点时,不但要在.nodes字典中进行登记,还要在父结点的.children列表中添加当前结点。

Forest使用键来引用每个结点,而不是让用户直接操作结点(_Node)对象,这是因为否则的话,用户对结点中数据的改动有可能导致不一致的结果。比如一个结点的父结点是某个结点,但前者在后者的.children列表中没有出现。或者反过来,一个结点在某结点的.children列表中出现,但是其.parent却指向另一个结点。用户在使用Forest时要注意键的唯一性。

值(.value)是结点中保存的用户数据,比如XML文档中的一个元素,企业中的一个部门对象等等。这样就使得结点可以与树之外的数据发生关联。另外,Forest中允许存在多棵树。一个结点如果没有父结点那么它就是一个根。Forest允许存在任意多个根。代码如下:

Python对树林的实现

class Forest:
    def __init__(self):
        self._nodes = {}

    def __contains__(self, key):  # 实现key in self操作,判断key是否在树林中
        return key in self._nodes

    def get_all(self):  # 获取所有结点的key
        keys = self._nodes.keys()
        keys.remove(None)

    def add_child(self, key, child_key, child_value=None):
        """
        在key所对应的结点下增加一个新的子结点
        :param key: key对应父结点, 如果找不到会扔出KeyError, key可以是None
        :param child_key: 子结点的键,如果已被占用则抛出AssertionError
        :param child_value: 子结点的值
        """
        assert child_key not in self._nodes, 'Key %s 已经被占用' % child_key

        if key is None:
            child = _Node(child_key, child_value)
        else:
            parent = self._get_node(key)
            child = _Node(child_key, child_value, parent)
            parent.children.append(child)
        self._nodes[child_key] = child

    def _get_node(self, key):
        # 获取key所对应的结点,如果找不到会扔出KeyError
        return self._nodes[key]

    def remove(self, key):
        """
        删除结点key,并返回该结点的值。如果key不存在会扔出KeyError
        :param key: 指定的key
        :return: 返回指定结点的值
        """
        node = self._get_node(key)
        self._remove(node)
        return node.value

    def _remove(self, node):  # 私有成员方法,不对外公开
        for child in node.children:
            self._remove(child)  # 递归地删除子结点
        node.parent.children.remove(node)  # 从父结点中删除自己
        del self._nodes[node.key]

    def get_value(self, key):
        """
        获取key所对应的值,如果key不存在会扔出KeyError
        :param key: 结点的key
        :return: key所对应的值
        """
        return self._get_node(key).value

    def set_value(self, key, new_value):
        # 设置结点key的值,如果key不存在会扔出KeyError
        self._get_node(key).value = new_value

    def get_parent(self, key):  # 获取父结点的键,根的父结点是None
        p = self._get_node(key).parent
        return None if p is None else p.key

    def set_parent(self, key, new_parent_key):  # 更改结点key的父结点
        me = self._get_node(key)
        np = self._get_node(new_parent_key)
        p = np
        while p is not None:
            if p == me:
                raise AssertionError('被移动的结点%s是目标结点%s的祖先' \
			% (key, new_parent_key))
            p = p.parent
        me.parent.children.remove(me)
        me.parent = np
        np.children.append(me)

    def is_parent(self, parent, key): # 判断结点parent是不是key的父结点
        c = self._get_node(key)
        if parent is None:
            return c.parent is None
        p = self._get_node(parent)
        return c.parent == p

    def is_root(self, key):  # 判断结点key是不是一个根结点
        return self._get_node(key).parent is None

    def get_roots(self):  # 获取所有的根结点
        return [key for key in self._nodes if self._nodes[key].parent is None]

    def get_root(self, key):
        # 获取结点key所在树的根。如果key不存在会扔出KeyError
        node = self._get_node(key)
        while node.parent is not None:
            node = node.parent
        return node.key

    def get_children_num(self, key):
        # 获取子结点的数量
        return len(self._get_node(key).children)

    def get_children(self, key):
        # 获取所有子结点的键
        return [child.key for child in self._get_node(key).children]

    def get_descendants(self, key, root_first:bool=True):
        """
        从结点key开始获取子孙结点的键(含key)。如果key不存在会扔出KeyError
        :param key: 指定的key
        :param root_first: True表示先根序,对每一棵子树来说,根总是出现
        在子树上所有其他结点之前。False表示后根序,根出现在所有其他结点之后。
        """
        return self._get_descendants(self._get_node(key), root_first)

    def _get_descendants(self, node, root_first):  # 私有成员方法,不对外公开
        result = []
        if root_first:
            result.append(node.key)
        for child in node.children:
            children = self._get_descendants(child, root_first)  # 递归调用
            result.extend(children)
        if not root_first:
            result.append(node.key)
        return result

    def get_ancestors(self, key):
        # 从结点key开始由近及远获取所有祖先(含key)。如果key不存在会扔出KeyError
        node = self._get_node(key)
        result = []
        while node is not None:
            result.append(node.key)
            node = node.parent
        return result

    def get_ancestors_to(self, key, ancestor):
        # 从结点key开始由近及远获取到ancestor为止的所有祖先。
        # 如果key不存在会扔出KeyError。如果ancestor并不是key的祖先则扔出KeyError
        # 这个函数常被用来确定两个结点之间是否存在亲缘关系
        node = self._get_node(key)
        result = []
        while node is not None:
            result.append(node.key)
            if node.key == ancestor:
                break
            node = node.parent
        if node is None:
            raise KeyError('%s不是%s的祖先' % (ancestor, key))
        return result

    def __repr__(self):
        result = []
        for root in self.get_roots():
            result.append(_to_str(self._get_node(root), '', '\t'))
        return '\n'.join(result)

def _to_str(node, prefix, unit):
    """
    把以node为根的树转为字符串输出
    :param node: 被输出的结点
    :param prefix: 当前输出的前缀
    :param unit:   输出子结点是需要增加的单位前缀字符
    :return: 以node为根的树转成的字符串
    """
    result = ['%s%s:%s' % (prefix, node.key, node.value)]
    prefix += unit
    for child in node.children:
        result.append(_to_str(child, prefix, unit))
    return '\n'.join(result)


class _Node:  # 结点
    def __init__(self, key, value, parent=None):  # value: 用户数据
        """
        :param key: 结点的key
        :param value: 结点对应的用户数据
        :param parent: 父结点的key
        """
        self.key = key
        self.value = value
        self.parent = parent
        self.children = []  # 所有子结点的列表


if __name__ == '__main__':
    f = Forest()
    f.add_child(None, 'A', 10)
    f.add_child('A', 'B', 11)
    f.add_child('A', 'C', 12)
    f.add_child('A', 'D', 13)
    f.add_child('B', 'B1', 111)
    f.add_child('B', 'B2', 112)
    f.add_child('C', 'C1', 121)
    f.add_child('C', 'C2', 122)
    f.add_child('D', 'D1', 131)
    f.add_child('D', 'D2', 132)

    print(f)

代码中用键来引用结点,成功地隔离了树所要表达的一对多关系与事物本身的联系,降低了它们之间的耦合度。举个例子,一个人可以在一个军队体系中成为一个班的士兵,同时他也可以在一个公司体系中成为一个老总。

代码还采用了面向对象技术,把所有复杂性都封装在Forest类中,用户只需简单地调用Forest提供的成员函数即可。私有类_Node则封装了结点的所有数据,包括值value,父结点parent,子结点列表children以及该结点的键key。这种封装很好地屏蔽了结点内部的复杂性。

比如,代码中用列表children保存所有子结点。学过数据结构中二叉树概念的读者可以把这个列表换成两个指针:first_son和next_brother,前者指向第一个子结点,后者指向下一个兄弟结点。由于每个结点都有这两个指针,所以只需访问当前结点的第一个子结点A,再访问A的下一个兄弟,再访问以及兄弟的兄弟,......,依此类推就可以获得当前结点的所有子结点。

代码中为了实现对树林的打印输出,特别定义了一个全局私有的递归函数_to_str(node, prefix, unit)以便输出以node为根的子树。请读者以递归三步曲分析这个函数为什么能起作用。代码输出:

A:10
	B:11
		B1:111
		B2:112
	C:12
		C1:121
		C2:122
	D:13
		D1:131
		D2:132

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

方林博士

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值