目录
**2.7 这是专注于maxHeapify函数的讲解。如果你已经看懂了。可跳过。
**2.8 这是Heapsort函数的讲解,如果你看懂了可以跳过。
2.在二叉树中,节点之间的关系主要是父子关系和兄弟关系。具体来说:
3.对于任意节点,假设其父节点的下标(或索引)为 parent,那么:
4.在二叉树中,叶子节点和非叶子节点是构成树形结构的基本元素,它们之间存在明确的关系,并通过特定的方式计算。
在学习堆排序之前之前需要具备什么知识?
递归,二叉树。
如果你不了解,也没关系。
我会在代码里面与你讲解二叉树思想和递归思想,如何运用到堆排序中
一、堆排序概念:
堆排序的基本思想是将待排序的序列构建成一个堆,然后依次将堆顶元素与堆尾元素交换,并将堆的大小减小1,然后再对剩余的堆进行调整,使其满足堆的性质。重复这个过程,直到堆的大小为1,此时排序完成。
二、堆排序性能:
2.1时间复杂度:
时间复杂度:O(nlogn)
堆排序的时间复杂度主要来自于两个步骤:建堆和排序。
- 建堆:对于包含n个元素的序列,建堆的时间复杂度为O(n)。因为每个元素最多只会向下调整一层,所以总的时间复杂度是线性的。
- 排序:排序过程需要重复n-1次,每次将堆顶元素与堆尾元素交换,并重新调整堆。每次调整堆的时间复杂度是O(logn),因为堆是一个完全二叉树,其高度为logn,每次调整需要遍历从堆顶到叶子节点的路径。因此,排序的总时间复杂度为O(nlogn)。
由于排序步骤是主要的耗时操作,所以堆排序的整体时间复杂度为O(nlogn)。无论是在最好情况、平均情况还是最坏情况下,堆排序的时间复杂度都是O(nlogn)。
2.2空间复杂度:
空间复杂度:O(1)
堆排序是原地排序算法,即它只需要一个额外的存储空间来交换元素,而不需要额外的数据结构来存储元素。因此,堆排序的空间复杂度是常数级别的,即O(1)。
2.3先说思想后上代码:
如果你已经了解,递归和二叉树的概念,请往下读。不然可跳转二叉树知识。
堆排序的步骤主要包括以下四个方面:
- 构建初始堆:
- 将待排序的序列构造成一个大顶堆(或小顶堆)。此时,整个序列的最大值(或最小值)就是堆顶的根节点。
- 构建初始堆的过程通常从最后一个非叶子节点开始,从后往前调整每个非叶子节点,使其成为以其为根的子树的大顶堆(或小顶堆)。
- 堆顶元素与末尾元素交换:
- 将堆顶元素(最大值或最小值)与末尾元素进行交换,此时末尾元素就为最大值(或最小值)。
- 通过交换,我们将最大值(或最小值)放到了序列的末尾,即其最终位置。
- 调整堆:
- 由于交换后末尾元素已确定,因此末尾元素之后的序列不再参与后续的排序过程。
- 将剩余的元素重新调整为大顶堆(或小顶堆)。这个过程与构建初始堆类似,但是调整的范围缩小了。
- 重复交换与调整:
- 重复步骤2和步骤3,每次将当前堆顶元素与剩余序列的末尾元素交换,并调整剩余元素为堆。
- 这个过程持续进行,直到整个序列有序。
2.4代码实现如下:
def maxHeapify(heap,start,end): #大顶堆的下沉操作
#其中heap代表顺序表,start为根节点,end为顺序表的末尾
son =start*2 #左节点
while son<=end: #如果左子树存在
#取左子树根和右子树根 两者中大者的下标
if son +1 <=end and heap[son+1] >heap[son]:
son+=1
#如果子节点的值大于根节点,则将根节点和子节点交换。即下沉操作
if heap[son]>heap[start]:
heap[start],heap[son]=heap[son],heap[start]
#对子节点迭代执行相同的操作
start,son=son,son*2
else:#如果子节点的值小于等于根节点,说明堆已经构造好了,退出循环
break
def Heapsort(arr):#进行堆排序操作
heap=[None]+arr #这里是因为列表从0开始计数,而我们找的子节点父节点的关系是2倍或2倍+1
root=1 #堆顶下标
count=len(heap) #获取堆元素个数
for i in range(count//2,root-1,-1):#逆序枚举列表的元素
#自底向上地构造堆
maxHeapify(heap,i,count-1)
for i in range(count-1,root,-1): #将堆顶和最后一个元素交换
heap[i],heap[root]=heap[root],heap[i]#保持除最后一个元素以外整个堆的合法性
#保持除最后第二个元素以外整个堆的合法性
maxHeapify(heap,root,i-1)
return heap[1:]
arr=[2,5,1,4,9,7,8,3]
print(Heapsort(arr))
看不懂也没关系,我会详细解释这段代码。
注意:Python列表最后一个下标为len(列表)-1,所以我增加了[None],方便理解父节点和子节点的关系。
2.5 maxHeapify
函数
maxHeapify
函数是一个私有辅助函数,用于维护大顶堆的性质。当堆的某个节点违反大顶堆性质时,这个函数通过下沉操作来修复它。
参数:
heap
: 代表整个堆的顺序表。start
: 需要调整的子树的根节点。end
: 顺序表的末尾。
逻辑流程:
- 计算左子节点的下标。
- 检查左子节点是否存在,如果存在,再检查右子节点是否存在且值是否大于左子节点。
- 找出左子节点和右子节点中值较大的那个。
- 如果选中的子节点值大于根节点,交换它们,并继续调整交换后的子树。
- 如果选中的子节点值不大于根节点,说明以
start
为根的子树已经是大顶堆,退出循环。
2.6 Heapsort
函数
Heapsort
函数是堆排序的主要函数,它实现了堆排序的全过程。
参数:
arr
: 待排序的数组。
逻辑流程:
- 构造大顶堆:在
Heapsort
函数中,首先创建一个新的列表heap
,其中包含None
和原数组arr
的所有元素。这样做是为了简化索引计算,因为堆的根节点位于索引 1,而不是 0。然后,从最后一个非叶子节点开始,通过调用maxHeapify
函数,自底向上地构造大顶堆。 - 排序:通过不断将堆顶元素(最大值)与当前未排序部分的最后一个元素交换,并重新调整堆,实现排序。这个过程持续进行,直到整个数组都排好序。
- 返回结果:最后,返回除去
None
的排序后的数组。
**2.7 这是专注于maxHeapify函数的讲解。如果你已经看懂了。可跳过。
# 定义maxHeapify函数,用于维护大顶堆的性质
# 这个函数用于调整以start为根的子树,确保它满足大顶堆的性质
# 参数heap是待调整的堆,start是要调整的子树的根节点下标,end是堆的末尾下标
def maxHeapify(heap, start, end):
# 计算左子节点的下标
# 对于数组中的元素,如果父节点的下标是start,则左子节点的下标是start*2
son = start * 2
# 当左子节点下标不超过末尾时,即左子节点存在
# 循环条件确保我们只处理存在的子节点
while son <= end:
# 检查右子节点是否存在且值是否大于左子节点
# 如果存在右子节点,并且它的值大于左子节点,则我们可能需要交换start和右子节点
if son + 1 <= end and heap[son + 1] > heap[son]:
# 如果右子节点值更大,则更新son为右子节点下标
# 这样,我们接下来会与右子节点进行比较和可能的交换
son += 1
# 如果子节点(son指向的节点)的值大于根节点(start指向的节点)
# 这表示当前的大顶堆性质被破坏了,因为子节点的值不应该大于父节点
if heap[son] > heap[start]:
# 交换根节点和子节点的值
# 通过交换,我们尝试恢复大顶堆的性质
heap[start], heap[son] = heap[son], heap[start]
# 更新start和son,继续向下调整子树
# 由于我们交换了start和son的值,所以新的start就是原来的son
# 同时,我们计算新的son(新start的左子节点)
start, son = son, son * 2
else:
# 如果子节点的值不大于根节点,说明以start为根的子树已是大顶堆
# 也就是说,当前子树已经满足大顶堆的性质,不需要进一步调整
# 退出循环
break
**2.8 这是Heapsort函数的讲解,如果你看懂了可以跳过。
# 定义Heapsort函数,用于执行堆排序
def Heapsort(arr):
# 创建新的列表heap,其中包含一个None和arr的所有元素
# 这样做的目的是为了简化索引计算,因为堆的根节点通常从下标1开始,而Python的列表下标从0开始
heap = [None] + arr
# 堆顶元素的下标
# 在这个实现中,堆顶元素的下标是1,而不是通常的0
root = 1
# 获取堆元素的总数
count = len(heap)
# 从最后一个非叶子节点开始,自底向上构造大顶堆
# 最后一个非叶子节点的下标是(count // 2) - 1
# 这是因为叶子节点开始的下标是(count // 2)
for i in range(count // 2, root - 1, -1):
# 调用maxHeapify函数,维护以当前非叶子节点为根的子树的大顶堆性质
maxHeapify(heap, i, count - 1)
# 堆排序的主要循环:从最后一个元素开始,与堆顶元素交换,并重新调整堆
# 这样做可以确保最大的元素被放到正确的位置(数组的末尾)
for i in range(count - 1, root, -1):
# 交换堆顶元素和当前最后一个元素
heap[i], heap[root] = heap[root], heap[i]
# 由于交换了元素,堆的性质可能已经被破坏
# 调用maxHeapify函数,保持除最后一个元素以外整个堆的合法性
# 注意,此时堆的末尾元素已经是排好序的,所以maxHeapify的end参数是i - 1
maxHeapify(heap, root, i - 1)
# 返回排序后的数组(去掉列表首部的None)
# 因为在创建heap时我们在列表开头添加了一个None,所以现在要移除它
return heap[1:]
至此,就结束了。
来喝一口鸡汤吧^-^:
文学远远比政治要美好。政治教人打架,文学教人恋爱。 ——莫言
三、二叉树的知识:
1.以下是二叉树中的一些重要组成概念:
- 节点:二叉树中的每个元素被称为一个节点。节点不仅包含数据元素,还包含指向其子树的链接。
- 左子树和右子树:对于二叉树中的每个节点,它有两个可能的子节点,一个位于左侧(称为左子树),另一个位于右侧(称为右子树)。这两个子树也是二叉树。
- 根节点:二叉树的最顶部节点被称为根节点。它是整个二叉树的起点。
- 叶子节点:没有子节点的节点被称为叶子节点。它们是二叉树中的终止节点。
- 内部节点:非叶子节点被称为内部节点。它们至少有一个子节点。
- 层次:从根节点开始,根节点位于第一层,根的子节点位于第二层,以此类推。节点的层次表示其在二叉树中的深度或位置。
- 深度或高度:二叉树中节点的最大层次被称为二叉树的深度或高度。它表示从根节点到最远叶子节点的最长路径上的节点数。
2.在二叉树中,节点之间的关系主要是父子关系和兄弟关系。具体来说:
- 父子关系:在二叉树中,一个节点可以有一个父节点和两个子节点(左子节点和右子节点)。如果一个节点B是另一个节点A的子节点,那么节点A就是节点B的父节点。这种关系形成了二叉树的层次结构。
- 兄弟关系:在二叉树中,具有相同父节点的两个节点被称为兄弟节点。例如,如果一个节点的左子节点和右子节点都存在,那么这两个子节点就是兄弟节点。
前提是数组中arr[.....]的第一个下标为0,如果是[None]+arr[.....],则下标从一开始,给的代码是从1开始的。
3.对于任意节点,假设其父节点的下标(或索引)为 parent
,那么:
- 左子节点的下标
leftchild
可以通过公式leftchild = parent * 2 + 1
计算得出。 - 右子节点的下标
rightchild
可以通过公式rightchild = parent * 2 + 2
计算得出。
反过来,如果已知一个子节点的下标,想要找到其父节点的下标,可以使用公式:
parent = (child - 1) / 2
4.在二叉树中,叶子节点和非叶子节点是构成树形结构的基本元素,它们之间存在明确的关系,并通过特定的方式计算。
-
叶子节点:是那些没有子节点的节点,也就是说它们没有左孩子和右孩子,指向NULL。在二叉树中,叶子节点通常位于树的最底层。
-
非叶子节点:是那些至少有一个子节点的节点,也就是说它们有左孩子或右孩子,或者两者都有。非叶子节点通常位于树的中间或上层,它们连接着其他的节点,形成了树的结构。