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的树称为空树

python 语法树模块 python的树_结点

树的特性:

1)如果树的结构不空,其中就存在着唯一的起始结点,称为树根

2)按结构的连接关系,树根外的其余结点都有且只有一个前驱,但另一方面,一个结点可以有0个或者多个后继。在非空的树结构中一定有一些结点并不连接到其他结点,与尾结点相似

3)从树根结点出发,经过若干次后继关系可以到达结构中的任意一点

4)结点之间的联系不会形成循环关系

5)从这个结构里的任意两个不同结点出发,通过后继关系可达的两个结点集合,或者互不相交,或者一个是另一个的子集

2.二叉树

定义:二叉树是结点的有穷集合。这个集合或者是空集,或者其中有一个称为根结点的特殊结点,其余结点分属两棵不相交的二叉树,这两棵二叉树分别是原二叉树的左子树和右子树

python 语法树模块 python的树_结点

关于二叉树的一些基本概念:

1)一棵二叉树的根结点称为该树的子树根结点的父结点;与此对应,子树的根结点称为二叉树树根结点的子结点

2)从父结点到子结点有一条连线,称为从父结点到子结点的边,父结点的两个结点互为兄弟结点

3)在二叉树里有些结点的两棵子树都空,没有子结点,这种结点称为树叶,树中其余结点称为分支结点

4)一个结点的子结点个数称为该结点的度数。显然,二叉树中树叶结点的度数为0,分支结点的度数可以是1或2

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

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

ADT BTree:

BTree(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 None

def root(btree):

return btree[0]

def left(btree):

return btree[1]

def right(btree):

return btree[2]

def set_root(btree,data):

btree[0] = data

def set_left(btree,data):

btree[1] = data

def 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棵子树,其具体的类实现方法与二叉树的类实现方法类似,这里不给出具体的源代码