Python是怎样管理内存的heap

Python是怎样管理内存的?

1. Python的内存管理是由私有heap空间管理的。所有的Python 对象和数据结构都在一个私有heap 中。程序员没有访问该heap 的权限,只有解释器才能对它进行操作。为Python 的heap 空间分配内存是由Python 的内存管理模块进行的,其核心API 会提供一些访问该模块的方法供程序员使用。

2. Python有自带的垃圾回收系统,他回收并释放没有被使用的内存,让他们能够被其他程序使用。

 

、堆是什么

    • 来自数组的树

堆是用数组实现的二叉树,所以它没有父指针或者子指针。堆根据“堆属性”来排序,“堆属性”决定了树中节点的位置。
堆的一般用法:
.构建优先队列
.支持堆排序
.快速查找出集合的最大值或者最小值

二、堆属性

    • 来自数组的树

堆分为最大堆(max heap)和最小堆(min-heap),两者的差别在于节点的排序方式。

 

 

最大堆中,父节点的值比每个子节点的值都要大,最小堆中,父节点的值比每一个子节点的值都要小。这就是所谓的“堆属性”(heap property),并且这个属性对堆中每一个节点都成立。
举个栗子:
堆属性
这是一个最大堆,因为每一个父节点的值比子节点的值都要更大。10 比 7 和 2 都打,7比5 和1 都大。
根据这一属性,那么最大堆总是将其中的最大值存放在树的根节点。而对于最小堆,根节点中的元素总是树中的最小值。堆属性非常的有用,因为堆常常被当做优先队列使用,因为可以快速的访问到“最重要”的元素。

1
2
3
注意:堆的根节点中存放的是最大或者最小元素,但是其他节点的排序顺序是未知的。
例如,在一个最大堆中,最大的那一个元素总是位于 index 0 的位置,但是最小的
元素则未必是最后一个元素。--唯一能够保证的是最小的元素是一个叶节点,但是不确定是哪一个。

2.1 堆和普通树的区别
堆不能取代二叉搜索树,它们之间确有一些相似的地方,但是又有一些不同的地方,下面是一下主要的不同之处。
1.节点的顺序。在一个二叉搜索树(BST)里面,左边的子节点必须小于父节点,右边的子节点必须大于子节点。但是在堆里面并非如此,在最大堆里面,所有子节点必须比父节点的值更小,最小堆里面则相反。
2.内存的占用,普通树占用的内存空间比他们实际存储的数据更多,你必须为节点对象以及左/右子节点指针分配额外的存储空间,而堆只用一个普通的数组结构来存储,并不会用到指针。
3.平衡,一个二叉树必须要在平衡的情况下,其大部分操作的时间复杂度才能保持在O(logn), 你可以以任意的顺序插入和删除数据,或者使用AVL树或者红黑树,但是在堆里面,我们不用保证整个树是有序的状态。我们只需要堆属性被实现即可,所以堆中平衡不是问题。因为堆的结构,所以保证O(log n)的性能。
4.搜索。在二叉搜索树里面搜索会很快,但是在堆中会很慢,因为堆的目的是把最大或者最小节点前置以及保证插入和删除相对快 所以搜索对于堆而言优先级并不是最高的。

**

来自数组的树

**

用数组来展示树状的结构可能看起来很奇怪,当时在时间和空间上都是很高效。
接下来我们来展示举个栗子:

1
[10,7,2,5,1]

 

 

就这么多,除了简单的数组以外,我们并不需要更多的存储空间。
那么,在不被允许使用指针的情况下我们如何知道那个节点是父节点,哪个是子节点呢?问得好!节点在数组中的位置index和它的父节点和子节点之间存在一个映射关系。
如果i是节点的索引,那么下面的公式就给出了父节点以及子节点在数组中的位置。

1
2
3
parent(i) = floor((i-1)/2)
left(i) = 2i+1
right = 2i + 2

