数据结构-树形结构
首先,什么是树形结构,简单地说,树形结构就是你现在想的那样的结构,数据结构像树形的就是树形结构,典型的树形结构示例:Windows操作系统和Unix操作系统和文件系统均是树形结构的应用。
-
树的基本概念
**树(Tree)**是一个由一个或者一个以上的节点(Node)组成的,存在一个特殊的节点,称为树根(Root),Root很熟悉吧,Linux的根目录。每个节点是一些数据和指针组合而成的记录
A B C D A为根节点,B、C、D均是A的子节点
树还可以组成森林(forest),也就是说森林是n个互斥树的集合(n>=0),移动树根即是森林
简单地说,把根节点去掉,就是森林,够离谱
树形结构的专有名词
在树形结构中,有许多常用的专有名词
- 度数(Degree):每个节点所有字数的个数,比如A的度数是3
- 层数(level):树的层数,比如A在第一层,B、C、D在第二层
- 高度(Height):树的最大层数。比如上面这个高度就是2
- 树叶或者终端节点(Terminal Nodes):度数为零的节点就是树叶,B、C、D节点就是树叶
- 父节点(Parent):每一个节点有连接的上一层节点
- 子节点(Children):每一个节点有连接的下一层节点为子节点
- 祖先(Ancestor)和子孙(Descendent):所谓祖先,是指从树根到该节点路径上所包含的节点,而子孙则是在该节点往下追溯子树中的任一节点。(这些东西就和你生物学的族谱图是一样的,就看着类比吧)
- 兄弟节点(Siblings):有共同父节点的节点为兄弟节点
- 非终端节点(Nonterminal Nodes):树叶以外的节点
- 同代(Generation):在同一棵树中具有相同层数的节点
-
二叉树简介
一般树形结构在计算机内存中的存储方式是以链表(Linked List)为主的。对于n叉树(n-way 树)来说,因为每个节点的度数都不相同,所以我们必须为每个节点都预留存放n个链接字段的最大存储空间,因为每个节点的数据结构如下:
data link1 link2 link3 … … linkn 在这种情况下,这种n叉树十分浪费链接存储空间。假设此n叉树有m个节点,那么此树共有nxm个链接字段。另外,因为除了树根外,每一个非空链接都指向一个节点,所以得知空链接个数为nxm-(m-1) = m x (n-1) + 1,而n叉树的链接浪费率为[m x (n - 1) + 1]/m x n,因此可以得出以下结论:
n = 2 时,2叉树的链接浪费率约为 1 / 2
n = 3 时,3叉树的链接浪费率约为 1 / 3
n = 4 时,4叉树的链接浪费率约为 1 / 4
… …
… …
当n = 2 时,它的链接浪费率最低,所以为了改进存储空间浪费的缺点,最常使用二叉树(Binary Tree) 结构来取代其他树形结构
-
二叉树的定义
二叉树(又称为Knuth 树)是一个由有限节点所组成的集合,此集合可以为空集合,或由一个数根及其左右两个子树组成。简单地说,二叉树最多只能有两个子节点,就是度数小于或等于2.期计算机中的数据结构如下:
LLINK Data ALINK 二叉树和一般树的不同之处:
- 树不可以为空集合,但是二叉树可以
- 树的度数为d >= 0,但二叉树的节点度数为 0 <= d <= 2
- 树的子树间没有次序关系,二叉树则有
-
特殊二叉树
-
满二叉树(Full Binary Tree)
如果二叉树的高度为h,树的节点数为2^h - 1 ,h >= 0,我们就称此树为“满二叉树”
-
完全二叉树(Complete Binary Tree)
如果二叉树的高度为h,所含的节点数小于2^h - 1,但其节点的编号方式如同高度为h的满二叉树一样,从左到右、从上到下的顺序一一对应
对于完全二叉树而言,假设有N个节点,那么此二叉树的层数h为**[log2(N+1)]**
-
斜二叉树(Skewed Binary Tree):当一个二叉树完全没有有节点或左节点时,我们就把它称为左斜二叉树或右斜二叉树
-
严格二叉树(Strict binary Tree)
二叉树中的每一个非终端节点均有非空的左右子树
-
-
二叉树的存储方式
二叉树有很多的存储方式,在数据结构中,我们通常是用链表来表示二叉树,这样在删除或者增加节点时会很方便并且很便捷。但是也可以使用一维数组这样的连续存储空间来表示二叉树,不过在对树中的中间节点进行插入或者删除操作时,可能要大量移动数组中节点的存储位置来反应树节点的变动
-
一维数组表示法
使用有序的一维数组来表示二叉树,首先可将此二叉树假想为一个满二叉树,而且第K层具有2^k-1个节点,他们按照顺序存放在这个一维数组中。
(1)A(根节点) (1)B (3)无 (4)无 (5)C (6)无 (7)D 将上面的二叉树存放到一维数组中,是什么样的
索引值 1 2 3 4 5 6 7 内容值 A B C D 从二叉树和一位数组的索引值直接关系,可以看出
- 左子树索引值是父节点索引值*2
- 右子树索引值是父节点索引值*2+1
接着来看以一维数组建立二叉树的实例,事实上就是创建一个二叉查找树,这是一种很好的排序应用模型,因为在建立二叉树的同时,数据就经过初步的比较判断并按照二叉树的建立规则来存放数据了。二叉查找树具有一下特点:
- 可以是空集合,但若不是空集合,则节点上一定要有一个键值
- 每一个树根的值需要大于左子树的值
- 每一个树根的值需要小于右子树的值
- 左右子树也是二叉查找树
- 树的每个节点的值都不相同
例子:设计一个程序,按需输入一颗二叉树
注意,因为数组的大小是固定的,所以在data设计时很容易出现索引超限,所以找个data托配合一下
def Btree_create(btree,data,length): for i in range(1,length): level = 1 while btree[level] != 0: if data[i] > btree[level]: #判断data中的数据和树根的比较,大于放在右边 level = level * 2 + 1 #将level赋值为下一个节点,如果有数据循环遍历下一个 else: level = level * 2 #小于就放在左边,*2,将level赋值为下一个节点,如果有数据循环遍历下一个 btree[level] = data[i] #遍历得到了没有数值的level,将其赋值 data = [0,3,5,6,1,9,3,2,1] length = 9 btree = [0] * 16 print("原数组内容") for i in range(length): print('[%2d]'% data[i],end='') print('') Btree_create(btree,data,9) print('二叉树内容') for i in range(1,16): print('[%2d]'%btree[i],end='') print('')
-
链表表示法
链表表示法,就是使用链表来存储二叉树。使用链表来表示二叉树的好处就是对于节点的增加和删除相当容易,缺点时很难找到父节点,除非在每一节点多增加一个父字段,以存放整数的数据类型为例
class tree: def __init__(self): self.data = 0 self.left = None self.right = None
以链表方式建立二叉树
def create_tree(self,root,val): newnode = tree() #实例化一个节点 newnode.data = val #赋值data newnode.left = None #左节点指向None newnode.right = None #右节点指向None if root == None: root = newnode #如果没有父节点,将newnode赋值给root return root else: current = root while current != None: backup = current #将每次遍历的current值给到backup if current.data > val: #节点的值大于val,要把val放在左边,并且把current设为左侧下一个节点遍历 current = current.left #current为左侧下一个节点 else: #节点的值小于val,要把val放在右边,并且把current设为右侧下一个节点遍历 current = current.right #current为右侧下一个节点 if backup.data > val: #比较此时的backup(current)与val,决定newnode放到backup的哪侧 backup.left = newnode else: backup.right = newnode return root
总结一下以上的代码,while循环就是遍历节点current,直至找到下一个节点为空时放置newnode,第一个if是判断此current节点不为空时往左遍历还是往右遍历,第二个if是判断此节点为空时newnode应该放置到左侧还是右侧
设计程序,使用链表建立二叉树
class tree: def __init__(self): self.data = 0 self.left = None self.right = None def create_tree(root,val): newnode = tree() #实例化一个节点 newnode.data = val #赋值data newnode.left = None #左节点指向None newnode.right = None #右节点指向None if root == None: root = newnode #如果没有父节点,将newnode赋值给root return root else: current = root while current != None: backup = current #将每次遍历的current值给到backup if current.data > val: #节点的值大于val,要把val放在左边,并且把current设为左侧下一个节点遍历 current = current.left #current为左侧下一个节点 else: #节点的值小于val,要把val放在右边,并且把current设为右侧下一个节点遍历 current = current.right #current为右侧下一个节点 if backup.data > val: #比较此时的backup(current)与val,决定newnode放到backup的哪侧 backup.left = newnode else: backup.right = newnode return root data = [5,4,8,1,12,3,6,7,9] ptr = None root = None for i in range(9): ptr = create_tree(ptr,data[i]) print('左子树') root = ptr.left while root != None: print('%d' %root.data) root = root.left print('--------------') print('右子树') root = ptr.right while root != None: print('%d'%root.data) root = root.right print()
-
-