本文主要内容是数据结构比较重要的一个板块:树。介绍了树的基本定义,主要有两种形式:满二叉树、完全二叉树;二叉树的物理存储结构;二叉树的遍历方式:深度优先遍历、广度优先遍历;此外还介绍了二叉堆,以及二叉堆的构成,还有一种遍历方式优先队列... ...
目录
3.1树的基本定义
树(tree)是n(n>=0)个节点的有限集。当n=0时,称为空树。
在任何一个非空树中有以下特点:
1.有且仅有一个特定的称为根的节点(root)。
2.当n>1时,其余节点可分为m(m>0)个互不相交的有限集,每一个集合本身又是一个树,并称为根的子树。没有孩子的节点被称为“叶子结点”(left),父节点(parent),孩子节点(child),兄弟节点(sibing)
3.树的最大层级即树的深度或高度
3.1.1二叉树(binary tree)
每个节点最多有2个孩子节点
1.二叉树有两种形式:满二叉树,完全二叉树
一个二叉树的所有非叶子节点都存在左孩子和右孩子,并且所有叶子节点都在同一层级上,那么这个树就是满二叉树。
对一个有n个节点的二叉树,按层级顺序编号,则所有节点的编号为从1到n。如果这个树所有节点和同样深度的满二叉树的编号为从1到n的节点位置相同,则这个二叉树为完全二叉树。
3.3.2 二叉树的物理存储结构
1.链式存储结构
链式存储是二叉树最直观的存储方式。
存储数据的data变量 指向左孩子的left指针 指向右孩子的right指针
2.数组
父节点的下标为parent,左孩子的下标为2 * parent+1,右孩子下标为2 * parent+2,同理也可求父节点的下标。
3.二叉树的应用
1.查找:
二叉查找树 (有点像二分法):如果左子树不为空,则左子树上所有节点的值均小于根节点的值。 如果右子树不为空,则右子树上所有节点的值均大于根节点的值。 左子树、右子树也都是二叉查找树。
对于一个节点分布相对均衡的二叉查找树来说,如果节点总数是n,那么搜索节点的时间复杂度就是O(logn),和树的深度是一致的。
2.维持相对顺序
二叉查找树又叫做二叉排序树。
主要是插入操作......
3.2二叉树的遍历
四种:
深度(前序遍历,中序遍历,后序遍历)
广度(层序遍历)
3.2.1深度优先遍历
1.前序遍历
输出顺序为根节点、左子树、右子树
2.中序遍历
输出顺序为左子树,根节点、右子树
3.后序遍历
输出顺序为左子树、右子树、根节点
链表节点的顺序恰恰是二叉树前序遍历的顺序。链表中的空值代表二叉树的左孩子或右孩子为空的情况。
# 树的遍历
class TreeNode:
def __init__(self,data):
self.data = data
self.left = None
self.right = None
def create_binary_tree(input_list = []):
# 构建二叉树:param input_list:输入数列
if input_list is None or len(input_list) == 0:
return None
data = input_list.pop(0)
if data is None:
return None
node = TreeNode(data)
node.left = create_binary_tree(input_list)
node.right = create_binary_tree(input_list)
return node
def pre_order_traversal(node):
# 前序遍历:param node:二叉树节点
if node is None:
return
print(node.data)
pre_order_traversal(node.left)
pre_order_traversal(node.right)
return node
def in_order_traversal(node):
# 中序遍历:param node:二叉树节点
if node is None:
return
in_order_traversal(node.left)
print(node.data)
in_order_traversal(node.right)
return node
def post_order_traversal(node):
# 后序遍历:param node:二叉树节点
if node is None:
return
post_order_traversal(node.left)
post_order_traversal(node.right)
print(node.data)
return node
my_input_list = list([3,2,9,None,None,10,None,None,8,None,4])
root = create_binary_tree(my_input_list)
print('前序遍历:')
pre_order_traversal(root)
print('中序遍历:')
in_order_traversal(root)
print('后序遍历:')
post_order_traversal(root)
以上是使用的递归方法来实现,也可以使用栈的方法来实现非递归遍历。
前序遍历:
3.2.2广度优先遍历
层序遍历,队列
一次出队一个,一次进队一层,直至所有节点遍历输出完毕。
解释:
# 层序遍历
from queue import Queue
def level_order_traversal(node):
queue = Queue()
queue.put(node)
while not queue.empty():
node = queue.get()
print(node.data)
if node.left is not None:
queue.put(node.left)
if node.right is not None:
queue.put(node.right)
3.3二叉堆
通过自己调用来使最大或最小值移动到顶点
1.最大堆,顶点为最大元素
2.最小堆,顶点为最小元素
二叉堆的根节点叫做堆顶,
3.3.1二叉堆的自我调整(顺序存储)
1.插入节点(时间复杂度:O(logn))
二叉堆插入节点时插入完全二叉树的最后一个位置,然后进行比较,上浮
2.删除节点(时间复杂度:O(logn))
删除的是堆顶节点,然后把最后一个节点临时补到原本堆顶的位置,然后继续比较,对其进行下沉,还原至正确的位置。
3.构建二叉堆(时间复杂度:O(n))
叶子结点就是有子节点,孩子的节点,非叶子节点就是除叶子结点的所有节点。
本质就是让所有非叶子节点依次“下沉”。
首先,从最后一个非叶子节点开始,和左右孩子进行比较,若是小于两者中的较小的一个则“下沉”,然后继续寻找下一个非叶子节点进行比较。
公式:左孩子下标:parent*2+1
右孩子下标:parent*2+2
层序遍历
# 二叉堆的实现
def up_adjust(array =[]):
'''
二叉堆的尾节点上浮操作
:param array: 原数组
:return:
'''
child_index = len(array) - 1
parent_index = (child_index - 1)//2
# temp保存插入的叶子节点值,用于最后的赋值
temp=array[child_index]
while child_index >0 and temp < array[parent_index]:
# 无须真正的交换,单项赋值即可 #从最后一个位置开始 插入操作
array[child_index] = array[parent_index] #交换值
child_index = parent_index #将父亲索引值变为孩子索引值
parent_index = (parent_index - 1)//2 #将父亲的索引值变为孩子的索引值
array[child_index] = temp #将新插入的节点值放置到通过上浮调整确定的正确位置上
def down_adjust(parent_index,length,array = []):
'''
二叉堆节点的下沉操作
:param parent_index:待下沉的节点下标
:param length: 堆的长度范围
:param array: 原数组
:return:
'''
# temp保存父节点值,用于最后的赋值
temp = array[parent_index] #从顶定节点开始下沉,删除操作
child_index = 2*parent_index + 1
while child_index <length:
# 如果有右孩子,且右孩子的值小于左孩子的值,则定位到右孩子
if child_index + 1 < length and array[child_index + 1] < array[child_index]:
child_index += 1
#如果父节点的值小于任何一个孩子的值,直接跳出
if temp <= array[child_index]:
break
#无须真正交换,单向赋值即可
array[parent_index] = array[child_index]
parent_index = child_index
child_index = 2*parent_index + 1
array[parent_index] = temp
def build_heap(array = []):
'''
二叉堆的构建操作
:param array:原数组
:return:
'''
# 从最后一个非叶子节点开始,一次下沉调整
# 最后一个非叶子节点的索引值: 数组长度为n 最后的叶子结点索引值为n-1,parent_index = (n-1-1)//2
for i in range((len(array) - 2)//2,-1,-1): #起始 结束 步长
down_adjust(i,len(array),array)
# 调用
my_array = list([1,3,2,6,5,7,8,9,0])
up_adjust(my_array)
print(my_array)
my_array = list([7,1,3,10,5,2,8,9,6])
build_heap(my_array)
print(my_array)
结果展示: [0, 1, 2, 3, 5, 7, 8, 9, 6] [1, 5, 2, 6, 7, 3, 8, 9, 10]
3.4 优先队列
最大优先队列:无论入队顺序如何,当前最大元素优先出队
最小优先队列:无论入队顺序如何,当前最小元素优先出队
3.4.1优先队列的实现
二叉堆的特性:
1.最大堆的堆顶是整个堆中的最大元素
2.最小堆的堆顶是整个堆中的最小元素
可以使用最大堆来实现最大优先队列,这样入队操作就是堆的插入操作(up)[时间复杂度为O(logn)],出队操作就是删除堆顶节点(down)[时间复杂度为O(logn)]。
#优先队列的基本操作
class PriorityQueue:
def __init__(self):
self.array = []
self.size = 0
def enqueue(self,element):
self.array.append(element)
self.size += 1
self.up_adjust()
def dequeue(self):
if self.size < 0:
raise Exception('队列为空!')
head = self.array[0]
self.array[0] = self.array[self.size-1]
self.size -= 1
self.down_adjust()
return head
def up_adjust(self):
child_index = self.size -1
parent_index = (child_index - 1)//2
#temp保存插入的叶子结点值,用于最后的赋值
temp = self.array[child_index]
while child_index > 0 and temp > self.array[parent_index]:
#单项赋值
self.array[child_index] = self.array[parent_index]
child_index = parent_index
parent_index = (parent_index - 1)//2
self.array[child_index] = temp
def down_adjust(self):
parent_index = 0
# temp保存父节点值,用于最后的赋值
temp = self.array[parent_index]
child_index = 1
while child_index < self.size:
# 如果有右孩子,且右孩子的值小于左孩子的值,则定位到右孩子
if child_index + 1 < self.size and self.array[child_index + 1] > self.array[child_index]:
child_index += 1
#如果父节点的值小于任何一个孩子的值,直接跳出
if temp >= self.array[child_index]:
break
#无须真正交换,单向赋值即可
self.array[parent_index] = self.array[child_index]
parent_index = child_index
child_index = 2*parent_index + 1
self.array[parent_index] = temp
queue = PriorityQueue()
queue.enqueue(3)
queue.enqueue(5)
queue.enqueue(10)
queue.enqueue(2)
queue.enqueue(7)
print(queue.dequeue())
print(queue.dequeue())
运行结果: 10 7