树 [ 基于Python语言 ] 二叉树 二叉堆 优先队列

本文主要内容是数据结构比较重要的一个板块:树。介绍了树的基本定义,主要有两种形式:满二叉树、完全二叉树;二叉树的物理存储结构;二叉树的遍历方式:深度优先遍历、广度优先遍历;此外还介绍了二叉堆,以及二叉堆的构成,还有一种遍历方式优先队列... ... 

目录

3.1树的基本定义

3.1.1二叉树(binary tree)

1.二叉树有两种形式:满二叉树,完全二叉树

3.3.2 二叉树的物理存储结构

1.查找:

2.维持相对顺序

3.2二叉树的遍历

3.2.1深度优先遍历

1.前序遍历

2.中序遍历

3.后序遍历

前序遍历:

3.2.2广度优先遍历

层序遍历,队列

3.3二叉堆

3.3.1二叉堆的自我调整(顺序存储)

1.插入节点(时间复杂度:O(logn))

2.删除节点(时间复杂度:O(logn))

3.构建二叉堆(时间复杂度:O(n))

3.4 优先队列

3.4.1优先队列的实现

二叉堆的特性:


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

  • 24
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值