python 数据结构四 之 二叉树和树

来源:互联网 发布:跳跃网络的游戏 编辑:程序博客网 时间:2024/05/16 00:54

python数据结构教程第四课
树形结构是复杂结构中最简单的一类,这是一类非常重要的结构,在实际中使用广泛,反映了许多计算过程的抽象结构

一、简介
1.树
2.二叉树
二、二叉树和树的抽象数据类型(ADT)
三、二叉树的python实现
1.二叉树的list实现
2.二叉树的遍历操作
3.二叉树的链表实现
四、二叉树的应用——Huffman Tree
五、树的python实现

一、简介

二叉树是树的子集,具有树的全部特性,这里先介绍树的定义与基本特性
1.树
定义:一棵树是n(n>=0)个结点的有限集T,当T非空时满足:
1)T中有且仅有一个特殊结点r称为树T的根
2)除根结点外的其余结点分为m(m>=0)个互不相交的非空有限子集,每个集合为一棵非空树,称为r的子树
结点个数为0的树称为空树
这里写图片描述
树的特性:
1)如果树的结构不空,其中就存在着唯一的起始结点,称为树根
2)按结构的连接关系,树根外的其余结点都有且只有一个前驱,但另一方面,一个结点可以有0个或者多个后继。在非空的树结构中一定有一些结点并不连接到其他结点,与尾结点相似
3)从树根结点出发,经过若干次后继关系可以到达结构中的任意一点
4)结点之间的联系不会形成循环关系
5)从这个结构里的任意两个不同结点出发,通过后继关系可达的两个结点集合,或者互不相交,或者一个是另一个的子集

2.二叉树
定义:二叉树是结点的有穷集合。这个集合或者是空集,或者其中有一个称为根结点的特殊结点,其余结点分属两棵不相交的二叉树,这两棵二叉树分别是原二叉树的左子树和右子树
这里写图片描述
关于二叉树的一些基本概念:
1)一棵二叉树的根结点称为该树的子树根结点的父结点;与此对应,子树的根结点称为二叉树树根结点的子结点
2)从父结点到子结点有一条连线,称为从父结点到子结点的边,父结点的两个结点互为兄弟结点
3)在二叉树里有些结点的两棵子树都空,没有子结点,这种结点称为树叶,树中其余结点称为分支结点
4)一个结点的子结点个数称为该结点的度数。显然,二叉树中树叶结点的度数为0,分支结点的度数可以是1或2

二、二叉树和树的抽象数据类型(ADT)

二叉树的基本操作包括创建空树,设置左右子树等,其基本ADT如下:

ADT BTreeBTree(self, data, left,right) #构造空树     is_empty(self)                #空树判断     num_nodes(self)               #返回结点树     data(self)                    #返回树根数据     left(self)                    #返回左子树     right(self)                   #返回右子树     set_left(self)                #设置左子树     set_right()                   #设置右子树     traversal(self)               #迭代器     forall(self,op)               #op的遍历操作

树的抽象数据类型与二叉树类似,不过在具体实现时,实现策略会有所不同,这里先给出ADT:

ADT Tree:     Tree(self, data,forest) #树的初始化     is_empty(self)          #空树判断     num_nodes(self)         #返回结点数据     data(self)              #返回树根中的数据     first_child(self,node)  #取得node的第一棵子树     children(self,node)     #结点node子树的迭代器     set_first(self,tree)    #用tree取代第一棵子树     insert_child(self, i,tree) #设置第i棵子树     traversal(self)            #所有结点的迭代器     forall(self,op)            #op的遍历操作

本文会着重介绍二叉树的实现方法和操作,由于篇幅问题对于树的实现只做简单的策略介绍,感兴趣的同学可以等待我以后另开博客讲解

三、二叉树的python实现

1.二叉树的list实现
思考可以发现,二叉树的一个结点其实就是一个三元组,分别存储着数据和左右子树,因此这里给出一个简单的list二叉树实现方式(也可以用tuple实现,区别只是在于list可以改变)
采用嵌套括号的形式,可以将二叉树表示为:

['A',['B',None,None],     ['C',['D',['F',None,None],               ['G',None,None]],          ['E',['I',None,None],               ['H',None,None]]]]

由上,我们来定义一组函数,描述二叉树的基本构造与方法,其具体的类实现,会在介绍了二叉树的遍历之后,使用链表方法实现

