1515
1616### 1.2 堆的存储结构
1717
18- 堆的逻辑结构就是一颗完全二叉树。而我们在「07.树 - 01.二叉树 - 01.树与二叉树的基础知识」章节中学过,对于完全二叉树(尤其是满二叉树)来说,采用顺序存储结构(数组)的形式来表示完全二叉树,能够充分利用存储空间。
18+ 堆的逻辑结构就是一颗完全二叉树。如下图所示:
19+
20+ ![ 堆的逻辑结构] ( ../../images/202405092006120.png )
21+
22+ 而我们在「07.树 - 01.二叉树 - 01.树与二叉树的基础知识」章节中学过,对于完全二叉树(尤其是满二叉树)来说,采用顺序存储结构(数组)的形式来表示完全二叉树,能够充分利用存储空间。如下图所示:
23+
24+ ![ 使用顺序存储结构(数组)表示堆] ( ../../images/202405092007823.png )
1925
2026当我们使用顺序存储结构(即数组)来表示堆时,堆中元素的节点编号与数组的索引关系为:
2127
@@ -28,8 +34,6 @@ class MaxHeap:
2834 self .max_heap = []
2935```
3036
31- ![ 堆的存储结构] ( ../../images/20230824154601.png )
32-
3337### 1.3 访问堆顶元素
3438
3539> ** 访问堆顶元素** :指的是从堆结构中获取位于堆顶的元素。
@@ -232,9 +236,7 @@ class MaxHeap:
2322363 . ** 重复交换和调整堆** :
233237 1 . 重复第 $2$ 步,直到堆的大小为 $1$ 时,此时大顶堆的数组已经完全有序。
234238
235- #### 2.2.1 构建初始大顶堆
236-
237- <!-- tabs:start -->
239+ <!-- tabs:start -->
238240
239241#### ** <1>**
240242
@@ -266,9 +268,7 @@ class MaxHeap:
266268
267269<!-- tabs:end -->
268270
269- #### 2.2.2 交换元素,调整堆
270-
271- <!-- tabs:start -->
271+ <!-- tabs:start -->
272272
273273#### ** <1>**
274274
@@ -312,84 +312,19 @@ class MaxHeap:
312312
313313#### ** <11>**
314314
315- ![ 2. 交换元素,调整堆 11 ] ( ../../images/20230831162505.png )
315+ ![ https://qcdn . ] ( ../../images/20230831162505.png )
316316
317317#### ** <12>**
318318
319319![ 2. 交换元素,调整堆 12] ( ../../images/20230831162512.png )
320320
321- <!-- tabs:end -->
321+ <!-- tabs:end -->
322322
323- ### 2.3 堆排序完整代码实现
323+ ### 2.3 堆排序代码实现
324324
325325``` python
326326class MaxHeap :
327- def __init__ (self ):
328- self .max_heap = []
329-
330- def peek (self ) -> int :
331- # 大顶堆为空
332- if not self .max_heap:
333- return None
334- # 返回堆顶元素
335- return self .max_heap[0 ]
336-
337- def push (self , val : int ):
338- # 将新元素添加到堆的末尾
339- self .max_heap.append(val)
340-
341- size = len (self .max_heap)
342- # 从新插入的元素节点开始,进行上移调整
343- self .__shift_up(size - 1 )
344-
345- def __shift_up (self , i : int ):
346- while (i - 1 ) // 2 >= 0 and self .max_heap[i] > self .max_heap[(i - 1 ) // 2 ]:
347- self .max_heap[i], self .max_heap[(i - 1 ) // 2 ] = self .max_heap[(i - 1 ) // 2 ], self .max_heap[i]
348- i = (i - 1 ) // 2
349-
350- def pop (self ) -> int :
351- # 堆为空
352- if not self .max_heap:
353- raise IndexError (" 堆为空" )
354-
355- size = len (self .max_heap)
356- self .max_heap[0 ], self .max_heap[size - 1 ] = self .max_heap[size - 1 ], self .max_heap[0 ]
357- # 删除堆顶元素
358- val = self .max_heap.pop()
359- # 节点数减 1
360- size -= 1
361-
362- self .__shift_down(0 , size)
363-
364- # 返回堆顶元素
365- return val
366-
367-
368- def __shift_down (self , i : int , n : int ):
369- while 2 * i + 1 < n:
370- # 左右子节点编号
371- left, right = 2 * i + 1 , 2 * i + 2
372-
373- # 找出左右子节点中的较大值节点编号
374- if 2 * i + 2 >= n:
375- # 右子节点编号超出范围(只有左子节点
376- larger = left
377- else :
378- # 左子节点、右子节点都存在
379- if self .max_heap[left] >= self .max_heap[right]:
380- larger = left
381- else :
382- larger = right
383-
384- # 将当前节点值与其较大的子节点进行比较
385- if self .max_heap[i] < self .max_heap[larger]:
386- # 如果当前节点值小于其较大的子节点,则将它们交换
387- self .max_heap[i], self .max_heap[larger] = self .max_heap[larger], self .max_heap[i]
388- i = larger
389- else :
390- # 如果当前节点值大于等于于其较大的子节点,此时结束
391- break
392-
327+ ......
393328 def __buildMaxHeap (self , nums : [int ]):
394329 size = len (nums)
395330 # 先将数组 nums 的元素按顺序添加到 max_heap 中
@@ -433,4 +368,4 @@ print(Solution().sortArray([10, 25, 6, 8, 7, 1, 20, 23, 16, 19, 17, 3, 18, 14]))
433368 2 . 在第 $2$ 个循环中,每次调用调整堆算法一次,节点移动的最大距离为这棵完全二叉树的深度 $d = \lfloor \log_2(n) \rfloor + 1$,一共调用了 $n - 1$ 次调整堆算法,所以,第 $2$ 个循环的时间花费为 $(n-1)(\lfloor \log_2 (n)\rfloor + 1) = O(n \times \log n)$。
434369 - 因此,堆积排序的时间复杂度为 $O(n \times \log n)$。
435370- ** 空间复杂度** :$O(1)$。由于在堆积排序中只需要一个记录大小的辅助空间,因此,堆积排序的空间复杂度为:$O(1)$。
436- - ** 排序稳定性** :在进行「下移调整」时,相等元素的相对位置可能会发生变化。因此,堆排序是一种 ** 不稳定排序算法** 。
371+ - ** 排序稳定性** :在进行「下移调整」时,相等元素的相对位置可能会发生变化。因此,堆排序是一种 ** 不稳定排序算法** 。
0 commit comments