注意右节点(right(i))只是左节点(left(i))的值+1,左右节点在数组的位置总是相邻的。让我们用上面的规则来推导出数组的索引以及左右节点的位置:
在这里插入图片描述
注意:根节点10 并没有父节点,因为-1 不是一个有效的数组索引,同样,节点2,5和1并没有子节点,因为这些索引已经超出了数组的大小,所以在我们使用这些索引的时候,我们必须确保计算的索引值有效。

三、python里面堆的实现

    • 来自数组的树

Python里面没有专门的堆类型,而只有包含一个堆类型操作的模块heapq(q表示队列),它包含6个函数,其中前4个与堆操作直接相关。必须通过列表这种结构来实现的。
heappush(heap,x) 把元素x压到堆中
heappop(heap) 弹出堆中的第一个元素
heapify() 让列表具有堆属性
heapreplace(heap,x) 替换堆中的最小元素,并用x代替
nlargest(n,heap) 找出堆中最大的n个元素
nsmallest(n,heap) 找出堆中最小的n个元素

函数heappush用于在堆中添加一个元素。请注意,不能将它用于普通列表,而只能用于使用各种堆函数创建的列表。原因是元素的顺序很重要(虽然元素的排列顺序看起来有点随意,并没有严格地排序)。

1
2
3
4
5
6
7
8
9
10
import heapq
from random import shuffle
data = list(range(10))
shuffle(data)
heap = []
for n in data:
    heappush(heap,n)
print(heap)
heappush(heap,0.5)
print(heap)

 

 

输出结果:

1
2
[0, 1, 2, 3, 5, 9, 6, 8, 4, 7]
[0, 0.5, 2, 3, 1, 9, 6, 8, 4, 7, 5]

函数heapify通过执行尽可能少的移位操作将列表变成合法的堆(即具备堆特征)。如果你的堆并不是使用heappush创建的,应在使用heappush和heappop之前使用这个函数。
eg:

1
2
3
heap =  [5, 8, 0, 3, 6, 7, 9, 1, 4, 2]
heapify(heap)
print(heap)

可以看到输出结果中会自动把最小值放在左边,来满足堆的属性(heap property):

1
[0, 1, 5, 3, 2, 7, 9, 8, 4, 6]

heapreplace() 使用的频率不是那么高,它通过弹出堆中的最小元素,然后把第二个参数作为元素放入堆中,相较于heappush()的移位操作,heapreplace()的效率来的会更快一些。
nlargest(n,iter) 和 nsmallest(n,iter)用于找出iter中的最大(小)的n个元素,这种任务也可以通过列表里面的先排序(sorted)再切片完成,但是使用堆的方式操作起来效率更高,也会更加节省内存空间。
代码示例:

1
2
3
4
5
6
7
8
9
10
11
from heapq import *
from random import shuffle

data = list(range(10))
shuffle(data)
heap = []
for n in data:
    heappush(heap,n)
print(heap)
print(nlargest(3,heap))
print(nsmallest(3,heap))

 

 

输出结果:

1
2
3
[0, 1, 3, 4, 2, 8, 7, 5, 6, 9]
[9, 8, 7]
[0, 1, 2]

 

 

四、heap模块的延展使用

    • 来自数组的树

总结:
综合我们本篇的讲解,相信大家对于heap结构有了一定的了解,它通过最大堆(max heap)和最小堆(min heap)两种形式来完成一些数据的创建和操作工作。
1.最大堆里面,根节点的数据最大,父节点的数据要比子节点都大
2.最小堆里面,根节点的数据最小,父节点的数据要比子节点都小
3.它和二叉搜索树(BST)的区别在于,父节点和子节点的数据大小关系
4.堆的优势在于插入和删除,二叉搜索树的优势在于搜索
5.python里面没有专门的堆类型,通过列表来存储,heapq中的函数来实现

posted @ 2022-07-14 20:22  断浪狂刀忆年少  阅读(304)  评论(0编辑  收藏  举报