def BTree(data,left=None,right = None):    return [data,left,right]def is_empt_BTree(btree):    return btree is Nonedef root(btree):    return btree[0]def left(btree):    return btree[1]def right(btree):    return btree[2]def set_root(btree,data):    btree[0] = datadef set_left(btree,data):    btree[1] = datadef set_right(btree,data):    btree[2] = data

定义一个二叉树的源码为:

t1 = BTree(2,BTree(4),BTree(8))

2.二叉树的遍历
二叉树的结构比较复杂,因此系统化遍历有许多种可能的方式,以根为起点,存在两种基本方式:
1)深度优先遍历,顺着一条路径尽可能向前搜索,必要时回朔。对于二叉树,最基本的回朔情况是检查完一个结点,由于无路可走便回头
2)宽度优先遍历,在所有路径上齐头并进
按深度优先遍历需要做三件事情:遍历左子树、遍历右子树和访问根结点,选择这三项工作不同的执行顺序,就可以得到三种常见的遍历顺序:先根序遍历、中根序遍历、后根序遍历;对二叉树做宽度优先遍历,就是按二叉树的层次逐层访问树中的各结点,常见的是在每一层从左往右逐个访问
下面给出先根序遍历二叉树的递归函数:

#先给出二叉树结点类的定义class BNode:    def __init__(self,data,left = None,right = None):        self._data = data        self._left = left        self._right = right    def data(self):        if self is None:            return None        else:            return self._data    def left(self):        if self is None:            return None        else:            return self._left    def right(self):        if self is None:            return None        else:            return self._right    def set_data(self,data):        self._data = data    def set_left(self,left):        self._left = left    def set_right(self,right):        self._right = right    def __eq__(self,another):        if self is None:            return another == None        if another is None:            return self is None        else:            return self._data == another.data() and self._left == another.left() and self._right == another.right()#递归遍历,先根序,proc是对结点中元素的操作def preorder(t,proc):    if t is None:        return    proc(t.data())    preorder(t.left())    preorder(t.right())

测试函数

#使用了上述递归算法思想的二叉树输出函数def print_BNode(t):    if t is None:        print('^',end = '')        return    print('(' + str(t.data()),end = '')    print_BNode(t.left())    print_BNode(t.right())    print(')',end='')t = BNode(1,BNode(2,BNode(5)),BNode(3))print_BNode(t)

结果

(1(2(5^^)^)(3^^))

下面给出宽度优先遍历的二叉树遍历函数:

def levelorder(t,proc):    qu = SQueue()    qu.enqueue(t)    while not qu.is_empty():        t = qu.dequeue()        if t is None:       #弹出的树为空则直接跳过            continue        qu.enqueue(t.left())        qu.enqueue(t.right())        proc(t.data())

当我们使用非递归算法实现二叉树的遍历时,算法的过程和结构会更加清晰,这里仅给出先根序遍历的非递归算法

def preorder_nonrec(t,proc):    s = SStack()    while t is not None or not s.is_empty():        while t is not None:            proc(t.data())            s.push(t.right())            t = t.left()        t = s.pop()

3.二叉树的链表实现
在熟悉了二叉树的遍历算法之后,我们便可以较为完整利用链表实现二叉树类,二叉树的结点继续使用前面的结点类,没有任何问题

