从python看数据结构中的树

本文深入探讨了二叉树的基本概念及其应用,包括二叉树的定义、类型(如满二叉树、完全二叉树)、存储结构、遍历方法及二叉堆等内容,并介绍了二叉查找树的特点和实现。

从python看数据结构中的树

树和二叉树

实际场景中,常常存在一对多、甚至多对多的情况。

树的定义如下:

树(tree)是n(n≥0)n(n\ge0)n(n0)个节点的有限集。当n=0n=0n=0时,称为空树。在任意一个非空树中,有如下特点。

  1. 有且仅有一个特定的称为根的节点。
  2. 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+12parent+1,右孩子的下标为2∗parent+22*parent+22parent+2

    对于稀疏的二叉树来说,数组表示法非常浪费空间的。

二叉树的应用

主要的应用是查找操作和维持相对顺序。

查找操作

一种特殊的二叉树来实现:二叉查找树

  • 如果左子树不为空,则左子树上所有节点的值均小于根节点的值。
  • 如果右子树不为空,则左子树上所有节点的值均大于根节点的值。
  • 左子树、右子树也都是二叉查找树。

对一个节点分布相对均衡的二叉查找树来说,如果节点总数是nnn,那么搜索节点的时间复杂度就是O(logn)O(logn)O(logn)​,和树的深度一样。

维持相对顺序

二叉树保证了二叉树的有序性,所以还有另一个名字:二叉排序树

如果产生一边过于冗余的问题,可以采用二叉树的自平衡的方法,如红黑树、AVL树、树堆等方式。

二叉树的遍历

从节点之间的位置关系来看,分为四种遍历。更宏观的角度可以分为两类。

  1. 深度优先遍历(前序遍历、中序遍历、后序遍历)。
  2. 广度优先遍历(层序遍历)。

深度优先遍历

在这里插入图片描述

以此图来看,前序遍历顺序根节点、左子树、右子树,即为: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())
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值