首页 > 编程语言 >【python基础之可变和不可变数据类型】--- python之堆的介绍

【python基础之可变和不可变数据类型】--- python之堆的介绍

时间:2023-12-04 18:47:14浏览次数:43  
标签:arr 之堆 python self 元素 数据类型 列表 heap 节点

【一】堆

  • 堆--简介:一种基于树的数据结构

堆是满足堆特性的完全二叉树,即树中每个节点的值大于或等于其子节点的值。

有两种类型的堆:

1. 最大堆:在最大堆中,每个节点的值都大于或等于其子节点的值,并且根节点在树中具有最大值。
2. 最小堆:在最小堆中,每个节点的值都小于或等于其子节点的值,并且根节点在树中具有最小值。

堆排序如何工作

堆排序是一种基于比较的排序算法,它使用堆数据结构对元素列表进行排序。它的工作原理是从输入列表构建一个堆,然后重复提取根元素(最大值或最小值)并将其放在排序列表的末尾。重复此过程,直到堆为空并且列表已完全排序。

简单来说,有两个步骤:

1. 构建堆
2. 从堆中提取元素

如何在 Python 中表示堆?

首先,我们需要知道如何在构建堆之前正确地表示它。

鉴于堆是一棵完全二叉树,我们可以直接使用 Python 列表来表示堆。

树的列表表示意味着真正的树在我们的脑海中。我们总是在真实代码中操作列表。

它之所以起作用,是因为列表和树之间存在以下特殊关系:

树的根始终是 index 处的元素0。

index 处节点的左孩子i存储在 index2i+1中,右孩子存储在 index2i+2中。

因此,我们总是可以通过列表的索引轻松访问树的节点。

例如,这是一个名为 的输入列表arr。

它的元素是[5, 2, 7, 1, 3]。

它代表一个原始的完全二叉树,我们需要将它转换为一个堆,如下所示:

image

  1. 树的根是arr[0]
  2. 根的左孩子是arr[20+1],右孩子是arr[20+2]。

如何建堆?

从列表构建堆的过程称为 heapify。

原始列表已经是二叉树的表示(树在我们的脑海中),如上所示。我们需要做的是将原始树转换为堆。

由于最大堆和最小堆具有相似的思想。下面就说说如何建立最大堆吧。

思路是自下而上遍历二叉树的非叶子节点构造一个最大堆,对于每个非叶子节点,将其与其左右子节点进行比较,将最大值与父节点交换这个子树。

为什么从非叶节点而不是最后一个节点开始?

因为叶子节点下面没有子节点,所以不需要操作。

为什么从下往上遍历而不是从上往下遍历?

如果我们从底部往上走,肯定会将最大值放入根节点(类似于冒泡排序的思想)。

堆的实现

  • 下文实现的是大顶堆。若要将其转换为小顶堆,只需将所有大小逻辑判断取逆

基于数组的实现

(1)堆的存储与表示

  • 我们在二叉树章节中学习到,完全二叉树非常适合用数组来表示。
    • 由于堆正是一种完全二叉树,我们将采用数组来存储堆
  • 当使用数组表示二叉树时,元素代表节点值,索引代表节点在二叉树中的位置。
    • 节点指针通过索引映射公式来实现

堆的表示与存储

  • 我们可以将索引映射公式封装成函数,方便后续使用。
def left(self, i: int) -> int:
    """获取左子节点索引"""
    return 2 * i + 1

def right(self, i: int) -> int:
    """获取右子节点索引"""
    return 2 * i + 2

def parent(self, i: int) -> int:
    """获取父节点索引"""
    return (i - 1) // 2  # 向下整除

(2)访问堆顶元素

  • 堆顶元素即为二叉树的根节点,也就是列表的首个元素。
def peek(self) -> int:
    """访问堆顶元素"""
    return self.max_heap[0]

(3)元素入堆

  • 给定元素 val ,我们首先将其添加到堆底。添加之后,由于 val 可能大于堆中其他元素,堆的成立条件可能已被破坏。
    • 因此,需要修复从插入节点到根节点的路径上的各个节点,这个操作被称为「堆化 heapify」。
  • 考虑从入堆节点开始,从底至顶执行堆化
    • 如图 8-3 所示,我们比较插入节点与其父节点的值,如果插入节点更大,则将它们交换。
    • 然后继续执行此操作,从底至顶修复堆中的各个节点,直至越过根节点或遇到无须交换的节点时结束。

动画

def push(self, val: int):
    """元素入堆"""
    # 添加节点
    self.max_heap.append(val)
    # 从底至顶堆化
    self.sift_up(self.size() - 1)

def sift_up(self, i: int):
    """从节点 i 开始,从底至顶堆化"""
    while True:
        # 获取节点 i 的父节点
        p = self.parent(i)
        # 当“越过根节点”或“节点无须修复”时,结束堆化
        if p < 0 or self.max_heap[i] <= self.max_heap[p]:
            break
        # 交换两节点
        self.swap(i, p)
        # 循环向上堆化
        i = p