#二叉树的基本链表实现类定义class BTree:    def __init__(self,data,left = None,right = None):        if data == None:            self._root = None            self._num = 0        else:            self._root = BNode(data,left,right)            self._num = 1    #空链表判断        def is_empty(self):        return self._num == 0    #获得根结点中的数据    def root(self):        if self.is_empty():            return None        else:            return self._root.data()    #获得根结点的左子树    def left(self):        if self.is_empty():            return None        else:            return self._root.left()    #获得根结点的右子树    def right(self):        if self.is_empty():            return None        else:            return self._root.right()    #设置根结点的根    def set_root(self,root):        self._root = root    #设置根结点的左子树        def set_left(self,left):        self._root.set_left(left)    #设置根结点的右子树       def set_right(self,right):        self.set_right(right)    #先根序遍历方法        def preorder_elements(self):        t,s = self._root,SStack()        while t is not None or not s.is_empty():            while t is not None:                s.push(t.right())                yield t.data()                t = t.left()            t = s.pop()    #层次序遍历方法            def levelorder(self):        qu = SQueue()        qu.enqueue(self._root)        while not qu.is_empty():            t = qu.dequeue()            if t is None:                continue            qu.enqueue(t.left())            qu.enqueue(t.right())            yield t.data()     #两棵树的结构数据都相等时,两棵树相等    def __eq__(self,another):        def preorder_node(Tree):            t,s = Tree._root,SStack()            while t is not None or not s.is_empty():                while t is not None:                    s.push(t.right())                    yield t                    t = t.left()                t = s.pop()        lt1 = []        lt2 = []        for i in preorder_node(self):            lt1.append(i)        for i in preorder_node(another):            lt2.append(i)           if len(lt1) != len(lt2):            return False        i = 0        while i < len(lt1):            if lt1[i] != lt2[i]:                return False        return True    #返回克隆树    def clone(self):        return copy.deepcopy(self)    #分别返回根结点和分支结点的数目    def count_nodes(self):        def preorder_node(Tree):            t,s = Tree._root,SStack()            while t is not None or not s.is_empty():                while t is not None:                    s.push(t.right())                    yield t                    t = t.left()                t = s.pop()        count_leaf = 0        count_nleaf = 0        for i in preorder_node(self):            if i.left() == None and i.right() == None:                count_leaf += 1            else:                count_nleaf += 1        return count_leaf,count_nleaf    #输出树的所有结点    def printall(self):        for i in self.preorder_elements():            print(i,end = ' ')

测试代码

T = BTree(10,BNode(3,BNode(6)),BNode(5))for i in T.levelorder():    print(i,end = ' ')T2 = BTree(10,BNode(4,BNode(6)),BNode(5))print()print(T == T2)print(T.count_nodes())

结果

10 3 5 6 False(2, 2)

四、二叉树的应用——Huffman Tree

Huffman Tree(哈夫曼树),哈夫曼树是信息领域重要的算法,这里首先给出哈夫曼树的定义:
设有实数集W={w0,w1,…,wm-1},T是一棵扩充二叉树,其m个外部结点分别以wi为权,而且T的带权外部路径长度WPL在所有这样的扩充二叉树中达到最小,则称T为数据集W的最优二叉树或者哈夫曼树
哈夫曼提出了一种算法,可以从任意的实数集中构造出与之对应的哈夫曼树,这个构造算法如下:
1)算法的输入为实数集W={w0,w1,…wm-1}
2)在构造中维护一个包含K棵二叉树的集合F,开始时k=m且F={T0,T1,…,Tm-1},其中每个Ti是一棵只包含权为wi的根结点的单点二叉树
3)在算法的过程中重复执行下面两个步骤,直到集合F中剩下一棵树为止:
构造一棵新二叉树,其左右子树是从集合F中选取的两棵权最小的二叉树,其根结点的权值设置为这两棵子树的根结点的权值之和;
将所选的两棵二叉树从F中删除,把新构造的二叉树加入F
下面给出哈夫曼树的实现算法:

#哈夫曼树结点类class HTNode(BNode):    def __lt__(self,othernode):        return self.data() < other.data()#哈夫曼树中需要的优先队列class HuffmanPrioQ(PrioQueue):    def number(self):        return len(self._elems)#哈夫曼树构造算法def HuffmanTree(weights):    trees = HuffmanPrioQ()    for w in weights:        trees.enqueue(HTNode(w))    while trees.number() > 1:        t1 = trees.dequeue()        t2 = trees.dequeue()        x = t1.data() + t2.data()        trees.enqueue(HTNode(x,t1,t2))    return trees.dequeue()

五、树的python实现

树的结构相对于二叉树来比较复杂,其实现方式也多种多样,比如子结点引用表示法、子结点表表示法、长子-兄弟表示法等,这里仅介绍一种较为简单的子结点引用法
树的最基本表示方法是子结点引用法,其基本设计与二叉树的链接表示法类似:用一个数据单元表示结点,通过结点间的链接表示树结构,通过结点间的链接表示树结构。但这里有一个麻烦:树的结点度数不确定,而且结点的度数差可能很大,在这种情况下,一种简单的考虑是只支持度数不超过固定m的树,也就是说,树中分支结点至多允许m棵子树,其具体的类实现方法与二叉树的类实现方法类似,这里不给出具体的源代码

阅读全文
0 0
原创粉丝点击