从python看数据结构中的树
树和二叉树
树
实际场景中,常常存在一对多、甚至多对多的情况。
树的定义如下:
树(tree)是n(n≥0)n(n\ge0)n(n≥0)个节点的有限集。当n=0n=0n=0时,称为空树。在任意一个非空树中,有如下特点。
- 有且仅有一个特定的称为根的节点。
- 当n>1n\gt1n>1时,其余节点可分为m(m>0)m(m\gt0)m(m>0)个互不相交的有限集,每一个集合本身又是一个树,并称为根的子树。
二叉树
二叉树是树的一种特殊形式。这个树的每个节点最多有2个孩子节点。二叉树节点的两个孩子节点,一个被称为左孩子,一个是右孩子。
二叉树的两种特殊形式分别位满二叉树和完全二叉树。
满二叉树即所有非叶子节点都存在左孩子和有孩子,并且所有叶子节点都在同一层级上。就是说每一个分支都是满的。
完全二叉树:对一个有nnn个节点的二叉树,按层级顺序编号,则所有节点的编号为从1到nnn。如果这个树所有节点和同样深度的满二叉树的编号从1到nnn的节点位置相同,则这个二叉树为完全二叉树。
二叉树可以使用链式存储结构或数组表达。
-
当使用链式存储结构,包含三个部分:存储数据的data变量、指向左孩子的left指针、指向右孩子的right指针。
-
当使用数组存储结构,会按照层级顺序把二叉树的节点放到数组中对应的位置上,可以更方便地定位二叉树的孩子节点和父节点。假设父节点的下标是parent,则左孩子下标为2∗parent+12*parent+12∗parent+1,右孩子的下标为2∗parent+22*parent+22∗parent+2。
对于稀疏的二叉树来说,数组表示法非常浪费空间的。
二叉树的应用
主要的应用是查找操作和维持相对顺序。
查找操作
一种特殊的二叉树来实现:二叉查找树。
- 如果左子树不为空,则左子树上所有节点的值均小于根节点的值。
- 如果右子树不为空,则左子树上所有节点的值均大于根节点的值。
- 左子树、右子树也都是二叉查找树。
对一个节点分布相对均衡的二叉查找树来说,如果节点总数是nnn,那么搜索节点的时间复杂度就是O(logn)O(logn)O(logn),和树的深度一样。
维持相对顺序
二叉树保证了二叉树的有序性,所以还有另一个名字:二叉排序树。
如果产生一边过于冗余的问题,可以采用二叉树的自平衡的方法,如红黑树、AVL树、树堆等方式。
二叉树的遍历
从节点之间的位置关系来看,分为四种遍历。更宏观的角度可以分为两类。
- 深度优先遍历(前序遍历、中序遍历、后序遍历)。
- 广度优先遍历(层序遍历)。
深度优先遍历

以此图来看,前序遍历顺序根节点、左子树、右子树,即为:1、2、4、5、3、6。
中序遍历顺序左子树、根节点、右子树,即为4、2、5、1、3、6。
后序遍历顺序左子树、右子树、根节点,即为4、5、2、6、3、1。
通过递归的方式实现的二叉树如下:
class TreeNode:
def __init__(self,data):
self.data = data
self.left = None
self.right = None
def create_binary_tree(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):
#前序遍历
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):
#中序遍历
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):
#后序遍历
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)
这里创建了一个二叉树链表,当然我们也可以通过栈的方式来实现。
def pre_order_traversal_with_stack(node):
stack = []
while node is not None or len(stack) > 0:
while node is not None:
print(node.data)
stack.append(node)
node = node.left
if len(stack) > 0:
node = stack.pop()
node = node.right
def in_order_traversal_with_stack(node):
stack = []
while node is not None or len(stack) > 0:
while node is not None:
stack.append(node)
node = node.left
if len(stack) > 0:
node = stack.pop()
print(node.data)
node = node.right
def post_order_traversal_with_stack(node):
stack = []
while node is not None or len(stack) > 0:
while node is not None:
stack.append(node)
node = node.left
if len(stack) > 0:
node = stack.pop()
tempnode = node.right
if tempnode is not None:
node.right = None
stack.append(node)
else:
print(node.data)
node = tempnode
print("前序遍历:")
pre_order_traversal_with_stack(root)
print("中序遍历:")
in_order_traversal_with_stack(root)
print("后序遍历:")
post_order_traversal_with_stack(root)
广度优先遍历
层序遍历按照从根节点到叶子节点的层次关系,一层一层横向遍历各个节点。
我们可以通过队列实现。
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)
二叉堆
本质上是一种完全二叉树,分为最大堆和最小堆。
最大堆的任何一个父节点的值,都大于或等于它左孩子或右孩子节点的值。
最小堆的任何一个父节点的值,都小于或等于它左孩子或右孩子节点的值。
二叉堆的根节点是堆顶。最大堆的堆顶就是整个堆中的最大元素,最小堆的堆顶就是整个堆中的最小元素。
二叉堆的自我调整
有几种操作:插入节点、删除节点、构建二叉堆。
插入节点:插入的位置是完全二叉树的最后一个位置。
删除节点:与插入节点相反。
构建二叉堆:让所有的非叶子节点依次“下沉”。
堆的插入和删除复杂度为O(logn)O(logn)O(logn),但构建堆的复杂度为O(n)O(n)O(n)。
二叉堆的代码实现
二叉堆虽然是一个完全二叉树,但是存储方式是顺序存储。即我们使用数组存储二叉堆的所有节点。
通过数组下标可以计算父节点的左孩子和右孩子。
def up_adjust(array = []):
#二叉堆的尾点上浮操作
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 = []):
#二叉堆的节点下沉操作
#temp保存父节点值,用于最后的赋值
temp = array[parent_index]
child_index = 2 * parent_index + 1
while child_index < length:
#如果有右孩子,则右孩子的值小于左孩子的值,则定位到右孩子
if child_index < 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 * child_index + 1
array[parent_index] = temp
def build_heap(array = []):
#二叉堆的构建操作
#从最后一个非叶子节点开始,依次下沉调整
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,10,0])
up_adjust(my_array)
print(my_array)
my_array = list([7,1,3,10,5,2,8,8,6])
build_heap(my_array)
print(my_array)
优先队列
二叉堆是实现优先队列的基础。
优先队列不遵循先入先出的原则,而是分为两种情况:
- 最大优先队列,无论入队顺序如何,都是当前最大的元素优先出队。
- 最小优先队列,无论入队顺序如何,都是当前最小的元素优先出队。
优先队列的实现
二叉堆节点“上浮”和“下沉”的时间复杂度都是O(logn)O(logn)O(logn),所以优先队列入队和出队的时间复杂度也是O(logn)O(logn)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 = 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 * child_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())
本文深入探讨了二叉树的基本概念及其应用,包括二叉树的定义、类型(如满二叉树、完全二叉树)、存储结构、遍历方法及二叉堆等内容,并介绍了二叉查找树的特点和实现。

1519

被折叠的 条评论
为什么被折叠?