(4)堆顶元素出堆

  • 堆顶元素是二叉树的根节点,即列表首元素。如果我们直接从列表中删除首元素,那么二叉树中所有节点的索引都会发生变化,这将使得后续使用堆化修复变得困难。为了尽量减少元素索引的变动,我们采用以下操作步骤。
    • 交换堆顶元素与堆底元素(即交换根节点与最右叶节点)。
    • 交换完成后,将堆底从列表中删除(注意,由于已经交换,实际上删除的是原来的堆顶元素)。
    • 从根节点开始,从顶至底执行堆化
  • 如图 8-4 所示,“从顶至底堆化”的操作方向与“从底至顶堆化”相反,我们将根节点的值与其两个子节点的值进行比较,将最大的子节点与根节点交换。然后循环执行此操作,直到越过叶节点或遇到无须交换的节点时结束。

动画

def pop(self) -> int:
    """元素出堆"""
    # 判空处理
    if self.is_empty():
        raise IndexError("堆为空")
    # 交换根节点与最右叶节点(即交换首元素与尾元素)
    self.swap(0, self.size() - 1)
    # 删除节点
    val = self.max_heap.pop()
    # 从顶至底堆化
    self.sift_down(0)
    # 返回堆顶元素
    return val

def sift_down(self, i: int):
    """从节点 i 开始,从顶至底堆化"""
    while True:
        # 判断节点 i, l, r 中值最大的节点,记为 ma
        l, r, ma = self.left(i), self.right(i), i
        if l < self.size() and self.max_heap[l] > self.max_heap[ma]:
            ma = l
        if r < self.size() and self.max_heap[r] > self.max_heap[ma]:
            ma = r
        # 若节点 i 最大或索引 l, r 越界,则无须继续堆化,跳出
        if ma == i:
            break
        # 交换两节点
        self.swap(i, ma)
        # 循环向下堆化
        i = ma

提取一个元素后如何更新堆?

把原来的链表转成堆后,剩下的就简单了。我们只需要反复提取堆的根元素,也就是最大/最小元素,放到需要返回的排序列表的末尾即可。

但是,我们需要做一件事——在提取根节点后更新整个堆。

它需要两个步骤:

用堆中的最后一个元素替换根节点:删除根节点(在最大堆中具有最大值或在最小堆中具有最小值)并将其替换为堆中的最后一个元素。

再次堆化整个堆。

在 Python 中实现堆排序

话不多说,让我们看看代码:

Def  heap_sort(arr):                                                               
#build a max heap from the input list                        
		Heaping(arr)                                                 
#Extract the root element (the maximum value) and place it at the end of the sorted list 
    sorted_arr = []                                              
    while arr:                                                   
    		sorted_arr.append(heappop(arr))
        # Return the sorted list
        return sorted_arr
def heapify(arr):
  	#start from the last non-leaf node and heapify all nodes from bottom to top
    n = len(arr)
    for i in range(n//2 -1,-1,-1):
      	heapify_node(arr,i,n)
        
def heapify_node(arr,i,n):
  #Heapify the node at index i and its children
    left = 2*i + 1
    right = 2*i +2
    largest =i
    if left < n and arr[left] > arr[larfest]:
      	largest = left
      	arr[i],arr[largest] = arr[largest],arr[i]
        heapify_node(arr,largest,n)
        
def heappop(arr):
  	#Extract the root element (the maximum value) from the heap
    root = arr[0]
    arr[0] = arr[-1]
    del arr[-1]
    heapify(arr)
    return root

  print(heap_sort([5,2,7,2077,3,99,4,54,20]))
  
  #[2077,99,54,20,7,5,4,3,2]

关键功能是heapify和heapify_node。它们用于从原始列表构建堆。

使用 Python 的 heapq 模块

幸运的是,我们不需要每次都实现构建堆的基本功能。Python 有一个名为heapq.

该heapq模块提供了处理堆的功能,包括:

heappush(heap, elem):将一个元素压入堆中,保持堆属性。

heappop(heap):从堆中弹出并返回最小的元素,保持堆属性。

heapify(x):在线性时间内将常规列表就地转换为堆。

nlargest(k, iterable, key=None):返回一个列表,其中包含指定迭代器中的 k 个最大元素并满足堆属性。

nsmallest(k, iterable, key=None):返回一个列表,其中包含指定可迭代对象中的 k 个最小元素并满足堆属性。

heapq模块实现为堆队列,即优先级队列实现为堆。它是高效排序和搜索列表和其他可迭代对象的有用工具。

现在,让我们用它来做堆排序:

import heapq

def heap_sort ( arr ):
\# 建立一个堆
heapq.heapify(arr)

\# 从堆中逐一提取元素
sorted_arr = []
while arr:
sorted_arr.append(heapq.heappop(arr))

return sorted_arr

\# 测试堆排序函数
arr = [ 5 , 21 , 207 , 19 , 3 ]
print (heap_sort(arr))
\# 输出:[3, 5, 19, 21, 207]

既简单又优雅

堆常见应用

- **优先队列**:堆通常作为实现优先队列的首选数据结构,其入队和出队操作的时间复杂度均为O(log n),而建队操作为 O(n) ,这些操作都非常高效。
- **堆排序**:给定一组数据,我们可以用它们建立一个堆,然后不断地执行元素出堆操作,从而得到有序数据。然而,我们通常会使用一种更优雅的方式实现堆排序,详见后续的堆排序章节。
- **获取最大的 k 个元素**:这是一个经典的算法问题,同时也是一种典型应用,例如选择热度前 10 的新闻作为微博热搜,选取销量前 10 的商品等。

堆排序的时空复杂度

堆排序的时间复杂度为O(n*logn),其中n是列表中元素的个数。这是因为该heapify函数需要O(logn)时间来堆化单个节点,并且它被调用了O(n)次(堆中的每个节点一次)。该heappop函数还需要O(logn)时间从堆中提取根元素并恢复堆属性,它被调用O(n)times(对列表中的每个元素一次)。

堆排序的空间复杂度为O(1),因为排序是就地完成的,不需要额外的空间。这也是堆排序的一大优势。因为它节省了大量内存使用,尤其是当原始数据集很大时。

标签:arr,之堆,python,self,元素,数据类型,列表,heap,节点
From: https://www.cnblogs.com/queryH/p/17875657.html

相关文章

  • 【python基础之可变和不可变数据类型】--- python堆栈的相关应用
    【一】用代码实现堆和栈【1】堆#堆的操作是先进先出(FIFO)list_queue=[]foriinrange(0,5):print(f'{i}已入堆(队列)')list_queue.append(i)print('------入堆完毕--------')whilelist_queue:print(f'{list_queue.pop(0)}已出堆(队列)')print('-......
  • 04-python代码审计
    eg1:@app.route('/getUrl',methods=['GET','POST'])defgetUrl():url=request.args.get("url")host=parse.urlparse(url).hostname#解析主机名ifhost=='suctf.cc':return"我扌y......
  • Java基本数据类型、包装类及拆装箱详解
    Java的基本数据类型和对应的包装类是Java语言中处理数据的两个关键概念。基本数据类型提供了简单而高效的方式来存储数据,而包装类使得基本数据类型具有对象的特性。本文将深入探讨基本数据类型与包装类的应用场景及详细描述,并对自动拆箱和装箱的源码实现进行分析。基本数据类型与......
  • Linux下编译安装python
    1安装依赖yuminstallgccpatchlibffi-develpython-develzlib-develbzip2-developenssl-develncurses-develsqlite-develreadline-develtk-develgdbm-develdb4-devellibpcap-develxz-devel-y2下载源码到linuxyuminstall-ywgetwgethttps://www.python.o......
  • Python上课笔记2
    Python中可以一次行输入多个数字的方法a,b=map(int,input().split())#split()函数就是可以自动识别空格断开猜数字游戏这里需要调用一下random这个库importrandomasra#当然我这里给他重新定义了一个名字i=0x=ra.randint(0,100)whilei<3:a=int(i......
  • linux python virtualenv虚拟环境安装
    pythonvirtualenv虚拟环境安装pip3installvirtualenvpip3installvirtualenvwrapper创建环境存放目录mkdir$HOME/.virtualenvs查看已安装的virtualenvfind/-namevirtualenv查看已安装的virtualenvwrapper.shfind/-namevirtualenvwrapper.sh查看......
  • python连接mysql数据库
    说明:1.如果你使用的是其他数据库,例如PostgreSQL,你可以使用psycopg2库来连接和获取数据库数据。使用方法类似,只需要根据你的实际情况修改连接参数和SQL语句即可。2.首先确保本地数据库可以查询到数据,比如:若没有登陆SVN,本地数据库无法查询数据,那么python代码也会执行失败 一、......
  • Python中级-01-数据类型的内置方法
    本篇内容来源于:【1.0】Python中级之数据类型的内置方法-Chimengmeng-博客园(cnblogs.com)写的verygood,非常详细【一】数字类型【1】整数类型(int)(1)基本运算实现整数的加法运算。#int.__add__(other)num1=5num2=2result=num1.__add__(num2)print(......
  • 【python入门之垃圾回收机制】---python 垃圾回收机制
    【一】引入解释器在执行到定义变量的语法时,会申请内存空间来存放变量的值,而内存的容量是有限的,这就涉及到变量值所占用内存空间的回收问题当一个变量值没有用了(简称垃圾)就应该将其占用的内存给回收掉,那什么样的变量值是没有用的呢?单从逻辑层面分析,我们定义变量将变量值存......
  • 【Lidar】基于Python的Open3D库、Laspy库保存点云文件/点云格式转换
    ​     因为最近在做点云相关的项目,过程中用到了Python中的Open3D库和Laspy库,所以今天给大家分享一下如何使用Open3D和Laspy这两个库对点云数据进行保存和格式的转换。1Open3D库介绍    Laspy库我到时候会单独介绍,所以这里就不多说了!!!        Open......